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.inventory.BuyGoodResult; import com.alterdekim.game.component.game.inventory.BuyGoodResultGood; 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.Good; 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.javatuples.Pair; import org.javatuples.Triplet; import org.javatuples.Tuple; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.io.IOException; import java.util.*; import java.util.stream.Collectors; import java.util.stream.Stream; @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; @Autowired private GoodsService goodsService; private final Map players; private final ObjectMapper xmlMapper; public GameServer() { this.players = new HashMap<>(); this.xmlMapper = new XmlMapper().registerModule(new JavaTimeModule()); } public void onConnect(int 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(int playerId, List params ) { try { log.info("GameServer.onMessage() pid: {}, params: {}", playerId, params); switch(CommandType.fromString( params.get(0).toString() )) { 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(int playerId, SetPlayerAvatarPosition message) { if( message.getCoordinates() != null ) { players.get(playerId).setX(message.getCoordinates().get("x").getDouble()); players.get(playerId).setY(message.getCoordinates().get("y").getDouble()); } this.sendInPlayersLocation(playerId, CommandType.SetUserAvatarPosition, new SetPlayerPosition( playerId, new Point(players.get(playerId).getX(), players.get(playerId).getY()), message.getTweenerId().longValue() ) ); } private void setPlayerAvatarState(int playerId, SetPlayerAvatarState message) { players.get(playerId).setState(message.getState()); this.sendInPlayersLocation(playerId, CommandType.SetUserAvatarState, new SetPlayerState(playerId, message.getState()) ); } private void processUserMessage(int 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, message.getArguments().get(0).getInt()); this.call(message.getArguments().get(0).getInt(), CommandType.OnFriendsRemoved, playerId); } case ApplyUserFriendship -> this.applyUserFriendship(playerId, message.getArguments().get(0).toString(), message.getArguments().get(1).toString()); 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 = message.getArguments().get(2).getDouble(); if (prevLocation == 0.0d) { this.sendResult(playerId, message.getTransactionId(), xmlMapper.writeValueAsString( locationService.getDefaultLocation() ) ); return; } long requestedLocation = message.getArguments().get(1).getLong(); if( requestedLocation == -1L ) { int homePlayerId = message.getArguments().get(0).getInt(); this.sendResult(playerId, message.getTransactionId(), xmlMapper.writeValueAsString(locationService.getHomeByUserId(homePlayerId))); return; } if( requestedLocation == -2L ) { return; } this.sendResult(playerId, message.getTransactionId(), xmlMapper.writeValueAsString(locationService.getLocationById(requestedLocation))); } case GetUserLocationById -> { long nextLocation = message.getArguments().get(1).getLong(); if (nextLocation == -1) { nextLocation = 0; } this.sendResult(playerId, message.getTransactionId(), xmlMapper.writeValueAsString(locationService.getLocationById(nextLocation))); } case ChatMessage -> { String text = message.getArguments().get(1).toString(); sendInPlayersLocation(playerId, CommandType.ChatMessage, new ChatMessage(playerId, text)); } case Shoot -> { if (message.getSrv().equals("ROOM")) { double x = message.getArguments().get(1).getDouble(); double y = message.getArguments().get(2).getDouble(); 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, message.getArguments().get(0).getInt(), message); case GetUserAvatar -> getAvatar(playerId, message); case SaveUserAvatarChanges -> saveAvatar(playerId, message); case GetUserUnlocks -> this.sendResult(playerId, message.getTransactionId(), "123"); case GetSnInvitePanelData -> {} case GetGrantBlocks -> {} case UseMagicAbility -> this.sendInPlayersLocation(playerId, CommandType.UseMagicAbility, new UseMagicAbility( message.getArguments().get(0).getInt(), message.getArguments().get(1).getInt() )); case SetDefaultUserPhone -> { long phoneId = message.getArguments().get(0).getLong(); this.users.setDefaultPhone(playerId, phoneId); } case SetUserBackground -> { long bgId = message.getArguments().get(0).getLong(); this.users.setDefaultBackground(playerId, bgId); } case QueryUserFriendship -> { int participantId = message.getArguments().get(0).getInt(); 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 -> { int receiverId = message.getArguments().get(0).getInt(); if (receiverId <= 0) return; String messageText = message.getArguments().get(1).toString(); 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 = message.getArguments().get(0).getLong(); phoneMessageService.markAsRead(messageId); } case DeletePhoneMessage -> { Long messageId1 = message.getArguments().get(0).getLong(); phoneMessageService.removeById(messageId1); } case CardMessage -> { if( !subtractUsualTickets(playerId, 20) ) return; int receiverId = message.getArguments().get(0).getInt(); int postcardId = message.getArguments().get(1).getInt(); 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 = message.getArguments().get(0).getLong(); this.postcardService.markAsRead(postcardMessageId); } case DeleteCardMessage -> { long postcardMessageId = message.getArguments().get(0).getLong(); this.postcardService.removeById(postcardMessageId); } case BuyBatch -> buySomething(playerId, message.getArguments().get(0), message); case LockHouse -> lockSomething(playerId, message.getArguments().get(0).getInt(), message.getArguments().get(1).getBoolean(), message); } } private void lockSomething(int playerId, int what2Lock, Boolean state, UserMessage message) { if( what2Lock == -2 ) { this.users.pushUserProperty(playerId, PlayerProperties.IsClubLocked, state); } else { this.users.pushUserProperty(playerId, PlayerProperties.IsHomeLocked, state); } this.sendResult(playerId, message.getTransactionId(), state); } private void buySomething(int playerId, AMFObject obj, UserMessage message) { List array = (List) obj.getRaw(); array.forEach(i -> this.buyOne(playerId, i, message)); } private void buyOne(int playerId, AMFObject item, UserMessage message) { Map obj = (Map) item.getRaw(); long goodId = obj.get("GoodId").getLong(); boolean isMagic = obj.get("Currency").getLong() == 2; Optional tGood = this.goodsService.findById(goodId); if( tGood.isEmpty() ) { this.sendResult(playerId, message.getTransactionId(), new BuyGoodResult(new ArrayList<>())); return; } Good good = tGood.get(); AvatarInventoryType invType = AvatarInventoryType.fromGoodType(good.getGoodTypeId()); if( isMagic ) { if( !subtractMagicTickets(playerId, good.getMagicTickets()) ) { this.sendResult(playerId, message.getTransactionId(), new BuyGoodResult(new ArrayList<>())); return; } this.users.pushToInventory(playerId, invType, goodId); this.sendResult(playerId, message.getTransactionId(), new BuyGoodResultGood(true, new ArrayList<>())); return; } if( !subtractUsualTickets(playerId, good.getUsualTickets()) ) { this.sendResult(playerId, message.getTransactionId(), new BuyGoodResult(new ArrayList<>())); return; } this.users.pushToInventory(playerId, invType, goodId); this.sendResult(playerId, message.getTransactionId(), new BuyGoodResultGood(true, new ArrayList<>())); } private boolean subtractUsualTickets(int 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(int 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(Integer userId, String acceptString, String denyString) { List accepted = Arrays.stream(acceptString.split(",")) .filter(s -> !s.isEmpty()) .map(Integer::parseInt) .collect(Collectors.toList()); List denied = Arrays.stream(denyString.split(",")) .filter(s -> !s.isEmpty()) .map(Integer::parseInt) .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 saveAvatar(int playerId, UserMessage message) { Map changes = (Map) message.getArguments().get(0).getRaw(); List bodyParts = (List) changes.get("BodyParts").getRaw(); List inventory = (List) changes.get("Inventory").getRaw(); List> l = Stream.concat( bodyParts.stream().map(f -> (Map) f.getRaw()).map(f -> Triplet.with(f.get("Id").getLong(), f.get("Color").getRaw(), AvatarInventoryType.BodyParts)), inventory.stream().map(f -> (Map) f.getRaw()).map(f -> Triplet.with(f.get("Id").getLong(), f.get("Color").getRaw(), AvatarInventoryType.fromGoodId(goodsService, f.get("Id").getLong()))) ).collect(Collectors.toList()); users.resetUsedClothes(playerId); for(var i: l) { var s = String.valueOf(i.getValue1()); int color = 0; if( !s.equals("NaN") ) { color = ((Double) i.getValue1()).intValue(); } users.setDefaultInventoryItem(playerId, i.getValue0(), i.getValue2(), color); } this.sendResult(playerId, message.getTransactionId(), new ArrayList<>()); this.updateLocationPlayers(playerId, this.players.get(playerId).getLocationId()); } private void getAvatar(int playerId, UserMessage message) throws JsonProcessingException { String r = xmlMapper.writeValueAsString(users.getUserAvatarByUserId(playerId)); r = r.substring(13); r = r.substring(0, r.length() - 14); this.sendResult(playerId, message.getTransactionId(), r); } private void getUserInfo(int playerId, int 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(int playerId, SetLocationMessage message) { Player p = this.players.get(playerId); this.deleteSelf(playerId, p.getLocationId()); long prevLocation = p.getLocationId(); p.setLocationId(GameUtils.extractLocationId(message.getLocation())); p.setX(message.getCoordinates().get("x").getDouble()); p.setY(message.getCoordinates().get("y").getDouble()); p.setState(message.getStartState()); this.updateLocationPlayers(playerId, prevLocation); this.sendResult(playerId, message.getTransactionId(), ""); } private void deleteSelf(int playerId, long locationId) { Player p = this.players.get(playerId); this.sendInLocation(locationId, CommandType.RemoveUserFromLocation, playerId); } private void updateLocationPlayers(int playerId, long prevLocation) { long locationId = this.players.get(playerId).getLocationId(); this.sendInLocation(prevLocation, CommandType.RemoveUserFromLocation, playerId); for( int i = 0; i < 3; i++ ) { try { Thread.sleep(300); this.sendInPlayersLocation(playerId, CommandType.AddUserToLocation, new AddUserToLocation( playerId, users.getAvatarById(playerId, this.players.get(playerId)) )); } catch (InterruptedException ie) { log.error("Sleep exception: {}", ie.getMessage()); } } 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(int playerId, CommandType type, Object obj) { Player player = this.players.get(playerId); this.sendInLocation(player.getLocationId(), type, obj); } private void sendInLocation(long locationId, CommandType type, Object obj) { players.keySet().forEach(k -> { if( locationId != players.get(k).getLocationId() ) return; this.call(k, type, obj); }); } private void sendResult(int playerId, double tid, Object obj) { this.sendMessage(playerId, new CallMessage("_result", tid, null, obj)); } private void call(int playerId, CommandType function, Object obj) { this.sendMessage(playerId, new CallMessage(function.getValue(), 0d, null, obj)); } private void sendMessage(int 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(int 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(); } }