package com.alterdekim.game.component; import com.alterdekim.game.component.game.*; import com.alterdekim.game.component.game.friends.OneFR; import com.alterdekim.game.component.game.friends.UserFriend; import com.alterdekim.game.component.game.response.init.GetUserInfo; import com.alterdekim.game.component.game.response.location.AddUserToLocation; import com.alterdekim.game.component.rtmp.ConnectedProcessor; import com.alterdekim.game.entity.PhoneMessage; import com.alterdekim.game.entity.Postcard; import com.alterdekim.game.message.*; import com.alterdekim.game.message.amf.AMFMapper; import com.alterdekim.game.message.amf.AMFObject; import com.alterdekim.game.message.amf.AMFSerializer; import com.alterdekim.game.message.amf.AMFValueType; import com.alterdekim.game.security.AuthenticationUtil; import com.alterdekim.game.service.*; import com.alterdekim.game.utils.GameUtils; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.dataformat.xml.XmlMapper; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.io.IOException; import java.util.*; import java.util.stream.Collectors; @Slf4j @Component public class GameServer { @Autowired private UserService users; @Autowired private LocationService locationService; @Autowired private RelationshipService relationshipService; @Autowired private PhoneMessageService phoneMessageService; @Autowired private PostcardService postcardService; private final Map players; private final ObjectMapper xmlMapper; public GameServer() { this.players = new HashMap<>(); this.xmlMapper = new XmlMapper().registerModule(new JavaTimeModule()); } public void onConnect(long playerId, String hwId, ConnectedProcessor connectedProcessor) { log.warn("GameServer.onConnect() connect: {}", playerId); Optional username = users.getUsernameById(playerId); if( username.isEmpty() || !AuthenticationUtil.hwIdAuth(users.findByUsername(username.get()), hwId) || this.players.containsKey(playerId) ) { connectedProcessor.close(); return; } this.players.put(playerId, new Player(connectedProcessor, username.get())); } public void onMessage(long playerId, List params ) { try { log.info("GameServer.onMessage() pid: {}, params: {}", playerId, params); switch(CommandType.fromString( (String) params.get(0).getValue() )) { case UserCommand: processUserMessage(playerId, AMFMapper.deobfuscate(params.subList(1, params.size()), UserMessage.class)); break; case SetLocation: setPlayerLocation(playerId, AMFMapper.deobfuscate(params.subList(1, params.size()), SetLocationMessage.class)); break; case SetUserAvatarState: setPlayerAvatarState(playerId, AMFMapper.deobfuscate(params.subList(1, params.size()), SetPlayerAvatarState.class)); break; case SetUserAvatarPosition: setPlayerAvatarPosition(playerId, AMFMapper.deobfuscate(params.subList(1, params.size()), SetPlayerAvatarPosition.class)); break; default: log.warn("GameServer.onMessage() unknown command: {}", params.get(0)); } } catch (Exception e) { log.error("GameServer.onMessage() error: {}", e.getMessage(), e); } } private void setPlayerAvatarPosition(long playerId, SetPlayerAvatarPosition message) { players.get(playerId).setX((Double) message.getCoordinates().get("x").getValue()); players.get(playerId).setY((Double) message.getCoordinates().get("y").getValue()); this.sendInPlayersLocation(playerId, CommandType.SetUserAvatarPosition, new SetPlayerPosition( playerId, new Point((Double) message.getCoordinates().get("x").getValue(), (Double) message.getCoordinates().get("y").getValue()), message.getTweenerId().longValue() ) ); } private void setPlayerAvatarState(long playerId, SetPlayerAvatarState message) { players.get(playerId).setState(message.getState()); this.sendInPlayersLocation(playerId, CommandType.SetUserAvatarState, new SetPlayerState(playerId, message.getState()) ); } private void processUserMessage(long playerId, UserMessage message) throws JsonProcessingException { log.info("GameServer.processUserMessage() message: {}", message); switch (UserCommandType.fromString(message.getCommandType())) { case GetUserInitData -> { String r = xmlMapper.writeValueAsString(users.getUserInitInfoByUserId(playerId)); this.sendResult(playerId, message.getTransactionId(), r); } case UserFriendsGet, GetOnlineUserFriends -> this.sendResult(playerId, message.getTransactionId(), users.getFriendsOfUser(playerId, players)); case UserFriendsRequests -> this.sendResult(playerId, message.getTransactionId(), users.getRequestsOfUser(playerId, players)); case RevokeUserFriendship -> { users.removeFriendshipWithUser(playerId, Long.parseLong((String) message.getArguments().get(0).getValue())); this.call(Long.parseLong((String) message.getArguments().get(0).getValue()), CommandType.OnFriendsRemoved, playerId); } case ApplyUserFriendship -> this.applyUserFriendship(playerId, (String) message.getArguments().get(0).getValue(), (String) message.getArguments().get(1).getValue()); case GetClubMap -> // todo: implement this.sendResult(playerId, message.getTransactionId(), ""); case GetFriendPanelData -> this.sendResult(playerId, message.getTransactionId(), ""); case UpdateUserData -> this.sendResult(playerId, message.getTransactionId(), null); case GetUserLocation -> { double prevLocation = (Double) message.getArguments().get(2).getValue(); if (prevLocation == 0.0d) { this.sendResult(playerId, message.getTransactionId(), xmlMapper.writeValueAsString( locationService.getDefaultLocation() ) ); } } case ChatMessage -> { String text = (String) message.getArguments().get(1).getValue(); sendInPlayersLocation(playerId, CommandType.ChatMessage, new ChatMessage(playerId, text)); } case Shoot -> { if (message.getSrv().equals("ROOM")) { double x = (double) message.getArguments().get(1).getValue(); double y = (double) message.getArguments().get(2).getValue(); sendInPlayersLocation(playerId, CommandType.Shoot, new Shoot(playerId, x, y)); break; } this.sendInPlayersLocation(playerId, CommandType.SetUserAvatarState, new SetPlayerState(playerId, players.get(playerId).getState()) ); this.users.setWeaponsCount(playerId, this.users.getIntegerUserProperty(playerId, PlayerProperties.WeaponsCount, 15) - 1); } case GetUserInfo -> getUserInfo(playerId, ((Double) message.getArguments().get(0).getValue()).longValue(), message); case GetUserAvatar -> // todo: Implement avatar saving getAvatar(playerId, message); case GetSnInvitePanelData -> {} case GetGrantBlocks -> {} case UseMagicAbility -> this.sendInPlayersLocation(playerId, CommandType.UseMagicAbility, new UseMagicAbility( Long.parseLong((String) message.getArguments().get(0).getValue()), ((Double) message.getArguments().get(1).getValue()).intValue() )); case SetDefaultUserPhone -> { long phoneId = ((Double) message.getArguments().get(0).getValue()).longValue(); this.users.setDefaultPhone(playerId, phoneId); } case SetUserBackground -> { long bgId = ((Double) message.getArguments().get(0).getValue()).longValue(); this.users.setDefaultBackground(playerId, bgId); } case QueryUserFriendship -> { long participantId = ((Double) message.getArguments().get(0).getValue()).longValue(); relationshipService.sendRequest(playerId, participantId); this.call(participantId, CommandType.OnFriendshipRequestAdded, new OneFR( new UserFriend(playerId, players.get(playerId).getUsername(), players.get(playerId).getUsername(), true) ) ); } case GetPhoneBalance -> { Integer balance = users.getUserAccountByUserId(playerId).getPhoneCardBalance(); this.sendResult(playerId, message.getTransactionId(), balance); } case PhoneMessage -> { long receiverId = message.getArguments().get(0).getType() == AMFValueType.NUMBER ? ((Double) message.getArguments().get(0).getValue()).longValue() : Long.parseLong((String) message.getArguments().get(0).getValue()); if (receiverId <= 0) return; String messageText = ((String) message.getArguments().get(1).getValue()); PhoneMessage pm = new PhoneMessage(); pm.setText(messageText); pm.setSenderId(playerId); pm.setReceiverId(receiverId); pm.setIsNew(true); pm.setSenderName(players.get(playerId).getUsername()); pm.setDateSent(""); phoneMessageService.savePhoneMessage(pm); this.call(receiverId, CommandType.AddNewPhoneMessage, xmlMapper.writeValueAsString(pm)); } case MarkPhoneMessageAsRead -> { Long messageId = ((Double) message.getArguments().get(0).getValue()).longValue(); phoneMessageService.markAsRead(messageId); } case DeletePhoneMessage -> { Long messageId1 = ((Double) message.getArguments().get(0).getValue()).longValue(); phoneMessageService.removeById(messageId1); } case CardMessage -> { if( !subtractUsualTickets(playerId, 20) ) return; long receiverId = ((Number) message.getArguments().get(0).getValue()).longValue(); int postcardId = ((Number) message.getArguments().get(1).getValue()).intValue(); Postcard postcard = new Postcard(); postcard.setIsNew(true); postcard.setDateSent(""); postcard.setSenderId(playerId); postcard.setReceiverId(receiverId); postcard.setSenderName(players.get(playerId).getUsername()); postcard.setCardId(postcardId); postcardService.savePostcard(postcard); this.call(receiverId, CommandType.CardMessage, xmlMapper.writeValueAsString(postcard)); } case MarkCardMessageAsRead -> { long postcardMessageId = ((Number) message.getArguments().get(0).getValue()).longValue(); this.postcardService.markAsRead(postcardMessageId); } case DeleteCardMessage -> { long postcardMessageId = ((Number) message.getArguments().get(0).getValue()).longValue(); this.postcardService.removeById(postcardMessageId); } } } private boolean subtractUsualTickets(long userId, int amount) { int val = this.users.getUsualTickets(userId); if ( val >= amount ) { val -= amount; this.users.setUsualTickets(userId, val); this.call(userId, CommandType.UpdateTickets, new UpdateTickets(val, this.users.getMagicTickets(userId))); return true; } return false; } private boolean subtractMagicTickets(long userId, int amount) { int val = this.users.getMagicTickets(userId); if ( val >= amount ) { val -= amount; this.users.setMagicTickets(userId, val); this.call(userId, CommandType.UpdateTickets, new UpdateTickets(this.users.getUsualTickets(userId), val)); return true; } return false; } private void applyUserFriendship(Long userId, String acceptString, String denyString) { List accepted = Arrays.stream(acceptString.split(",")) .filter(s -> !s.isEmpty()) .map(Long::parseLong) .collect(Collectors.toList()); List denied = Arrays.stream(denyString.split(",")) .filter(s -> !s.isEmpty()) .map(Long::parseLong) .collect(Collectors.toList()); users.acceptFriendshipWithUsers(userId, accepted); users.denyFriendshipsWithUsers(userId, denied); Map m = new HashMap<>(); m.put("1", new UserFriend(userId, "", players.get(userId).getUsername(), true)); accepted.forEach(uid -> this.call(uid, CommandType.OnFriendsAdded, m)); } private void getAvatar(long playerId, UserMessage message) throws JsonProcessingException { String r = xmlMapper.writeValueAsString(users.getUserAvatarByUserId(playerId)); r = r.substring(13); r = r.substring(0, r.length() - 14); log.info("getAvatar: {}", r); this.sendResult(playerId, message.getTransactionId(), r); } private void getUserInfo(long playerId, long targetId, UserMessage message) throws JsonProcessingException { String r = xmlMapper.writeValueAsString(new GetUserInfo(users.getUserInfoByUserId(targetId, false, playerId), users.getOtherUserAvatar(targetId))); r = r.substring(11); r = r.substring(0, r.length()-12); this.sendResult(playerId, message.getTransactionId(), r); } private void setPlayerLocation(long playerId, SetLocationMessage message) { Player p = this.players.get(playerId); this.deleteSelf(playerId, p.getLocationId()); int prevLocation = p.getLocationId(); p.setLocationId(GameUtils.extractLocationId(message.getLocation())); p.setX((Double) message.getCoordinates().get("x").getValue()); p.setY((Double) message.getCoordinates().get("y").getValue()); p.setState(message.getStartState()); this.updateLocationPlayers(playerId, prevLocation); this.sendResult(playerId, message.getTransactionId(), ""); } private void deleteSelf(long playerId, int locationId) { this.sendInLocation(locationId, CommandType.RemoveUserFromLocation, playerId); } private void updateLocationPlayers(long playerId, int prevLocation) { int locationId = this.players.get(playerId).getLocationId(); this.sendInLocation(prevLocation, CommandType.RemoveUserFromLocation, playerId); for( int i = 0; i < 5; i++ ) { this.sendInPlayersLocation(playerId, CommandType.AddUserToLocation, new AddUserToLocation( playerId, users.getAvatarById(playerId, this.players.get(playerId)) )); } this.players.keySet().forEach(pid -> { Player p = this.players.get(pid); if( p.getLocationId() != locationId ) return; this.call(playerId, CommandType.AddUserToLocation, new AddUserToLocation( pid, users.getAvatarById(pid, this.players.get(pid)) )); this.call(playerId, CommandType.SetUserAvatarState, new SetPlayerState( pid, this.players.get(pid).getState() )); }); } private void sendInPlayersLocation(long playerId, CommandType type, Object obj) { this.sendInLocation(players.get(playerId).getLocationId(), type, obj); } private void sendInLocation(int locationId, CommandType type, Object obj) { players.keySet().forEach(k -> { if( locationId != players.get(k).getLocationId() ) return; this.call(k, type, obj); }); } private void sendResult(long playerId, double tid, Object obj) { this.sendMessage(playerId, new CallMessage("_result", tid, null, obj)); } private void call(long playerId, CommandType function, Object obj) { this.sendMessage(playerId, new CallMessage(function.getValue(), 0d, null, obj)); } private void sendMessage(long playerId, Object obj) { try { this.players.get(playerId) .getConnection() .write_packet(AMFSerializer.serialize(obj)); } catch (IOException e) { log.error("Unable to send message: {}", e.getMessage()); } } public void onDisconnect(long playerId) { log.warn("GameServer.onDisconnect() close: {}", playerId); if( !this.players.containsKey(playerId) ) return; this.deleteSelf(playerId, this.players.get(playerId).getLocationId()); this.players.remove(playerId); } public int getPlayersCount() { return this.players.size(); } }