// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . // ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· // ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ // ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ // ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ // Magicbane Emulator Project © 2013 - 2022 // www.magicbane.com package engine.net.client; import engine.Enum.DispatchChannel; import engine.Enum.GameObjectType; import engine.InterestManagement.WorldGrid; import engine.exception.MsgSendException; import engine.gameManager.*; import engine.job.JobScheduler; import engine.jobs.RefreshGroupJob; import engine.net.Dispatch; import engine.net.DispatchMessage; import engine.net.NetMsgHandler; import engine.net.client.handlers.AbstractClientMsgHandler; import engine.net.client.msg.*; import engine.net.client.msg.chat.AbstractChatMsg; import engine.net.client.msg.commands.ClientAdminCommandMsg; import engine.objects.*; import engine.server.MBServerStatics; import engine.server.world.WorldServer; import engine.session.Session; import engine.util.StringUtils; import org.pmw.tinylog.Logger; import java.sql.SQLException; import static engine.math.FastMath.sqr; /** * @author: * @summary: This class is the mainline router for application protocol * messages received by the client. */ public class ClientMessagePump implements NetMsgHandler { // Instance variable declaration private final WorldServer server; public ClientMessagePump(WorldServer server) { super(); this.server = server; } private static void toggleLfgRecruiting(ToggleLfgRecruitingMsg msg, ClientConnection origin) throws MsgSendException { PlayerCharacter pc = SessionManager.getPlayerCharacter(origin); if (pc == null) return; int num = msg.toggleLfgRecruiting(); if (num == 1) pc.toggleLFGroup(); else if (num == 2) pc.toggleLFGuild(); else if (num == 3) pc.toggleRecruiting(); UpdateStateMsg rwss = new UpdateStateMsg(); rwss.setPlayer(pc); DispatchMessage.dispatchMsgToInterestArea(pc, rwss, DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, true, false); } private static void toggleSitStand(ToggleSitStandMsg msg, ClientConnection origin) throws MsgSendException { PlayerCharacter pc = SessionManager.getPlayerCharacter(origin); if (pc == null) return; pc.update(); pc.setSit(msg.toggleSitStand()); // cancel effects that break on sit if (pc.isSit()) { pc.setCombat(false); pc.cancelOnSit(); } UpdateStateMsg rwss = new UpdateStateMsg(); if (pc.isSit()) { pc.setCombat(false); rwss.setAware(1); } rwss.setPlayer(pc); DispatchMessage.dispatchMsgToInterestArea(pc, rwss, DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, true, false); } private static void ackBankWindowOpened(AckBankWindowOpenedMsg msg, ClientConnection origin) { // According to the Wiki, the client should not send this message. // Log the instance to investigate, and modify Wiki accordingly. Logger.error(msg.toString()); } private static void modifyStat(ModifyStatMsg msg, ClientConnection origin) { PlayerCharacter pc = SessionManager.getPlayerCharacter(origin); if (pc == null) return; int type = msg.getType(); switch (type) { case MBServerStatics.STAT_STR_ID: pc.addStr(msg.getAmount()); break; case MBServerStatics.STAT_DEX_ID: pc.addDex(msg.getAmount()); break; case MBServerStatics.STAT_CON_ID: pc.addCon(msg.getAmount()); break; case MBServerStatics.STAT_INT_ID: pc.addInt(msg.getAmount()); break; case MBServerStatics.STAT_SPI_ID: pc.addSpi(msg.getAmount()); break; } } // called when player clicks respawn button private static void respawn(RespawnMsg msg, ClientConnection origin) throws MsgSendException { PlayerCharacter sourcePlayer = SessionManager.getPlayerCharacter(origin); if (sourcePlayer == null) return; if (msg.getObjectType() != sourcePlayer.getObjectType().ordinal() || msg.getObjectID() != sourcePlayer.getObjectUUID()) { Logger.error("Player " + sourcePlayer.getObjectUUID() + " respawning character of id " + msg.getObjectType() + ' ' + msg.getObjectID()); return; } if (sourcePlayer.isAlive()) { Logger.error("Player " + sourcePlayer.getObjectUUID() + " respawning while alive"); return; } // ResetAfterDeath player sourcePlayer.respawnLock.writeLock().lock(); try { sourcePlayer.respawn(true, false, true); } catch (Exception e) { Logger.error(e); } finally { sourcePlayer.respawnLock.writeLock().unlock(); } // Echo ResetAfterDeath message back msg.setPlayerHealth(sourcePlayer.getHealth()); // TODO calculate any experience loss before this point msg.setPlayerExp(sourcePlayer.getExp() + sourcePlayer.getOverFlowEXP()); Dispatch dispatch = Dispatch.borrow(sourcePlayer, msg); DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.PRIMARY); MoveToPointMsg moveMsg = new MoveToPointMsg(); moveMsg.setPlayer(sourcePlayer); moveMsg.setStartCoord(sourcePlayer.getLoc()); moveMsg.setEndCoord(sourcePlayer.getLoc()); moveMsg.setInBuilding(-1); moveMsg.setInBuildingFloor(-1); dispatch = Dispatch.borrow(sourcePlayer, moveMsg); DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.PRIMARY); MovementManager.sendRWSSMsg(sourcePlayer); // refresh the whole group with what just happened JobScheduler.getInstance().scheduleJob(new RefreshGroupJob(sourcePlayer), MBServerStatics.LOAD_OBJECT_DELAY); } private static void loot(LootMsg msg, ClientConnection origin) throws MsgSendException { PlayerCharacter player = SessionManager.getPlayerCharacter(origin); if (player == null) return; if (!player.isAlive()) return; Item item = msg.getItem(); if (item == null) return; if (item.lootLock.tryLock()) { try { Item itemRet = null; // get current owner int targetType = msg.getTargetType(); int targetID = msg.getTargetID(); if (targetType == GameObjectType.PlayerCharacter.ordinal() || targetType == GameObjectType.Mob.ordinal() || targetType == GameObjectType.Corpse.ordinal()) { } else { //needed for getting contracts for some reason targetType = msg.getSourceID2(); targetID = msg.getUnknown01(); } //can't loot while flying if (player.getAltitude() > 0) return; AbstractCharacter tar = null; Corpse corpse = null; if (targetType == GameObjectType.PlayerCharacter.ordinal() || targetType == GameObjectType.Mob.ordinal()) { if (targetType == GameObjectType.PlayerCharacter.ordinal()) { tar = PlayerCharacter.getFromCache(targetID); if (tar == null) return; if (player.getObjectUUID() != tar.getObjectUUID() && ((PlayerCharacter) tar).isInSafeZone()) return; } else if (targetType == GameObjectType.NPC.ordinal()) tar = NPC.getFromCache(targetID); else if (targetType == GameObjectType.Mob.ordinal()) tar = Mob.getFromCache(targetID); if (tar == null) return; if (tar.equals(player)) { ErrorPopupMsg.sendErrorMsg(player, "Cannot loot this item."); return; } if (player.getLoc().distanceSquared2D(tar.getLoc()) > sqr(MBServerStatics.LOOT_RANGE)) { ErrorPopupMsg.sendErrorMsg(player, "You are too far away to loot this corpse."); Logger.info(player.getFirstName() + " tried looting at " + player.getLoc().distance2D(tar.getLoc()) + " distance."); return; } //can't loot from someone who is alive. if (AbstractWorldObject.IsAbstractCharacter(tar)) { if (tar.isAlive()) return; // Logger.error("WorldServer.loot", "Looting from live player"); } if (!GroupManager.goldSplit(player, item, origin, tar)) { if (tar.charItemManager != null) { itemRet = tar.charItemManager.lootItemFromMe(item, player, origin); //Take equipment off mob if (tar.getObjectType() == GameObjectType.Mob && itemRet != null) { Mob mobTarget = (Mob) tar; if (item != null && item.getObjectType() == GameObjectType.MobLoot) { for (Item equip : mobTarget.charItemManager.equipped.values()) { TransferItemFromEquipToInventoryMsg back = new TransferItemFromEquipToInventoryMsg(mobTarget, equip.equipSlot); DispatchMessage.dispatchMsgToInterestArea(mobTarget, back, DispatchChannel.SECONDARY, MBServerStatics.CHARACTER_LOAD_RANGE, false, false); LootMsg lootMsg = new LootMsg(0, 0, tar.getObjectType().ordinal(), tar.getObjectUUID(), equip); Dispatch dispatch = Dispatch.borrow(player, lootMsg); DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); break; } } } } } } else if (targetType == GameObjectType.Corpse.ordinal()) { corpse = Corpse.getCorpse(targetID); if (corpse == null) return; if (player.getLoc().distanceSquared2D(corpse.getLoc()) > sqr(MBServerStatics.LOOT_RANGE)) { ErrorPopupMsg.sendErrorMsg(player, "You are too far away to loot this corpse."); Logger.info(player.getFirstName() + " tried looting at " + player.getLoc().distance2D(corpse.getLoc()) + " distance."); return; } //can't loot other players in safe zone. if (corpse.getBelongsToType() == GameObjectType.PlayerCharacter.ordinal()) { if (player.getObjectUUID() == corpse.getBelongsToID()) itemRet = corpse.lootItem(item, player); else if (!GroupManager.goldSplit(player, item, origin, corpse)) { itemRet = corpse.lootItem(item, player); } if (itemRet == null) return; if (item.template.item_type.equals(engine.Enum.ItemType.GOLD)) { // this is done to prevent the temporary goldItem item // (from the mob) from appearing in player's inventory. // It also updates the goldItem quantity display UpdateGoldMsg updateTargetGold = null; if (corpse != null) updateTargetGold = new UpdateGoldMsg(corpse); updateTargetGold.configure(); DispatchMessage.dispatchMsgToInterestArea(corpse, updateTargetGold, DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, false, false); UpdateGoldMsg ugm = new UpdateGoldMsg(player); ugm.configure(); Dispatch dispatch = Dispatch.borrow(player, ugm); DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); // respond back loot message. Try sending to everyone. } else { DispatchMessage.dispatchMsgToInterestArea(corpse, msg, DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, false, true); //player.getCharItemManager().updateInventory(); } //TODO send group loot message if player is grouped and visible Group group = GroupManager.getGroup(player); if (group != null && group.getSplitGold() && (item.template.item_type.equals(engine.Enum.ItemType.GOLD) == false)) { String name = item.getName(); String text = player.getFirstName() + " has looted " + name + '.'; ChatManager.chatGroupInfoCanSee(player, text); } return; } } else return; if (itemRet == null) { return; } if (item.template.item_type.equals(engine.Enum.ItemType.GOLD)) { // this is done to prevent the temporary goldItem item // (from the mob) from appearing in player's inventory. // It also updates the goldItem quantity display UpdateGoldMsg updateTargetGold = null; if (tar != null) updateTargetGold = new UpdateGoldMsg(tar); else if (corpse != null) updateTargetGold = new UpdateGoldMsg(corpse); updateTargetGold.configure(); DispatchMessage.dispatchMsgToInterestArea(tar, updateTargetGold, DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, true, false); UpdateGoldMsg ugm = new UpdateGoldMsg(player); ugm.configure(); Dispatch dispatch = Dispatch.borrow(player, ugm); DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); // respond back loot message. Try sending to everyone. } else { msg.setSourceType1(0); msg.setSourceType2(0); msg.setSourceID1(0); msg.setSourceID2(0); Dispatch dispatch = Dispatch.borrow(player, msg); //DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.PRIMARY); DispatchMessage.dispatchMsgToInterestArea(tar, msg, DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, false, true); LootMsg newItemMsg = new LootMsg(GameObjectType.PlayerCharacter.ordinal(), player.getObjectUUID(), 0, 0, itemRet); dispatch = Dispatch.borrow(player, newItemMsg); DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.PRIMARY); //player.getCharItemManager().updateInventory(); } //TODO send group loot message if player is grouped and visible Group group = GroupManager.getGroup(player); if (group != null && group.getSplitGold() && (item.template.item_type.equals(engine.Enum.ItemType.GOLD) == false)) { String name = item.getName(); String text = player.getFirstName() + " has looted " + name + '.'; ChatManager.chatGroupInfoCanSee(player, text); } } catch (Exception e) { Logger.info(e.getMessage()); } finally { item.lootLock.unlock(); } } } // called when player types /show private static void show(ShowMsg msg, ClientConnection origin) throws MsgSendException { PlayerCharacter pc = SessionManager.getPlayerCharacter(origin); if (pc == null) return; int targetType = msg.getTargetType(); AbstractCharacter tar = null; if (targetType == GameObjectType.PlayerCharacter.ordinal()) tar = PlayerCharacter.getFromCache(msg.getTargetID()); else if (targetType == GameObjectType.NPC.ordinal()) tar = NPC.getFromCache(msg.getTargetID()); else if (targetType == GameObjectType.Mob.ordinal()) tar = Mob.getFromCache(msg.getTargetID()); if (tar == null || !tar.isAlive() || !tar.isActive()) return; msg.setUnknown01(pc.getLoc()); msg.setUnknown02(pc.getLoc()); msg.setRange01(pc.getRange()); msg.setUnknown03(tar.getLoc()); msg.setUnknown04(tar.getLoc()); msg.setRange01(tar.getRange()); Dispatch dispatch = Dispatch.borrow(pc, msg); DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); } private static void ViewResourcesMessage(ViewResourcesMessage msg, ClientConnection origin) throws SQLException { PlayerCharacter player = SessionManager.getPlayerCharacter(origin); if (player == null) return; Guild guild = player.getGuild(); City city = guild.getOwnedCity(); if (city == null) return; Building warehouse = BuildingManager.getBuilding(city.getWarehouseBuildingID()); if (warehouse == null) return; ViewResourcesMessage vrm = new ViewResourcesMessage(player); vrm.setWarehouseBuilding(warehouse); vrm.setGuild(player.getGuild()); vrm.configure(); Dispatch dispatch = Dispatch.borrow(player, vrm); DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY); } protected static void petAttack(PetAttackMsg msg, ClientConnection conn) throws MsgSendException { PlayerCharacter pc = SessionManager.getPlayerCharacter(conn); if (pc == null) return; Mob pet = pc.getPet(); if (pet == null) return; if (!pet.isAlive()) return; if ((pc.inSafeZone()) && (msg.getTargetType() == GameObjectType.PlayerCharacter.ordinal())) return; //CombatManager.setAttackTarget(msg, conn); if (msg.getTargetType() == GameObjectType.Building.ordinal()) { conn.getPlayerCharacter().getPet().setCombatTarget(PlayerCharacter.getPlayerCharacter(msg.getTargetID())); } switch (msg.getTargetType()) { case 53: //player character conn.getPlayerCharacter().getPet().setCombatTarget(PlayerCharacter.getPlayerCharacter(msg.getTargetID())); break; case 37://mob conn.getPlayerCharacter().getPet().setCombatTarget(Mob.getMob(msg.getTargetID())); break; case 8://mob conn.getPlayerCharacter().getPet().setCombatTarget(BuildingManager.getBuilding(msg.getTargetID())); break; } if (pet.getCombatTarget() == null) return; } protected static void petCmd(PetCmdMsg msg, ClientConnection conn) throws MsgSendException { PlayerCharacter pc = SessionManager.getPlayerCharacter(conn); if (pc == null) return; Mob pet = pc.getPet(); if (pet == null) return; if (!pet.isAlive()) return; //if (pet.state == STATE.Disabled) // return; int type = msg.getType(); if (type == 1) { //stop attack pet.setCombatTarget(null); pc.setCombat(false); } else if (type == 2) { //dismiss pet.dismiss(); pc.dismissPet(); if (pet.isAlive()) WorldGrid.updateObject(pet); } else if (type == 3) //toggle assist pet.toggleAssist(); else if (type == 5) { //rest boolean sit = (!(pet.isSit())); pet.setSit(sit); // cancel effects that break on sit if (pet.isSit()) pet.cancelOnSit(); UpdateStateMsg rwss = new UpdateStateMsg(); rwss.setPlayer(pet); DispatchMessage.sendToAllInRange(pet, rwss); } } //Handle RepairObject Window and RepairObject Requests @Override public boolean handleClientMsg(ClientNetMsg msg) { if (msg == null) { Logger.error("handleClientMsg", "Recieved null msg. Returning."); return false; } ClientConnection origin; Protocol protocolMsg = Protocol.NONE; Session s; try { // Try registered opcodes first as we take a hatchet to this GodObject AbstractClientMsgHandler msgHandler = msg.getProtocolMsg().handler; if (msgHandler != null) return msgHandler.handleNetMsg(msg); // Any remaining opcodes fall through and are routed // through this ungodly switch of doom. origin = (ClientConnection) msg.getOrigin(); s = SessionManager.getSession(origin); protocolMsg = msg.getProtocolMsg(); switch (protocolMsg) { /* * Chat */ // Simplify by fall through. Route in ChatManager case CHATSAY: case CHATSHOUT: case CHATTELL: case CHATGUILD: case CHATGROUP: case CHATPVP: case CHATIC: case CHATCITY: case CHATINFO: case SYSTEMBROADCASTCHANNEL: case CHATCSR: case SYSTEMCHANNEL: case GLOBALCHANNELMESSAGE: case LEADERCHANNELMESSAGE: ChatManager.handleChatMsg(s, (AbstractChatMsg) msg); break; case READYTOENTER: break; case OPENVAULT: break; case CLIENTADMINCOMMAND: ChatManager.HandleClientAdminCmd((ClientAdminCommandMsg) msg, origin); break; case COMBATMODE: CombatManager.toggleCombat(((ToggleCombatMsg) msg).toggleCombat(), origin); break; case ARCCOMBATMODEATTACKING: CombatManager.toggleCombat(((SetCombatModeMsg) msg).getToggle(), origin); break; case MODIFYGUILDSTATE: ToggleLfgRecruitingMsg tlrm = (ToggleLfgRecruitingMsg) msg; toggleLfgRecruiting(tlrm, origin); break; case TOGGLESITSTAND: ToggleSitStandMsg tssm = (ToggleSitStandMsg) msg; toggleSitStand(tssm, origin); break; case IGNORE: ((IgnoreMsg) msg).handleRequest(origin); break; case VIEWRESOURCES: ViewResourcesMessage((ViewResourcesMessage) msg, origin); break; case RAISEATTR: modifyStat((ModifyStatMsg) msg, origin); break; case COSTTOOPENBANK: ackBankWindowOpened((AckBankWindowOpenedMsg) msg, origin); break; case RESETAFTERDEATH: respawn((RespawnMsg) msg, origin); break; case MOVEOBJECTTOCONTAINER: loot((LootMsg) msg, origin); break; case SHOWCOMBATINFO: show((ShowMsg) msg, origin); break; case REQUESTTOTRADE: TradeManager.tradeRequest((TradeRequestMsg) msg, origin); break; case REQUESTTRADEOK: TradeManager.acceptTradeRequest((AcceptTradeRequestMsg) msg, origin); break; case REQUESTTRADECANCEL: TradeManager.rejectTradeRequest((RejectTradeRequestMsg) msg, origin); break; case TRADEADDOBJECT: TradeManager.addItemToTradeWindow((AddItemToTradeWindowMsg) msg, origin); break; case TRADEADDGOLD: TradeManager.addGoldToTradeWindow((AddGoldToTradeWindowMsg) msg, origin); break; case TRADECONFIRM: TradeManager.commitToTrade((CommitToTradeMsg) msg, origin); break; case TRADEUNCONFIRM: TradeManager.uncommitToTrade((UncommitToTradeMsg) msg, origin); break; case TRADECLOSE: TradeManager.closeTradeWindow((CloseTradeWindowMsg) msg, origin); break; case ARCREQUESTTRADEBUSY: TradeManager.invalidTradeRequest((InvalidTradeRequestMsg) msg); break; case TRAINERLIST: WorldServer.trainerInfo((TrainerInfoMsg) msg, origin); break; case ARCUNTRAINLIST: WorldServer.refinerScreen((RefinerScreenMsg) msg, origin); break; case ARCUNTRAINABILITY: RefineMsg.refine((RefineMsg) msg, origin); break; case POWERTARGNAME: PowersManager.summon((SendSummonsRequestMsg) msg, origin); break; case ARCSUMMON: PowersManager.recvSummon((RecvSummonsRequestMsg) msg, origin); break; case ARCTRACKINGLIST: PowersManager.trackWindow((TrackWindowMsg) msg, origin); break; case STUCK: MovementManager.stuck(origin); break; case ARCPETATTACK: petAttack((PetAttackMsg) msg, origin); break; case ARCPETCMD: petCmd((PetCmdMsg) msg, origin); break; case CHANNELMUTE: break; case KEEPALIVESERVERCLIENT: break; case UNKNOWN: break; case CONFIRMPROMOTE: break; default: String ocHex = StringUtils.toHexString(protocolMsg.opcode); Logger.error("Cannot handle Opcode: " + ocHex + " " + protocolMsg.name()); return false; } } catch (MsgSendException | SQLException e) { Logger.error("handler for " + protocolMsg + " failed: " + e); return false; } return true; } }