From bbfdde57a3b19ccd5b7b9ef8f39167255ee76b7d Mon Sep 17 00:00:00 2001
From: MagicBot <MagicBot@magicbane.com>
Date: Sat, 30 Apr 2022 09:41:17 -0400
Subject: [PATCH] Initial Repository Push

---
 src/discord/ChatChannel.java                  |   41 +
 src/discord/Database.java                     |  380 ++
 src/discord/DiscordAccount.java               |   25 +
 src/discord/MagicBot.java                     |  372 ++
 src/discord/RobotSpeak.java                   |  106 +
 src/discord/handlers/AccountInfoRequest.java  |   78 +
 .../handlers/AnnounceChannelHandler.java      |   55 +
 src/discord/handlers/BanToggleHandler.java    |  102 +
 src/discord/handlers/ChangeLogHandler.java    |   44 +
 src/discord/handlers/FlashHandler.java        |   51 +
 .../handlers/ForToFixChannelHandler.java      |   56 +
 .../handlers/GeneralChannelHandler.java       |   56 +
 src/discord/handlers/LogsRequestHandler.java  |   62 +
 .../handlers/LookupRequestHandler.java        |   77 +
 .../handlers/PasswordChangeHandler.java       |  113 +
 .../handlers/PoliticalChannelHandler.java     |   55 +
 .../handlers/RecruitChannelHandler.java       |   56 +
 .../handlers/RegisterAccountHandler.java      |  126 +
 src/discord/handlers/RulesRequestHandler.java |   30 +
 .../handlers/ServerRequestHandler.java        |   63 +
 src/discord/handlers/SetAvailHandler.java     |   51 +
 .../handlers/StatusRequestHandler.java        |   42 +
 src/discord/handlers/TrashRequestHandler.java |   75 +
 src/engine/Enum.java                          | 2943 +++++++++
 src/engine/InterestManagement/HeightMap.java  | 1099 ++++
 .../InterestManagement/InterestManager.java   |  554 ++
 src/engine/InterestManagement/RealmMap.java   |  102 +
 src/engine/InterestManagement/WorldGrid.java  |  312 +
 src/engine/ai/MobileFSM.java                  | 1769 ++++++
 src/engine/ai/MobileFSMManager.java           |  104 +
 src/engine/ai/utilities/CombatUtilities.java  |  413 ++
 .../ai/utilities/MovementUtilities.java       |  303 +
 src/engine/ai/utilities/PowerUtilities.java   |   15 +
 src/engine/core/ControlledRunnable.java       |  174 +
 src/engine/db/archive/BaneRecord.java         |  383 ++
 src/engine/db/archive/CharacterRecord.java    |  284 +
 src/engine/db/archive/CityRecord.java         |  161 +
 src/engine/db/archive/DataRecord.java         |   23 +
 src/engine/db/archive/DataWarehouse.java      |  324 +
 src/engine/db/archive/GuildRecord.java        |  215 +
 src/engine/db/archive/MineRecord.java         |  166 +
 src/engine/db/archive/PvpRecord.java          |  312 +
 src/engine/db/archive/RealmRecord.java        |  142 +
 src/engine/db/handlers/dbAccountHandler.java  |  202 +
 src/engine/db/handlers/dbBaneHandler.java     |  115 +
 .../db/handlers/dbBaseClassHandler.java       |   50 +
 .../db/handlers/dbBlueprintHandler.java       |   86 +
 src/engine/db/handlers/dbBoonHandler.java     |   51 +
 src/engine/db/handlers/dbBuildingHandler.java |  809 +++
 .../handlers/dbBuildingLocationHandler.java   |   27 +
 .../db/handlers/dbCSSessionHandler.java       |  100 +
 .../db/handlers/dbCharacterPowerHandler.java  |  113 +
 .../db/handlers/dbCharacterRuneHandler.java   |   63 +
 .../db/handlers/dbCharacterSkillHandler.java  |  116 +
 src/engine/db/handlers/dbCityHandler.java     |  196 +
 src/engine/db/handlers/dbContractHandler.java |  157 +
 .../db/handlers/dbEffectsBaseHandler.java     |  181 +
 .../dbEffectsResourceCostHandler.java         |   30 +
 .../db/handlers/dbEnchantmentHandler.java     |   51 +
 src/engine/db/handlers/dbGuildHandler.java    |  486 ++
 src/engine/db/handlers/dbHandlerBase.java     |  470 ++
 .../db/handlers/dbHeightMapHandler.java       |   47 +
 src/engine/db/handlers/dbItemBaseHandler.java |  152 +
 src/engine/db/handlers/dbItemHandler.java     |  430 ++
 src/engine/db/handlers/dbKitHandler.java      |   36 +
 .../db/handlers/dbLootTableHandler.java       |  233 +
 src/engine/db/handlers/dbMenuHandler.java     |   28 +
 src/engine/db/handlers/dbMineHandler.java     |  117 +
 src/engine/db/handlers/dbMobBaseHandler.java  |  351 +
 src/engine/db/handlers/dbMobHandler.java      |  316 +
 src/engine/db/handlers/dbNPCHandler.java      |  397 ++
 .../db/handlers/dbPlayerCharacterHandler.java |  402 ++
 .../db/handlers/dbPromotionClassHandler.java  |   54 +
 src/engine/db/handlers/dbRaceHandler.java     |   85 +
 src/engine/db/handlers/dbRealmHandler.java    |   73 +
 src/engine/db/handlers/dbResistHandler.java   |   37 +
 .../handlers/dbRuneBaseAttributeHandler.java  |   35 +
 .../db/handlers/dbRuneBaseEffectHandler.java  |   73 +
 src/engine/db/handlers/dbRuneBaseHandler.java |  173 +
 src/engine/db/handlers/dbShrineHandler.java   |  147 +
 .../db/handlers/dbSkillBaseHandler.java       |  143 +
 src/engine/db/handlers/dbSkillReqHandler.java |   35 +
 .../db/handlers/dbSpecialLootHandler.java     |   75 +
 .../db/handlers/dbVendorDialogHandler.java    |   31 +
 .../db/handlers/dbWarehouseHandler.java       |  449 ++
 src/engine/db/handlers/dbZoneHandler.java     |   93 +
 src/engine/devcmd/AbstractDevCmd.java         |  181 +
 src/engine/devcmd/cmds/AddBuildingCmd.java    |  127 +
 src/engine/devcmd/cmds/AddGoldCmd.java        |   77 +
 src/engine/devcmd/cmds/AddMobCmd.java         |  111 +
 src/engine/devcmd/cmds/AddMobPowerCmd.java    |   98 +
 src/engine/devcmd/cmds/AddMobRuneCmd.java     |   98 +
 src/engine/devcmd/cmds/AddNPCCmd.java         |  111 +
 src/engine/devcmd/cmds/ApplyBonusCmd.java     |  158 +
 src/engine/devcmd/cmds/ApplyStatModCmd.java   |  149 +
 .../devcmd/cmds/AuditFailedItemsCmd.java      |  130 +
 src/engine/devcmd/cmds/AuditHeightMapCmd.java |   71 +
 src/engine/devcmd/cmds/AuditMobsCmd.java      |  106 +
 src/engine/devcmd/cmds/BoundsCmd.java         |   77 +
 src/engine/devcmd/cmds/ChangeNameCmd.java     |  114 +
 src/engine/devcmd/cmds/CombatMessageCmd.java  |   70 +
 src/engine/devcmd/cmds/CopyMobCmd.java        |   84 +
 src/engine/devcmd/cmds/CreateItemCmd.java     |   65 +
 src/engine/devcmd/cmds/DebugCmd.java          |  123 +
 src/engine/devcmd/cmds/DebugMeleeSyncCmd.java |   58 +
 src/engine/devcmd/cmds/DecachePlayerCmd.java  |   54 +
 src/engine/devcmd/cmds/DespawnCmd.java        |   73 +
 src/engine/devcmd/cmds/DistanceCmd.java       |   68 +
 src/engine/devcmd/cmds/EffectCmd.java         |   63 +
 src/engine/devcmd/cmds/EnchantCmd.java        |   88 +
 src/engine/devcmd/cmds/FindBuildingsCmd.java  |  111 +
 src/engine/devcmd/cmds/FlashMsgCmd.java       |   68 +
 src/engine/devcmd/cmds/GateInfoCmd.java       |   87 +
 src/engine/devcmd/cmds/GetBankCmd.java        |   52 +
 src/engine/devcmd/cmds/GetCacheCountCmd.java  |   40 +
 .../devcmd/cmds/GetDisciplineLocCmd.java      |   64 +
 src/engine/devcmd/cmds/GetHeightCmd.java      |  233 +
 src/engine/devcmd/cmds/GetMemoryCmd.java      |   57 +
 src/engine/devcmd/cmds/GetMobBaseLoot.java    |   59 +
 src/engine/devcmd/cmds/GetOffsetCmd.java      |   63 +
 .../devcmd/cmds/GetRuneDropRateCmd.java       |   42 +
 src/engine/devcmd/cmds/GetVaultCmd.java       |   47 +
 src/engine/devcmd/cmds/GetZoneCmd.java        |   66 +
 src/engine/devcmd/cmds/GetZoneMobsCmd.java    |   82 +
 src/engine/devcmd/cmds/GotoBoundsCmd.java     |  108 +
 src/engine/devcmd/cmds/GotoCmd.java           |  182 +
 src/engine/devcmd/cmds/GotoObj.java           |  103 +
 src/engine/devcmd/cmds/GuildListCmd.java      |  105 +
 src/engine/devcmd/cmds/HeartbeatCmd.java      |   43 +
 src/engine/devcmd/cmds/HelpCmd.java           |   59 +
 src/engine/devcmd/cmds/HotzoneCmd.java        |  114 +
 src/engine/devcmd/cmds/InfoCmd.java           |  514 ++
 src/engine/devcmd/cmds/JumpCmd.java           |   85 +
 src/engine/devcmd/cmds/MBDropCmd.java         |  134 +
 src/engine/devcmd/cmds/MakeBaneCmd.java       |  215 +
 src/engine/devcmd/cmds/MakeItemCmd.java       |  256 +
 src/engine/devcmd/cmds/NetDebugCmd.java       |   86 +
 src/engine/devcmd/cmds/PrintBankCmd.java      |   73 +
 src/engine/devcmd/cmds/PrintBonusesCmd.java   |   91 +
 src/engine/devcmd/cmds/PrintEquipCmd.java     |  102 +
 src/engine/devcmd/cmds/PrintInventoryCmd.java |  109 +
 src/engine/devcmd/cmds/PrintLocationCmd.java  |   67 +
 src/engine/devcmd/cmds/PrintPowersCmd.java    |   68 +
 src/engine/devcmd/cmds/PrintResistsCmd.java   |   71 +
 src/engine/devcmd/cmds/PrintSkillsCmd.java    |   66 +
 src/engine/devcmd/cmds/PrintStatsCmd.java     |  154 +
 src/engine/devcmd/cmds/PrintVaultCmd.java     |   72 +
 src/engine/devcmd/cmds/PullCmd.java           |  104 +
 src/engine/devcmd/cmds/PurgeObjectsCmd.java   |  315 +
 src/engine/devcmd/cmds/RealmInfoCmd.java      |   92 +
 src/engine/devcmd/cmds/RebootCmd.java         |   48 +
 src/engine/devcmd/cmds/RegionCmd.java         |   79 +
 src/engine/devcmd/cmds/RemoveBaneCmd.java     |   70 +
 src/engine/devcmd/cmds/RemoveObjectCmd.java   |  253 +
 src/engine/devcmd/cmds/RenameCmd.java         |   81 +
 src/engine/devcmd/cmds/RenameMobCmd.java      |  101 +
 src/engine/devcmd/cmds/ResetLevelCmd.java     |   36 +
 src/engine/devcmd/cmds/RotateCmd.java         |  251 +
 src/engine/devcmd/cmds/SetAICmd.java          |  128 +
 .../devcmd/cmds/SetActivateMineCmd.java       |   67 +
 src/engine/devcmd/cmds/SetAdminRuneCmd.java   |   86 +
 src/engine/devcmd/cmds/SetBaneActiveCmd.java  |   80 +
 src/engine/devcmd/cmds/SetBaseClassCmd.java   |   64 +
 .../devcmd/cmds/SetBuildingAltitudeCmd.java   |  107 +
 .../devcmd/cmds/SetForceRenameCityCmd.java    |   54 +
 src/engine/devcmd/cmds/SetGuildCmd.java       |  105 +
 src/engine/devcmd/cmds/SetHealthCmd.java      |   67 +
 src/engine/devcmd/cmds/SetInvulCmd.java       |   91 +
 src/engine/devcmd/cmds/SetLevelCmd.java       |   73 +
 src/engine/devcmd/cmds/SetMaintCmd.java       |   61 +
 src/engine/devcmd/cmds/SetManaCmd.java        |   56 +
 src/engine/devcmd/cmds/SetMineExpansion.java  |   87 +
 src/engine/devcmd/cmds/SetMineTypeCmd.java    |   65 +
 src/engine/devcmd/cmds/SetNPCSlotCmd.java     |   80 +
 src/engine/devcmd/cmds/SetNpcEquipSetCmd.java |  138 +
 src/engine/devcmd/cmds/SetNpcMobbaseCmd.java  |   67 +
 src/engine/devcmd/cmds/SetNpcNameCmd.java     |   62 +
 src/engine/devcmd/cmds/SetOwnerCmd.java       |  118 +
 .../devcmd/cmds/SetPromotionClassCmd.java     |   72 +
 src/engine/devcmd/cmds/SetRankCmd.java        |  142 +
 src/engine/devcmd/cmds/SetRateCmd.java        |   98 +
 src/engine/devcmd/cmds/SetRuneCmd.java        |   80 +
 src/engine/devcmd/cmds/SetStaminaCmd.java     |   68 +
 src/engine/devcmd/cmds/SetSubRaceCmd.java     |  209 +
 src/engine/devcmd/cmds/ShowOffsetCmd.java     |   57 +
 src/engine/devcmd/cmds/SlotNpcCmd.java        |  155 +
 src/engine/devcmd/cmds/SplatMobCmd.java       |  166 +
 src/engine/devcmd/cmds/SqlDebugCmd.java       |   86 +
 src/engine/devcmd/cmds/SummonCmd.java         |  114 +
 src/engine/devcmd/cmds/SysMsgCmd.java         |   67 +
 src/engine/devcmd/cmds/TeleportModeCmd.java   |   56 +
 .../devcmd/cmds/UnloadFurnitureCmd.java       |  102 +
 src/engine/devcmd/cmds/ZoneInfoCmd.java       |  150 +
 src/engine/devcmd/cmds/convertLoc.java        |   62 +
 src/engine/devcmd/cmds/setOpenDateCmd.java    |   94 +
 .../exception/FactoryBuildException.java      |   32 +
 src/engine/exception/MBServerException.java   |   38 +
 src/engine/exception/MsgSendException.java    |   31 +
 .../exception/SerializationException.java     |   31 +
 src/engine/gameManager/BuildingManager.java   |  636 ++
 src/engine/gameManager/ChatManager.java       | 1231 ++++
 src/engine/gameManager/CombatManager.java     | 1455 +++++
 src/engine/gameManager/ConfigManager.java     |  113 +
 src/engine/gameManager/DbManager.java         |  314 +
 src/engine/gameManager/DevCmdManager.java     |  228 +
 src/engine/gameManager/GroupManager.java      |  393 ++
 src/engine/gameManager/GuildManager.java      |  206 +
 .../gameManager/MaintenanceManager.java       |  359 ++
 src/engine/gameManager/MovementManager.java   |  650 ++
 src/engine/gameManager/PowersManager.java     | 2790 ++++++++
 src/engine/gameManager/SessionManager.java    |  289 +
 src/engine/gameManager/SimulationManager.java |  216 +
 src/engine/gameManager/TradeManager.java      |  163 +
 src/engine/gameManager/ZoneManager.java       |  437 ++
 src/engine/job/AbstractJob.java               |  151 +
 src/engine/job/AbstractJobStatistics.java     |  118 +
 src/engine/job/AbstractScheduleJob.java       |   28 +
 src/engine/job/ClassJobStatistics.java        |   19 +
 src/engine/job/JobContainer.java              |   84 +
 src/engine/job/JobManager.java                |  232 +
 src/engine/job/JobPool.java                   |  295 +
 src/engine/job/JobScheduler.java              |  169 +
 src/engine/job/JobWorker.java                 |  109 +
 src/engine/jobs/AbstractEffectJob.java        |  152 +
 src/engine/jobs/ActivateBaneJob.java          |   76 +
 src/engine/jobs/AttackJob.java                |   39 +
 src/engine/jobs/BaneDefaultTimeJob.java       |   47 +
 src/engine/jobs/BasicScheduledJob.java        |  126 +
 src/engine/jobs/BonusCalcJob.java             |   31 +
 src/engine/jobs/CSessionCleanupJob.java       |   28 +
 src/engine/jobs/ChangeAltitudeJob.java        |   42 +
 src/engine/jobs/ChantJob.java                 |  103 +
 src/engine/jobs/CloseGateJob.java             |   44 +
 src/engine/jobs/DamageOverTimeJob.java        |   92 +
 src/engine/jobs/DatabaseUpdateJob.java        |   65 +
 src/engine/jobs/DebugTimerJob.java            |   61 +
 src/engine/jobs/DeferredPowerJob.java         |  109 +
 src/engine/jobs/DisconnectJob.java            |   34 +
 src/engine/jobs/DoorCloseJob.java             |   63 +
 src/engine/jobs/EndFearJob.java               |   45 +
 src/engine/jobs/FinishCooldownTimeJob.java    |   32 +
 src/engine/jobs/FinishEffectTimeJob.java      |   33 +
 src/engine/jobs/FinishRecycleTimeJob.java     |   36 +
 src/engine/jobs/FinishSpireEffectJob.java     |   37 +
 src/engine/jobs/FinishSummonsJob.java         |   81 +
 src/engine/jobs/FlightJob.java                |   40 +
 src/engine/jobs/LoadEffectsJob.java           |   48 +
 src/engine/jobs/LogoutCharacterJob.java       |   37 +
 src/engine/jobs/MineActiveJob.java            |   71 +
 src/engine/jobs/NoTimeJob.java                |   28 +
 src/engine/jobs/PersistentAoeJob.java         |  110 +
 src/engine/jobs/RefreshGroupJob.java          |   82 +
 src/engine/jobs/RemoveCorpseJob.java          |   35 +
 src/engine/jobs/SiegeSpireWithdrawlJob.java   |   78 +
 src/engine/jobs/StuckJob.java                 |  119 +
 src/engine/jobs/SummonSendJob.java            |   54 +
 src/engine/jobs/TeleportJob.java              |   65 +
 src/engine/jobs/TrackJob.java                 |   92 +
 src/engine/jobs/TransferStatOTJob.java        |   74 +
 src/engine/jobs/UpdateGroupJob.java           |   51 +
 src/engine/jobs/UpgradeBuildingJob.java       |   55 +
 src/engine/jobs/UpgradeNPCJob.java            |   64 +
 src/engine/jobs/UseItemJob.java               |   57 +
 src/engine/jobs/UseMobPowerJob.java           |   57 +
 src/engine/jobs/UsePowerJob.java              |   59 +
 src/engine/loot/LootGroup.java                |  114 +
 src/engine/loot/LootManager.java              |  231 +
 src/engine/loot/LootTable.java                |   98 +
 src/engine/loot/ModifierGroup.java            |   78 +
 src/engine/loot/ModifierTable.java            |   88 +
 src/engine/math/AtomicFloat.java              |  101 +
 src/engine/math/Bounds.java                   |  583 ++
 src/engine/math/FastMath.java                 |  668 ++
 src/engine/math/Matrix3f.java                 | 1220 ++++
 src/engine/math/Matrix4f.java                 | 1791 ++++++
 src/engine/math/Quaternion.java               | 1264 ++++
 src/engine/math/Vector2f.java                 |  662 ++
 src/engine/math/Vector3f.java                 | 1188 ++++
 src/engine/math/Vector3fImmutable.java        |  595 ++
 src/engine/net/AbstractConnection.java        |  425 ++
 src/engine/net/AbstractConnectionManager.java |  708 +++
 src/engine/net/AbstractNetMsg.java            |  245 +
 src/engine/net/ByteBufferReader.java          |  370 ++
 src/engine/net/ByteBufferWriter.java          |  206 +
 src/engine/net/CheckNetMsgFactoryJob.java     |   53 +
 src/engine/net/ConnectionMonitorJob.java      |   44 +
 src/engine/net/Dispatch.java                  |   74 +
 src/engine/net/DispatchMessage.java           |  312 +
 src/engine/net/ItemProductionManager.java     |  155 +
 src/engine/net/ItemQueue.java                 |   99 +
 src/engine/net/MessageDispatcher.java         |  145 +
 src/engine/net/NetMsgFactory.java             |  425 ++
 src/engine/net/NetMsgHandler.java             |   17 +
 src/engine/net/NetMsgStat.java                |   77 +
 src/engine/net/Network.java                   |   60 +
 .../net/client/ClientAuthenticator.java       |  381 ++
 src/engine/net/client/ClientConnection.java   |  354 ++
 .../net/client/ClientConnectionManager.java   |   35 +
 src/engine/net/client/ClientMessagePump.java  | 2340 +++++++
 src/engine/net/client/Protocol.java           |  338 +
 .../handlers/AbandonAssetMsgHandler.java      |  206 +
 .../handlers/AbstractClientMsgHandler.java    |   33 +
 .../handlers/AcceptInviteToGuildHandler.java  |  122 +
 .../handlers/AcceptSubInviteHandler.java      |  106 +
 .../handlers/ActivateNPCMsgHandler.java       |  136 +
 .../handlers/AllianceChangeMsgHandler.java    |  161 +
 .../handlers/AllyEnemyListMsgHandler.java     |   81 +
 .../handlers/AppointGroupLeaderHandler.java   |   85 +
 .../handlers/ArcLoginNotifyMsgHandler.java    |  141 +
 .../handlers/ArcSiegeSpireMsgHandler.java     |  120 +
 .../ArcViewAssetTransactionsMsgHandler.java   |   54 +
 .../handlers/AssetSupportMsgHandler.java      |  240 +
 .../handlers/BanishUnbanishHandler.java       |  129 +
 .../client/handlers/BreakFealtyHandler.java   |  193 +
 .../handlers/ChangeAltitudeHandler.java       |  190 +
 .../handlers/ChangeGuildLeaderHandler.java    |  147 +
 .../client/handlers/ChangeRankHandler.java    |  176 +
 .../handlers/ChannelMuteMsgHandler.java       |  123 +
 .../client/handlers/CityChoiceMsgHandler.java |   69 +
 .../client/handlers/ClaimAssetMsgHandler.java |  139 +
 .../handlers/ClaimGuildTreeMsgHandler.java    |  191 +
 .../handlers/DestroyBuildingHandler.java      |   95 +
 .../client/handlers/DisbandGroupHandler.java  |   56 +
 .../client/handlers/DisbandGuildHandler.java  |  121 +
 .../client/handlers/DismissGuildHandler.java  |  147 +
 .../handlers/DoorTryOpenMsgHandler.java       |   88 +
 .../handlers/FormationFollowHandler.java      |   75 +
 .../client/handlers/FriendAcceptHandler.java  |   78 +
 .../client/handlers/FriendDeclineHandler.java |   63 +
 .../client/handlers/FriendRequestHandler.java |   69 +
 .../net/client/handlers/FurnitureHandler.java |   48 +
 .../client/handlers/GroupInviteHandler.java   |  126 +
 .../handlers/GroupInviteResponseHandler.java  |  112 +
 .../client/handlers/GroupUpdateHandler.java   |   33 +
 .../client/handlers/GuildControlHandler.java  |   74 +
 .../handlers/GuildCreationCloseHandler.java   |   30 +
 .../GuildCreationFinalizeHandler.java         |  165 +
 .../handlers/GuildCreationOptionsHandler.java |   47 +
 .../net/client/handlers/GuildInfoHandler.java |   67 +
 .../net/client/handlers/GuildListHandler.java |   36 +
 .../client/handlers/GuildUnknownHandler.java  |   29 +
 .../handlers/HirelingServiceMsgHandler.java   |   94 +
 .../client/handlers/InviteToGuildHandler.java |  151 +
 .../client/handlers/InviteToSubHandler.java   |  133 +
 .../handlers/ItemProductionMsgHandler.java    |  521 ++
 .../KeepAliveServerClientHandler.java         |   48 +
 .../net/client/handlers/KeyCloneAudit.java    |   32 +
 .../client/handlers/LeaveGroupHandler.java    |   31 +
 .../client/handlers/LeaveGuildHandler.java    |   72 +
 .../handlers/LockUnlockDoorMsgHandler.java    |   88 +
 .../handlers/LoginToGameServerMsgHandler.java |  119 +
 .../client/handlers/MOTDCommitHandler.java    |   82 +
 .../net/client/handlers/MOTDEditHandler.java  |   83 +
 .../handlers/ManageCityAssetMsgHandler.java   |  364 ++
 .../client/handlers/MerchantMsgHandler.java   |  465 ++
 .../handlers/MinionTrainingMsgHandler.java    |  316 +
 .../client/handlers/MoveToPointHandler.java   |   38 +
 .../handlers/ObjectActionMsgHandler.java      |  569 ++
 .../OpenFriendsCondemnListMsgHandler.java     |  397 ++
 .../client/handlers/OrderNPCMsgHandler.java   |  575 ++
 .../client/handlers/PlaceAssetMsgHandler.java | 1380 ++++
 .../handlers/RecommendNationMsgHandler.java   |   90 +
 .../client/handlers/RemoveFriendHandler.java  |   64 +
 .../handlers/RemoveFromGroupHandler.java      |  118 +
 .../handlers/RepairBuildingMsgHandler.java    |  139 +
 .../handlers/RequestBallListHandler.java      |   47 +
 .../handlers/RequestEnterWorldHandler.java    |  162 +
 .../handlers/RequestGuildListHandler.java     |   55 +
 .../client/handlers/SendBallEntryHandler.java |   47 +
 .../client/handlers/SwearInGuildHandler.java  |  138 +
 .../net/client/handlers/SwearInHandler.java   |   79 +
 .../client/handlers/TaxCityMsgHandler.java    |  154 +
 .../handlers/TaxResourcesMsgHandler.java      |   73 +
 .../handlers/ToggleGroupSplitHandler.java     |   71 +
 .../handlers/TransferAssetMsgHandler.java     |   99 +
 .../TransferGoldToFromBuildingMsgHandler.java |  115 +
 .../handlers/UpdateFriendStatusHandler.java   |   84 +
 .../handlers/UpgradeAssetMsgHandler.java      |  154 +
 .../net/client/msg/AbandonAssetMsg.java       |   75 +
 .../net/client/msg/AcceptFriendMsg.java       |   65 +
 .../net/client/msg/AcceptTradeRequestMsg.java |  133 +
 .../client/msg/AckBankWindowOpenedMsg.java    |   90 +
 .../net/client/msg/ActivateNPCMessage.java    |  159 +
 .../net/client/msg/AddFriendMessage.java      |   67 +
 .../client/msg/AddGoldToTradeWindowMsg.java   |   97 +
 .../client/msg/AddItemToTradeWindowMsg.java   |  120 +
 .../net/client/msg/AllianceChangeMsg.java     |  144 +
 .../net/client/msg/AllyEnemyListMsg.java      |  122 +
 .../client/msg/ApplyBuildingEffectMsg.java    |  110 +
 src/engine/net/client/msg/ApplyEffectMsg.java |  264 +
 src/engine/net/client/msg/ApplyRuneMsg.java   |  393 ++
 .../net/client/msg/ArcLoginNotifyMsg.java     |  148 +
 .../msg/ArcMineChangeProductionMsg.java       |   60 +
 .../msg/ArcMineWindowAvailableTimeMsg.java    |  110 +
 .../client/msg/ArcMineWindowChangeMsg.java    |   70 +
 .../net/client/msg/ArcOwnedMinesListMsg.java  |   59 +
 .../net/client/msg/ArcSiegeSpireMsg.java      |   60 +
 .../msg/ArcViewAssetTransactionsMsg.java      |  150 +
 .../net/client/msg/AssetSupportMsg.java       |  334 +
 src/engine/net/client/msg/AttackCmdMsg.java   |  113 +
 src/engine/net/client/msg/BuyFromNPCMsg.java  |  139 +
 .../net/client/msg/BuyFromNPCWindowMsg.java   |  269 +
 .../net/client/msg/ChangeAltitudeMsg.java     |  146 +
 .../net/client/msg/ChangeGuildLeaderMsg.java  |   72 +
 src/engine/net/client/msg/ChatFilterMsg.java  |   73 +
 src/engine/net/client/msg/CityAssetMsg.java   |  219 +
 src/engine/net/client/msg/CityChoiceMsg.java  |  107 +
 src/engine/net/client/msg/CityZoneMsg.java    |  156 +
 src/engine/net/client/msg/ClaimAssetMsg.java  |   71 +
 .../net/client/msg/ClaimGuildTreeMsg.java     |  376 ++
 src/engine/net/client/msg/ClientNetMsg.java   |   73 +
 .../net/client/msg/CloseTradeWindowMsg.java   |   88 +
 .../net/client/msg/CommitToTradeMsg.java      |   95 +
 .../net/client/msg/ConfirmPromoteMsg.java     |   55 +
 .../net/client/msg/DeclineFriendMsg.java      |   65 +
 src/engine/net/client/msg/DeleteItemMsg.java  |   79 +
 .../net/client/msg/DestroyBuildingMsg.java    |   72 +
 src/engine/net/client/msg/DoorTryOpenMsg.java |  158 +
 src/engine/net/client/msg/DropGoldMsg.java    |  146 +
 .../net/client/msg/EnterWorldReceivedMsg.java |   58 +
 src/engine/net/client/msg/ErrorPopupMsg.java  |  316 +
 .../net/client/msg/FriendRequestMsg.java      |   65 +
 src/engine/net/client/msg/FurnitureMsg.java   |  294 +
 .../net/client/msg/GrantExperienceMsg.java    |  100 +
 .../net/client/msg/GuildTreeStatusMsg.java    |  195 +
 .../net/client/msg/HirelingServiceMsg.java    |  122 +
 .../net/client/msg/HotzoneChangeMsg.java      |   80 +
 src/engine/net/client/msg/IgnoreListMsg.java  |  110 +
 src/engine/net/client/msg/IgnoreMsg.java      |  181 +
 .../client/msg/InvalidTradeRequestMsg.java    |   98 +
 src/engine/net/client/msg/ItemEffectMsg.java  |  263 +
 .../net/client/msg/ItemHealthUpdateMsg.java   |   76 +
 .../net/client/msg/ItemProductionMsg.java     |  567 ++
 .../client/msg/KeepAliveServerClientMsg.java  |   86 +
 .../net/client/msg/LeaderboardMessage.java    |  103 +
 src/engine/net/client/msg/LeaveWorldMsg.java  |   52 +
 .../net/client/msg/LoadCharacterMsg.java      |  169 +
 .../net/client/msg/LoadStructureMsg.java      |  104 +
 .../net/client/msg/LockUnlockDoorMsg.java     |  113 +
 .../net/client/msg/LoginToGameServerMsg.java  |  106 +
 src/engine/net/client/msg/LootMsg.java        |  267 +
 .../net/client/msg/LootWindowRequestMsg.java  |  112 +
 .../net/client/msg/LootWindowResponseMsg.java |  105 +
 .../net/client/msg/ManageCityAssetsMsg.java   |  866 +++
 src/engine/net/client/msg/ManageNPCMsg.java   | 1462 +++++
 src/engine/net/client/msg/MerchantMsg.java    |  228 +
 .../net/client/msg/MinionTrainingMessage.java |  195 +
 .../client/msg/ModifyCommitToTradeMsg.java    |  145 +
 .../net/client/msg/ModifyHealthKillMsg.java   |  236 +
 .../net/client/msg/ModifyHealthMsg.java       |  272 +
 src/engine/net/client/msg/ModifyStatMsg.java  |   91 +
 .../net/client/msg/MoveCorrectionMsg.java     |  226 +
 src/engine/net/client/msg/MoveToPointMsg.java |  305 +
 .../net/client/msg/ObjectActionMsg.java       |  131 +
 .../client/msg/OpenFriendsCondemnListMsg.java |  931 +++
 .../net/client/msg/OpenTradeWindowMsg.java    |   94 +
 src/engine/net/client/msg/OpenVaultMsg.java   |   76 +
 src/engine/net/client/msg/OrderNPCMsg.java    |  209 +
 .../client/msg/PassiveMessageTriggerMsg.java  |   65 +
 .../net/client/msg/PerformActionMsg.java      |  298 +
 src/engine/net/client/msg/PetAttackMsg.java   |   73 +
 src/engine/net/client/msg/PetCmdMsg.java      |   68 +
 src/engine/net/client/msg/PetMsg.java         |  117 +
 .../net/client/msg/PetitionReceivedMsg.java   |  274 +
 src/engine/net/client/msg/PlaceAssetMsg.java  |  593 ++
 .../net/client/msg/PowerProjectileMsg.java    |   92 +
 .../net/client/msg/PromptRecallMsg.java       |   59 +
 src/engine/net/client/msg/RandomMsg.java      |   94 +
 .../net/client/msg/RecommendNationMsg.java    |   92 +
 .../net/client/msg/RecvSummonsRequestMsg.java |  111 +
 .../net/client/msg/RecyclePowerMsg.java       |   62 +
 src/engine/net/client/msg/RefineMsg.java      |  226 +
 .../net/client/msg/RefinerScreenMsg.java      |  117 +
 .../net/client/msg/RejectTradeRequestMsg.java |  113 +
 .../net/client/msg/RemoveFriendMessage.java   |   67 +
 .../net/client/msg/RepairBuildingMsg.java     |  105 +
 src/engine/net/client/msg/RepairMsg.java      |  218 +
 .../net/client/msg/ReqBankInventoryMsg.java   |   87 +
 .../client/msg/RequestBallListMessage.java    |   73 +
 .../net/client/msg/RequestEnterWorldMsg.java  |   71 +
 src/engine/net/client/msg/RespawnMsg.java     |  100 +
 .../net/client/msg/RespondLeaveWorldMsg.java  |   52 +
 .../net/client/msg/RotateObjectMsg.java       |   61 +
 src/engine/net/client/msg/SafeModeMsg.java    |   72 +
 src/engine/net/client/msg/ScaleObjectMsg.java |   99 +
 src/engine/net/client/msg/SelectCityMsg.java  |   93 +
 src/engine/net/client/msg/SellToNPCMsg.java   |  115 +
 .../net/client/msg/SellToNPCWindowMsg.java    |  289 +
 .../net/client/msg/SendBallEntryMessage.java  |  107 +
 .../net/client/msg/SendOwnPlayerMsg.java      |  123 +
 .../net/client/msg/SendSummonsRequestMsg.java |  106 +
 src/engine/net/client/msg/ServerInfoMsg.java  |   84 +
 .../net/client/msg/SetCombatModeMsg.java      |   97 +
 .../net/client/msg/SetObjectValueMsg.java     |   98 +
 .../net/client/msg/ShowBankInventoryMsg.java  |  115 +
 src/engine/net/client/msg/ShowMsg.java        |  140 +
 .../net/client/msg/ShowVaultInventoryMsg.java |   86 +
 src/engine/net/client/msg/SocialMsg.java      |  178 +
 .../net/client/msg/StuckCommandMsg.java       |   52 +
 src/engine/net/client/msg/SyncMessage.java    |  146 +
 .../net/client/msg/TargetObjectMsg.java       |   81 +
 .../net/client/msg/TargetedActionMsg.java     |  346 +
 src/engine/net/client/msg/TaxCityMsg.java     |  115 +
 .../net/client/msg/TaxResourcesMsg.java       |  123 +
 .../client/msg/TeleportRepledgeListMsg.java   |  112 +
 .../net/client/msg/TeleportToPointMsg.java    |  222 +
 .../client/msg/TerritoryChangeMessage.java    |   95 +
 .../net/client/msg/ToggleCombatMsg.java       |   85 +
 .../client/msg/ToggleLfgRecruitingMsg.java    |   96 +
 .../net/client/msg/ToggleSitStandMsg.java     |   84 +
 src/engine/net/client/msg/TrackArrowMsg.java  |   61 +
 src/engine/net/client/msg/TrackWindowMsg.java |  148 +
 .../net/client/msg/TradeRequestMsg.java       |  130 +
 src/engine/net/client/msg/TrainMsg.java       |  361 ++
 src/engine/net/client/msg/TrainerInfoMsg.java |  101 +
 .../net/client/msg/TransferAssetMsg.java      |   99 +
 .../net/client/msg/TransferBuildingMsg.java   |   97 +
 .../TransferGoldFromInventoryToVaultMsg.java  |  105 +
 .../TransferGoldFromVaultToInventoryMsg.java  |  132 +
 .../msg/TransferGoldToFromBuildingMsg.java    |  149 +
 .../TransferItemFromBankToInventoryMsg.java   |  220 +
 .../TransferItemFromEquipToInventoryMsg.java  |   91 +
 .../TransferItemFromInventoryToBankMsg.java   |  220 +
 .../TransferItemFromInventoryToEquipMsg.java  |  221 +
 .../TransferItemFromInventoryToVaultMsg.java  |  207 +
 .../TransferItemFromVaultToInventoryMsg.java  |  200 +
 .../net/client/msg/UncommitToTradeMsg.java    |   95 +
 src/engine/net/client/msg/Unknown1Msg.java    |   79 +
 src/engine/net/client/msg/UnknownMsg.java     |  155 +
 .../net/client/msg/UnloadObjectsMsg.java      |   96 +
 .../client/msg/UpdateCharOrMobMessage.java    |  178 +
 .../client/msg/UpdateClientAlliancesMsg.java  |   95 +
 .../net/client/msg/UpdateEffectsMsg.java      |   86 +
 .../client/msg/UpdateFriendStatusMessage.java |   78 +
 src/engine/net/client/msg/UpdateGoldMsg.java  |  119 +
 .../net/client/msg/UpdateInventoryMsg.java    |  108 +
 .../net/client/msg/UpdateObjectMsg.java       |  136 +
 src/engine/net/client/msg/UpdateStateMsg.java |  222 +
 .../net/client/msg/UpdateTradeWindowMsg.java  |   95 +
 src/engine/net/client/msg/UpdateVaultMsg.java |   69 +
 .../net/client/msg/UpgradeAssetMessage.java   |   92 +
 src/engine/net/client/msg/UseCharterMsg.java  |  105 +
 .../net/client/msg/VendorDialogMsg.java       |  905 +++
 .../net/client/msg/ViewResourcesMessage.java  |  198 +
 .../net/client/msg/VisualUpdateMessage.java   |   91 +
 src/engine/net/client/msg/WhoRequestMsg.java  |  106 +
 src/engine/net/client/msg/WhoResponseMsg.java |  313 +
 src/engine/net/client/msg/WorldDataMsg.java   |  134 +
 src/engine/net/client/msg/WorldObjectMsg.java |  282 +
 src/engine/net/client/msg/WorldRealmMsg.java  |  100 +
 .../net/client/msg/chat/AbstractChatMsg.java  |  190 +
 .../net/client/msg/chat/ChatCSRMsg.java       |   78 +
 .../net/client/msg/chat/ChatCityMsg.java      |   88 +
 .../net/client/msg/chat/ChatGlobalMsg.java    |   89 +
 .../net/client/msg/chat/ChatGroupMsg.java     |   75 +
 .../net/client/msg/chat/ChatGuildMsg.java     |  153 +
 src/engine/net/client/msg/chat/ChatICMsg.java |  132 +
 .../net/client/msg/chat/ChatInfoMsg.java      |   60 +
 .../net/client/msg/chat/ChatLeaderMsg.java    |  151 +
 .../net/client/msg/chat/ChatPvPMsg.java       |   87 +
 .../net/client/msg/chat/ChatSayMsg.java       |   91 +
 .../net/client/msg/chat/ChatShoutMsg.java     |   80 +
 .../client/msg/chat/ChatSystemChannelMsg.java |  162 +
 .../net/client/msg/chat/ChatSystemMsg.java    |  203 +
 .../net/client/msg/chat/ChatTellMsg.java      |  171 +
 .../client/msg/chat/GuildEnterWorldMsg.java   |  246 +
 .../msg/commands/ClientAdminCommandMsg.java   |  101 +
 .../msg/group/AppointGroupLeaderMsg.java      |  129 +
 .../net/client/msg/group/DisbandGroupMsg.java |   69 +
 .../client/msg/group/FormationFollowMsg.java  |  112 +
 .../net/client/msg/group/GroupInviteMsg.java  |  220 +
 .../msg/group/GroupInviteResponseMsg.java     |  123 +
 .../net/client/msg/group/GroupUpdateMsg.java  |  352 +
 .../net/client/msg/group/LeaveGroupMsg.java   |  122 +
 .../client/msg/group/RemoveFromGroupMsg.java  |  129 +
 .../client/msg/group/ToggleGroupSplitMsg.java |   72 +
 .../msg/guild/AcceptInviteToGuildMsg.java     |  110 +
 .../client/msg/guild/AcceptSubInviteMsg.java  |  154 +
 .../client/msg/guild/BanishUnbanishMsg.java   |   72 +
 .../net/client/msg/guild/BreakFealtyMsg.java  |   61 +
 .../net/client/msg/guild/ChangeRankMsg.java   |  163 +
 .../net/client/msg/guild/DisbandGuildMsg.java |   58 +
 .../net/client/msg/guild/DismissGuildMsg.java |   78 +
 .../net/client/msg/guild/GuildControlMsg.java |  241 +
 .../msg/guild/GuildCreationCloseMsg.java      |   67 +
 .../msg/guild/GuildCreationFinalizeMsg.java   |  160 +
 .../msg/guild/GuildCreationOptionsMsg.java    |  151 +
 .../net/client/msg/guild/GuildInfoMsg.java    |  453 ++
 .../net/client/msg/guild/GuildListMsg.java    |  228 +
 .../net/client/msg/guild/GuildUnknownMsg.java |   67 +
 .../client/msg/guild/InviteToGuildMsg.java    |  196 +
 .../net/client/msg/guild/InviteToSubMsg.java  |  201 +
 .../net/client/msg/guild/LeaveGuildMsg.java   |  105 +
 .../net/client/msg/guild/MOTDCommitMsg.java   |  110 +
 src/engine/net/client/msg/guild/MOTDMsg.java  |  133 +
 .../net/client/msg/guild/ReqGuildListMsg.java |   56 +
 .../client/msg/guild/SendGuildEntryMsg.java   |  106 +
 .../net/client/msg/guild/SwearInGuildMsg.java |   78 +
 .../net/client/msg/guild/SwearInMsg.java      |  123 +
 .../client/msg/login/CharSelectScreenMsg.java |  294 +
 .../client/msg/login/ClientLoginInfoMsg.java  |  267 +
 .../msg/login/CommitNewCharacterMsg.java      |  457 ++
 .../client/msg/login/DeleteCharacterMsg.java  |   75 +
 .../msg/login/GameServerIPRequestMsg.java     |   63 +
 .../msg/login/GameServerIPResponseMsg.java    |   85 +
 .../net/client/msg/login/InvalidNameMsg.java  |   89 +
 .../net/client/msg/login/LoginErrorMsg.java   |   72 +
 .../net/client/msg/login/ServerStatusMsg.java |   80 +
 .../net/client/msg/login/VersionInfoMsg.java  |   72 +
 src/engine/objects/AbstractCharacter.java     | 1974 ++++++
 src/engine/objects/AbstractGameObject.java    |  235 +
 .../objects/AbstractIntelligenceAgent.java    |  244 +
 src/engine/objects/AbstractWorldObject.java   |  638 ++
 src/engine/objects/Account.java               |  390 ++
 src/engine/objects/Bane.java                  |  650 ++
 src/engine/objects/BaseClass.java             |  220 +
 src/engine/objects/Blueprint.java             |  630 ++
 src/engine/objects/Boon.java                  |   64 +
 src/engine/objects/Building.java              | 1761 +++++
 src/engine/objects/BuildingFriends.java       |   51 +
 src/engine/objects/BuildingLocation.java      |  143 +
 src/engine/objects/BuildingModelBase.java     |   87 +
 src/engine/objects/BuildingRegions.java       |  388 ++
 src/engine/objects/CharacterItemManager.java  | 2630 ++++++++
 src/engine/objects/CharacterPower.java        |  628 ++
 src/engine/objects/CharacterRune.java         |  211 +
 src/engine/objects/CharacterSkill.java        | 1248 ++++
 src/engine/objects/CharacterTitle.java        |  121 +
 src/engine/objects/City.java                  | 1486 +++++
 src/engine/objects/Colliders.java             |   65 +
 src/engine/objects/Condemned.java             |   98 +
 src/engine/objects/Contract.java              |  301 +
 src/engine/objects/Corpse.java                |  419 ++
 src/engine/objects/Effect.java                |  665 ++
 src/engine/objects/EffectsResourceCosts.java  |   76 +
 src/engine/objects/EnchantmentBase.java       |   92 +
 src/engine/objects/EquipmentSetEntry.java     |   47 +
 src/engine/objects/Experience.java            |  441 ++
 src/engine/objects/Formation.java             |  118 +
 src/engine/objects/Group.java                 |  182 +
 src/engine/objects/Guild.java                 | 1297 ++++
 src/engine/objects/GuildAlliances.java        |  102 +
 src/engine/objects/GuildCondemn.java          |   77 +
 src/engine/objects/GuildHistory.java          |   94 +
 src/engine/objects/GuildStatusController.java |  131 +
 src/engine/objects/GuildTag.java              |   93 +
 src/engine/objects/Heraldry.java              |  171 +
 src/engine/objects/Item.java                  | 1466 +++++
 src/engine/objects/ItemBase.java              |  918 +++
 src/engine/objects/ItemContainer.java         |  102 +
 src/engine/objects/ItemFactory.java           | 1202 ++++
 src/engine/objects/Kit.java                   |  429 ++
 src/engine/objects/LevelDefault.java          |   72 +
 src/engine/objects/LootRow.java               |   63 +
 src/engine/objects/LootTable.java             | 1381 ++++
 src/engine/objects/MaxSkills.java             |   79 +
 src/engine/objects/MenuOption.java            |   58 +
 src/engine/objects/MeshBounds.java            |   49 +
 src/engine/objects/Mine.java                  |  791 +++
 src/engine/objects/MineProduction.java        |   94 +
 src/engine/objects/Mob.java                   | 3011 +++++++++
 src/engine/objects/MobBase.java               |  396 ++
 src/engine/objects/MobBaseEffects.java        |   66 +
 src/engine/objects/MobBaseStats.java          |   95 +
 src/engine/objects/MobEquipment.java          |  396 ++
 src/engine/objects/MobLoot.java               |  408 ++
 src/engine/objects/MobLootBase.java           |   59 +
 src/engine/objects/MobbaseGoldEntry.java      |   57 +
 src/engine/objects/NPC.java                   | 1866 ++++++
 src/engine/objects/NPCProfits.java            |  106 +
 src/engine/objects/NPCRune.java               |  119 +
 src/engine/objects/Nation.java                |  121 +
 src/engine/objects/PlayerBonuses.java         |  500 ++
 src/engine/objects/PlayerCharacter.java       | 5659 +++++++++++++++++
 src/engine/objects/PlayerFriends.java         |  122 +
 src/engine/objects/Portal.java                |  169 +
 src/engine/objects/PowerGrant.java            |  138 +
 src/engine/objects/PowerReq.java              |  194 +
 src/engine/objects/PowersBaseAttribute.java   |   65 +
 .../objects/PreparedStatementShared.java      | 1264 ++++
 src/engine/objects/ProducedItem.java          |  236 +
 src/engine/objects/PromotionClass.java        |  203 +
 src/engine/objects/Race.java                  |  368 ++
 src/engine/objects/Realm.java                 |  460 ++
 src/engine/objects/Regions.java               |  384 ++
 src/engine/objects/Resists.java               |  562 ++
 src/engine/objects/Resource.java              |   69 +
 src/engine/objects/RuneBase.java              |  217 +
 src/engine/objects/RuneBaseAttribute.java     |  107 +
 src/engine/objects/RuneBaseEffect.java        |   72 +
 src/engine/objects/Runegate.java              |  220 +
 src/engine/objects/Shrine.java                |  355 ++
 src/engine/objects/SkillReq.java              |   97 +
 src/engine/objects/SkillsBase.java            |  160 +
 src/engine/objects/SkillsBaseAttribute.java   |   70 +
 src/engine/objects/SpecialLoot.java           |   77 +
 src/engine/objects/StaticColliders.java       |  101 +
 src/engine/objects/Transaction.java           |  111 +
 src/engine/objects/VendorDialog.java          |   74 +
 src/engine/objects/Warehouse.java             | 1340 ++++
 src/engine/objects/Zone.java                  |  512 ++
 src/engine/pooling/ByteBufferPool.java        |   69 +
 src/engine/pooling/ConnectionPool.java        |   55 +
 src/engine/pooling/LinkedObjectPool.java      |  142 +
 .../pooling/MultisizeByteBufferPool.java      |  190 +
 src/engine/pooling/ObjectPool.java            |  195 +
 src/engine/pooling/Poolable.java              |   14 +
 src/engine/powers/ActionsBase.java            |  266 +
 src/engine/powers/DamageShield.java           |   43 +
 src/engine/powers/EffectsBase.java            |  892 +++
 src/engine/powers/FailCondition.java          |  118 +
 src/engine/powers/PowerPrereq.java            |  105 +
 src/engine/powers/PowersBase.java             |  700 ++
 src/engine/powers/RangeBasedAwo.java          |  104 +
 .../AbstractEffectModifier.java               |  373 ++
 .../AdjustAboveDmgCapEffectModifier.java      |   49 +
 .../AmbidexterityEffectModifier.java          |   42 +
 .../ArmorPiercingEffectModifier.java          |   42 +
 .../AttackDelayEffectModifier.java            |   46 +
 .../AttributeEffectModifier.java              |   56 +
 .../BlackMantleEffectModifier.java            |   53 +
 .../BladeTrailsEffectModifier.java            |   41 +
 .../effectmodifiers/BlockEffectModifier.java  |   46 +
 .../BlockedPowerTypeEffectModifier.java       |   62 +
 .../CannotAttackEffectModifier.java           |   41 +
 .../CannotCastEffectModifier.java             |   46 +
 .../CannotMoveEffectModifier.java             |   41 +
 .../CannotTrackEffectModifier.java            |   40 +
 .../CharmedEffectModifier.java                |   41 +
 ...onstrainedAmbidexterityEffectModifier.java |   40 +
 .../effectmodifiers/DCVEffectModifier.java    |   54 +
 .../effectmodifiers/DREffectModifier.java     |   61 +
 .../DamageCapEffectModifier.java              |   46 +
 .../DamageShieldEffectModifier.java           |   64 +
 .../effectmodifiers/DodgeEffectModifier.java  |   54 +
 .../DurabilityEffectModifier.java             |   41 +
 .../ExclusiveDamageCapEffectModifier.java     |   45 +
 .../effectmodifiers/FadeEffectModifier.java   |   41 +
 .../effectmodifiers/FlyEffectModifier.java    |   39 +
 .../effectmodifiers/HealthEffectModifier.java |  331 +
 .../HealthFullEffectModifier.java             |   53 +
 .../HealthRecoverRateEffectModifier.java      |   47 +
 .../IgnoreDamageCapEffectModifier.java        |   46 +
 .../IgnorePassiveDefenseEffectModifier.java   |   39 +
 .../ImmuneToAttackEffectModifier.java         |   40 +
 .../ImmuneToEffectModifier.java               |   39 +
 .../ImmuneToPowersEffectModifier.java         |   40 +
 .../InvisibleEffectModifier.java              |   82 +
 .../ItemNameEffectModifier.java               |  103 +
 .../effectmodifiers/ManaEffectModifier.java   |  232 +
 .../ManaFullEffectModifier.java               |   53 +
 .../ManaRecoverRateEffectModifier.java        |   45 +
 .../MaxDamageEffectModifier.java              |   61 +
 .../MeleeDamageEffectModifier.java            |   53 +
 .../MinDamageEffectModifier.java              |   61 +
 .../effectmodifiers/NoModEffectModifier.java  |   53 +
 .../effectmodifiers/OCVEffectModifier.java    |   53 +
 .../effectmodifiers/ParryEffectModifier.java  |   53 +
 .../PassiveDefenseEffectModifier.java         |   53 +
 .../PowerCostEffectModifier.java              |   53 +
 .../PowerCostHealthEffectModifier.java        |   41 +
 .../PowerDamageEffectModifier.java            |   53 +
 .../ProtectionFromEffectModifier.java         |   42 +
 .../ResistanceEffectModifier.java             |   53 +
 .../ScaleHeightEffectModifier.java            |   41 +
 .../ScaleWidthEffectModifier.java             |   41 +
 .../ScanRangeEffectModifier.java              |   53 +
 .../SeeInvisibleEffectModifier.java           |   42 +
 .../SilencedEffectModifier.java               |   40 +
 .../effectmodifiers/SkillEffectModifier.java  |   53 +
 .../effectmodifiers/SlayEffectModifier.java   |   53 +
 .../effectmodifiers/SpeedEffectModifier.java  |   53 +
 .../SpireBlockEffectModifier.java             |   39 +
 .../StaminaEffectModifier.java                |  230 +
 .../StaminaFullEffectModifier.java            |   53 +
 .../StaminaRecoverRateEffectModifier.java     |   47 +
 .../StunnedEffectModifier.java                |   47 +
 .../effectmodifiers/ValueEffectModifier.java  |   41 +
 .../WeaponProcEffectModifier.java             |   47 +
 .../WeaponRangeEffectModifier.java            |   54 +
 .../WeaponSpeedEffectModifier.java            |   48 +
 .../poweractions/AbstractPowerAction.java     |  274 +
 .../poweractions/ApplyEffectPowerAction.java  |  265 +
 .../poweractions/ApplyEffectsPowerAction.java |  124 +
 .../powers/poweractions/BlockPowerAction.java |   43 +
 .../powers/poweractions/CharmPowerAction.java |   73 +
 .../poweractions/ClaimMinePowerAction.java    |   61 +
 .../poweractions/ClearAggroPowerAction.java   |   52 +
 .../ClearNearbyAggroPowerAction.java          |   49 +
 .../poweractions/ConfusionPowerAction.java    |   43 +
 .../poweractions/CreateMobPowerAction.java    |  175 +
 .../DamageOverTimePowerAction.java            |   85 +
 .../DeferredPowerPowerAction.java             |   95 +
 .../poweractions/DirectDamagePowerAction.java |   93 +
 .../powers/poweractions/FearPowerAction.java  |   76 +
 .../powers/poweractions/InvisPowerAction.java |   78 +
 .../poweractions/MobRecallPowerAction.java    |   60 +
 .../poweractions/OpenGatePowerAction.java     |  130 +
 .../powers/poweractions/PeekPowerAction.java  |  137 +
 .../poweractions/RecallPowerAction.java       |   88 +
 .../poweractions/RemoveEffectPowerAction.java |   62 +
 .../poweractions/ResurrectPowerAction.java    |   43 +
 .../RunegateTeleportPowerAction.java          |   95 +
 .../poweractions/SetItemFlagPowerAction.java  |   65 +
 .../poweractions/SimpleDamagePowerAction.java |   51 +
 .../poweractions/SpireDisablePowerAction.java |   89 +
 .../powers/poweractions/StealPowerAction.java |  201 +
 .../poweractions/SummonPowerAction.java       |   78 +
 .../poweractions/TeleportPowerAction.java     |  116 +
 .../powers/poweractions/TrackPowerAction.java |  127 +
 .../TransferStatOTPowerAction.java            |   68 +
 .../poweractions/TransferStatPowerAction.java |  299 +
 .../poweractions/TransformPowerAction.java    |   71 +
 .../poweractions/TreeChokePowerAction.java    |   43 +
 src/engine/server/MBServerStatics.java        |  830 +++
 src/engine/server/login/LoginServer.java      |  524 ++
 .../server/login/LoginServerMsgHandler.java   |  444 ++
 src/engine/server/world/WorldServer.java      |  861 +++
 src/engine/session/CSSession.java             |  123 +
 src/engine/session/Session.java               |   71 +
 src/engine/session/SessionID.java             |   50 +
 src/engine/util/ByteAnalyzer.java             |  224 +
 src/engine/util/ByteBufferUtils.java          |  221 +
 src/engine/util/ByteUtils.java                |  171 +
 src/engine/util/Hasher.java                   |  382 ++
 src/engine/util/MapLoader.java                |  104 +
 src/engine/util/MiscUtils.java                |   94 +
 src/engine/util/StringUtils.java              |  150 +
 src/engine/util/ThreadUtils.java              |   43 +
 src/engine/workthreads/DestroyCityThread.java |  155 +
 .../workthreads/DisconnectTrashTask.java      |   52 +
 src/engine/workthreads/HourlyJobThread.java   |  131 +
 src/engine/workthreads/PurgeOprhans.java      |   65 +
 .../workthreads/TransferCityThread.java       |   99 +
 .../workthreads/WarehousePushThread.java      |  450 ++
 835 files changed, 168392 insertions(+)
 create mode 100644 src/discord/ChatChannel.java
 create mode 100644 src/discord/Database.java
 create mode 100644 src/discord/DiscordAccount.java
 create mode 100644 src/discord/MagicBot.java
 create mode 100644 src/discord/RobotSpeak.java
 create mode 100644 src/discord/handlers/AccountInfoRequest.java
 create mode 100644 src/discord/handlers/AnnounceChannelHandler.java
 create mode 100644 src/discord/handlers/BanToggleHandler.java
 create mode 100644 src/discord/handlers/ChangeLogHandler.java
 create mode 100644 src/discord/handlers/FlashHandler.java
 create mode 100644 src/discord/handlers/ForToFixChannelHandler.java
 create mode 100644 src/discord/handlers/GeneralChannelHandler.java
 create mode 100644 src/discord/handlers/LogsRequestHandler.java
 create mode 100644 src/discord/handlers/LookupRequestHandler.java
 create mode 100644 src/discord/handlers/PasswordChangeHandler.java
 create mode 100644 src/discord/handlers/PoliticalChannelHandler.java
 create mode 100644 src/discord/handlers/RecruitChannelHandler.java
 create mode 100644 src/discord/handlers/RegisterAccountHandler.java
 create mode 100644 src/discord/handlers/RulesRequestHandler.java
 create mode 100644 src/discord/handlers/ServerRequestHandler.java
 create mode 100644 src/discord/handlers/SetAvailHandler.java
 create mode 100644 src/discord/handlers/StatusRequestHandler.java
 create mode 100644 src/discord/handlers/TrashRequestHandler.java
 create mode 100644 src/engine/Enum.java
 create mode 100644 src/engine/InterestManagement/HeightMap.java
 create mode 100644 src/engine/InterestManagement/InterestManager.java
 create mode 100644 src/engine/InterestManagement/RealmMap.java
 create mode 100644 src/engine/InterestManagement/WorldGrid.java
 create mode 100644 src/engine/ai/MobileFSM.java
 create mode 100644 src/engine/ai/MobileFSMManager.java
 create mode 100644 src/engine/ai/utilities/CombatUtilities.java
 create mode 100644 src/engine/ai/utilities/MovementUtilities.java
 create mode 100644 src/engine/ai/utilities/PowerUtilities.java
 create mode 100644 src/engine/core/ControlledRunnable.java
 create mode 100644 src/engine/db/archive/BaneRecord.java
 create mode 100644 src/engine/db/archive/CharacterRecord.java
 create mode 100644 src/engine/db/archive/CityRecord.java
 create mode 100644 src/engine/db/archive/DataRecord.java
 create mode 100644 src/engine/db/archive/DataWarehouse.java
 create mode 100644 src/engine/db/archive/GuildRecord.java
 create mode 100644 src/engine/db/archive/MineRecord.java
 create mode 100644 src/engine/db/archive/PvpRecord.java
 create mode 100644 src/engine/db/archive/RealmRecord.java
 create mode 100644 src/engine/db/handlers/dbAccountHandler.java
 create mode 100644 src/engine/db/handlers/dbBaneHandler.java
 create mode 100644 src/engine/db/handlers/dbBaseClassHandler.java
 create mode 100644 src/engine/db/handlers/dbBlueprintHandler.java
 create mode 100644 src/engine/db/handlers/dbBoonHandler.java
 create mode 100644 src/engine/db/handlers/dbBuildingHandler.java
 create mode 100644 src/engine/db/handlers/dbBuildingLocationHandler.java
 create mode 100644 src/engine/db/handlers/dbCSSessionHandler.java
 create mode 100644 src/engine/db/handlers/dbCharacterPowerHandler.java
 create mode 100644 src/engine/db/handlers/dbCharacterRuneHandler.java
 create mode 100644 src/engine/db/handlers/dbCharacterSkillHandler.java
 create mode 100644 src/engine/db/handlers/dbCityHandler.java
 create mode 100644 src/engine/db/handlers/dbContractHandler.java
 create mode 100644 src/engine/db/handlers/dbEffectsBaseHandler.java
 create mode 100644 src/engine/db/handlers/dbEffectsResourceCostHandler.java
 create mode 100644 src/engine/db/handlers/dbEnchantmentHandler.java
 create mode 100644 src/engine/db/handlers/dbGuildHandler.java
 create mode 100644 src/engine/db/handlers/dbHandlerBase.java
 create mode 100644 src/engine/db/handlers/dbHeightMapHandler.java
 create mode 100644 src/engine/db/handlers/dbItemBaseHandler.java
 create mode 100644 src/engine/db/handlers/dbItemHandler.java
 create mode 100644 src/engine/db/handlers/dbKitHandler.java
 create mode 100644 src/engine/db/handlers/dbLootTableHandler.java
 create mode 100644 src/engine/db/handlers/dbMenuHandler.java
 create mode 100644 src/engine/db/handlers/dbMineHandler.java
 create mode 100644 src/engine/db/handlers/dbMobBaseHandler.java
 create mode 100644 src/engine/db/handlers/dbMobHandler.java
 create mode 100644 src/engine/db/handlers/dbNPCHandler.java
 create mode 100644 src/engine/db/handlers/dbPlayerCharacterHandler.java
 create mode 100644 src/engine/db/handlers/dbPromotionClassHandler.java
 create mode 100644 src/engine/db/handlers/dbRaceHandler.java
 create mode 100644 src/engine/db/handlers/dbRealmHandler.java
 create mode 100644 src/engine/db/handlers/dbResistHandler.java
 create mode 100644 src/engine/db/handlers/dbRuneBaseAttributeHandler.java
 create mode 100644 src/engine/db/handlers/dbRuneBaseEffectHandler.java
 create mode 100644 src/engine/db/handlers/dbRuneBaseHandler.java
 create mode 100644 src/engine/db/handlers/dbShrineHandler.java
 create mode 100644 src/engine/db/handlers/dbSkillBaseHandler.java
 create mode 100644 src/engine/db/handlers/dbSkillReqHandler.java
 create mode 100644 src/engine/db/handlers/dbSpecialLootHandler.java
 create mode 100644 src/engine/db/handlers/dbVendorDialogHandler.java
 create mode 100644 src/engine/db/handlers/dbWarehouseHandler.java
 create mode 100644 src/engine/db/handlers/dbZoneHandler.java
 create mode 100644 src/engine/devcmd/AbstractDevCmd.java
 create mode 100644 src/engine/devcmd/cmds/AddBuildingCmd.java
 create mode 100644 src/engine/devcmd/cmds/AddGoldCmd.java
 create mode 100644 src/engine/devcmd/cmds/AddMobCmd.java
 create mode 100644 src/engine/devcmd/cmds/AddMobPowerCmd.java
 create mode 100644 src/engine/devcmd/cmds/AddMobRuneCmd.java
 create mode 100644 src/engine/devcmd/cmds/AddNPCCmd.java
 create mode 100644 src/engine/devcmd/cmds/ApplyBonusCmd.java
 create mode 100644 src/engine/devcmd/cmds/ApplyStatModCmd.java
 create mode 100644 src/engine/devcmd/cmds/AuditFailedItemsCmd.java
 create mode 100644 src/engine/devcmd/cmds/AuditHeightMapCmd.java
 create mode 100644 src/engine/devcmd/cmds/AuditMobsCmd.java
 create mode 100644 src/engine/devcmd/cmds/BoundsCmd.java
 create mode 100644 src/engine/devcmd/cmds/ChangeNameCmd.java
 create mode 100644 src/engine/devcmd/cmds/CombatMessageCmd.java
 create mode 100644 src/engine/devcmd/cmds/CopyMobCmd.java
 create mode 100644 src/engine/devcmd/cmds/CreateItemCmd.java
 create mode 100644 src/engine/devcmd/cmds/DebugCmd.java
 create mode 100644 src/engine/devcmd/cmds/DebugMeleeSyncCmd.java
 create mode 100644 src/engine/devcmd/cmds/DecachePlayerCmd.java
 create mode 100644 src/engine/devcmd/cmds/DespawnCmd.java
 create mode 100644 src/engine/devcmd/cmds/DistanceCmd.java
 create mode 100644 src/engine/devcmd/cmds/EffectCmd.java
 create mode 100644 src/engine/devcmd/cmds/EnchantCmd.java
 create mode 100644 src/engine/devcmd/cmds/FindBuildingsCmd.java
 create mode 100644 src/engine/devcmd/cmds/FlashMsgCmd.java
 create mode 100644 src/engine/devcmd/cmds/GateInfoCmd.java
 create mode 100644 src/engine/devcmd/cmds/GetBankCmd.java
 create mode 100644 src/engine/devcmd/cmds/GetCacheCountCmd.java
 create mode 100644 src/engine/devcmd/cmds/GetDisciplineLocCmd.java
 create mode 100644 src/engine/devcmd/cmds/GetHeightCmd.java
 create mode 100644 src/engine/devcmd/cmds/GetMemoryCmd.java
 create mode 100644 src/engine/devcmd/cmds/GetMobBaseLoot.java
 create mode 100644 src/engine/devcmd/cmds/GetOffsetCmd.java
 create mode 100644 src/engine/devcmd/cmds/GetRuneDropRateCmd.java
 create mode 100644 src/engine/devcmd/cmds/GetVaultCmd.java
 create mode 100644 src/engine/devcmd/cmds/GetZoneCmd.java
 create mode 100644 src/engine/devcmd/cmds/GetZoneMobsCmd.java
 create mode 100644 src/engine/devcmd/cmds/GotoBoundsCmd.java
 create mode 100644 src/engine/devcmd/cmds/GotoCmd.java
 create mode 100644 src/engine/devcmd/cmds/GotoObj.java
 create mode 100644 src/engine/devcmd/cmds/GuildListCmd.java
 create mode 100644 src/engine/devcmd/cmds/HeartbeatCmd.java
 create mode 100644 src/engine/devcmd/cmds/HelpCmd.java
 create mode 100644 src/engine/devcmd/cmds/HotzoneCmd.java
 create mode 100644 src/engine/devcmd/cmds/InfoCmd.java
 create mode 100644 src/engine/devcmd/cmds/JumpCmd.java
 create mode 100644 src/engine/devcmd/cmds/MBDropCmd.java
 create mode 100644 src/engine/devcmd/cmds/MakeBaneCmd.java
 create mode 100644 src/engine/devcmd/cmds/MakeItemCmd.java
 create mode 100644 src/engine/devcmd/cmds/NetDebugCmd.java
 create mode 100644 src/engine/devcmd/cmds/PrintBankCmd.java
 create mode 100644 src/engine/devcmd/cmds/PrintBonusesCmd.java
 create mode 100644 src/engine/devcmd/cmds/PrintEquipCmd.java
 create mode 100644 src/engine/devcmd/cmds/PrintInventoryCmd.java
 create mode 100644 src/engine/devcmd/cmds/PrintLocationCmd.java
 create mode 100644 src/engine/devcmd/cmds/PrintPowersCmd.java
 create mode 100644 src/engine/devcmd/cmds/PrintResistsCmd.java
 create mode 100644 src/engine/devcmd/cmds/PrintSkillsCmd.java
 create mode 100644 src/engine/devcmd/cmds/PrintStatsCmd.java
 create mode 100644 src/engine/devcmd/cmds/PrintVaultCmd.java
 create mode 100644 src/engine/devcmd/cmds/PullCmd.java
 create mode 100644 src/engine/devcmd/cmds/PurgeObjectsCmd.java
 create mode 100644 src/engine/devcmd/cmds/RealmInfoCmd.java
 create mode 100644 src/engine/devcmd/cmds/RebootCmd.java
 create mode 100644 src/engine/devcmd/cmds/RegionCmd.java
 create mode 100644 src/engine/devcmd/cmds/RemoveBaneCmd.java
 create mode 100644 src/engine/devcmd/cmds/RemoveObjectCmd.java
 create mode 100644 src/engine/devcmd/cmds/RenameCmd.java
 create mode 100644 src/engine/devcmd/cmds/RenameMobCmd.java
 create mode 100644 src/engine/devcmd/cmds/ResetLevelCmd.java
 create mode 100644 src/engine/devcmd/cmds/RotateCmd.java
 create mode 100644 src/engine/devcmd/cmds/SetAICmd.java
 create mode 100644 src/engine/devcmd/cmds/SetActivateMineCmd.java
 create mode 100644 src/engine/devcmd/cmds/SetAdminRuneCmd.java
 create mode 100644 src/engine/devcmd/cmds/SetBaneActiveCmd.java
 create mode 100644 src/engine/devcmd/cmds/SetBaseClassCmd.java
 create mode 100644 src/engine/devcmd/cmds/SetBuildingAltitudeCmd.java
 create mode 100644 src/engine/devcmd/cmds/SetForceRenameCityCmd.java
 create mode 100644 src/engine/devcmd/cmds/SetGuildCmd.java
 create mode 100644 src/engine/devcmd/cmds/SetHealthCmd.java
 create mode 100644 src/engine/devcmd/cmds/SetInvulCmd.java
 create mode 100644 src/engine/devcmd/cmds/SetLevelCmd.java
 create mode 100644 src/engine/devcmd/cmds/SetMaintCmd.java
 create mode 100644 src/engine/devcmd/cmds/SetManaCmd.java
 create mode 100644 src/engine/devcmd/cmds/SetMineExpansion.java
 create mode 100644 src/engine/devcmd/cmds/SetMineTypeCmd.java
 create mode 100644 src/engine/devcmd/cmds/SetNPCSlotCmd.java
 create mode 100644 src/engine/devcmd/cmds/SetNpcEquipSetCmd.java
 create mode 100644 src/engine/devcmd/cmds/SetNpcMobbaseCmd.java
 create mode 100644 src/engine/devcmd/cmds/SetNpcNameCmd.java
 create mode 100644 src/engine/devcmd/cmds/SetOwnerCmd.java
 create mode 100644 src/engine/devcmd/cmds/SetPromotionClassCmd.java
 create mode 100644 src/engine/devcmd/cmds/SetRankCmd.java
 create mode 100644 src/engine/devcmd/cmds/SetRateCmd.java
 create mode 100644 src/engine/devcmd/cmds/SetRuneCmd.java
 create mode 100644 src/engine/devcmd/cmds/SetStaminaCmd.java
 create mode 100644 src/engine/devcmd/cmds/SetSubRaceCmd.java
 create mode 100644 src/engine/devcmd/cmds/ShowOffsetCmd.java
 create mode 100644 src/engine/devcmd/cmds/SlotNpcCmd.java
 create mode 100644 src/engine/devcmd/cmds/SplatMobCmd.java
 create mode 100644 src/engine/devcmd/cmds/SqlDebugCmd.java
 create mode 100644 src/engine/devcmd/cmds/SummonCmd.java
 create mode 100644 src/engine/devcmd/cmds/SysMsgCmd.java
 create mode 100644 src/engine/devcmd/cmds/TeleportModeCmd.java
 create mode 100644 src/engine/devcmd/cmds/UnloadFurnitureCmd.java
 create mode 100644 src/engine/devcmd/cmds/ZoneInfoCmd.java
 create mode 100644 src/engine/devcmd/cmds/convertLoc.java
 create mode 100644 src/engine/devcmd/cmds/setOpenDateCmd.java
 create mode 100644 src/engine/exception/FactoryBuildException.java
 create mode 100644 src/engine/exception/MBServerException.java
 create mode 100644 src/engine/exception/MsgSendException.java
 create mode 100644 src/engine/exception/SerializationException.java
 create mode 100644 src/engine/gameManager/BuildingManager.java
 create mode 100644 src/engine/gameManager/ChatManager.java
 create mode 100644 src/engine/gameManager/CombatManager.java
 create mode 100644 src/engine/gameManager/ConfigManager.java
 create mode 100644 src/engine/gameManager/DbManager.java
 create mode 100644 src/engine/gameManager/DevCmdManager.java
 create mode 100644 src/engine/gameManager/GroupManager.java
 create mode 100644 src/engine/gameManager/GuildManager.java
 create mode 100644 src/engine/gameManager/MaintenanceManager.java
 create mode 100644 src/engine/gameManager/MovementManager.java
 create mode 100644 src/engine/gameManager/PowersManager.java
 create mode 100644 src/engine/gameManager/SessionManager.java
 create mode 100644 src/engine/gameManager/SimulationManager.java
 create mode 100644 src/engine/gameManager/TradeManager.java
 create mode 100644 src/engine/gameManager/ZoneManager.java
 create mode 100644 src/engine/job/AbstractJob.java
 create mode 100644 src/engine/job/AbstractJobStatistics.java
 create mode 100644 src/engine/job/AbstractScheduleJob.java
 create mode 100644 src/engine/job/ClassJobStatistics.java
 create mode 100644 src/engine/job/JobContainer.java
 create mode 100644 src/engine/job/JobManager.java
 create mode 100644 src/engine/job/JobPool.java
 create mode 100644 src/engine/job/JobScheduler.java
 create mode 100644 src/engine/job/JobWorker.java
 create mode 100644 src/engine/jobs/AbstractEffectJob.java
 create mode 100644 src/engine/jobs/ActivateBaneJob.java
 create mode 100644 src/engine/jobs/AttackJob.java
 create mode 100644 src/engine/jobs/BaneDefaultTimeJob.java
 create mode 100644 src/engine/jobs/BasicScheduledJob.java
 create mode 100644 src/engine/jobs/BonusCalcJob.java
 create mode 100644 src/engine/jobs/CSessionCleanupJob.java
 create mode 100644 src/engine/jobs/ChangeAltitudeJob.java
 create mode 100644 src/engine/jobs/ChantJob.java
 create mode 100644 src/engine/jobs/CloseGateJob.java
 create mode 100644 src/engine/jobs/DamageOverTimeJob.java
 create mode 100644 src/engine/jobs/DatabaseUpdateJob.java
 create mode 100644 src/engine/jobs/DebugTimerJob.java
 create mode 100644 src/engine/jobs/DeferredPowerJob.java
 create mode 100644 src/engine/jobs/DisconnectJob.java
 create mode 100644 src/engine/jobs/DoorCloseJob.java
 create mode 100644 src/engine/jobs/EndFearJob.java
 create mode 100644 src/engine/jobs/FinishCooldownTimeJob.java
 create mode 100644 src/engine/jobs/FinishEffectTimeJob.java
 create mode 100644 src/engine/jobs/FinishRecycleTimeJob.java
 create mode 100644 src/engine/jobs/FinishSpireEffectJob.java
 create mode 100644 src/engine/jobs/FinishSummonsJob.java
 create mode 100644 src/engine/jobs/FlightJob.java
 create mode 100644 src/engine/jobs/LoadEffectsJob.java
 create mode 100644 src/engine/jobs/LogoutCharacterJob.java
 create mode 100644 src/engine/jobs/MineActiveJob.java
 create mode 100644 src/engine/jobs/NoTimeJob.java
 create mode 100644 src/engine/jobs/PersistentAoeJob.java
 create mode 100644 src/engine/jobs/RefreshGroupJob.java
 create mode 100644 src/engine/jobs/RemoveCorpseJob.java
 create mode 100644 src/engine/jobs/SiegeSpireWithdrawlJob.java
 create mode 100644 src/engine/jobs/StuckJob.java
 create mode 100644 src/engine/jobs/SummonSendJob.java
 create mode 100644 src/engine/jobs/TeleportJob.java
 create mode 100644 src/engine/jobs/TrackJob.java
 create mode 100644 src/engine/jobs/TransferStatOTJob.java
 create mode 100644 src/engine/jobs/UpdateGroupJob.java
 create mode 100644 src/engine/jobs/UpgradeBuildingJob.java
 create mode 100644 src/engine/jobs/UpgradeNPCJob.java
 create mode 100644 src/engine/jobs/UseItemJob.java
 create mode 100644 src/engine/jobs/UseMobPowerJob.java
 create mode 100644 src/engine/jobs/UsePowerJob.java
 create mode 100644 src/engine/loot/LootGroup.java
 create mode 100644 src/engine/loot/LootManager.java
 create mode 100644 src/engine/loot/LootTable.java
 create mode 100644 src/engine/loot/ModifierGroup.java
 create mode 100644 src/engine/loot/ModifierTable.java
 create mode 100644 src/engine/math/AtomicFloat.java
 create mode 100644 src/engine/math/Bounds.java
 create mode 100644 src/engine/math/FastMath.java
 create mode 100644 src/engine/math/Matrix3f.java
 create mode 100644 src/engine/math/Matrix4f.java
 create mode 100644 src/engine/math/Quaternion.java
 create mode 100644 src/engine/math/Vector2f.java
 create mode 100644 src/engine/math/Vector3f.java
 create mode 100644 src/engine/math/Vector3fImmutable.java
 create mode 100644 src/engine/net/AbstractConnection.java
 create mode 100644 src/engine/net/AbstractConnectionManager.java
 create mode 100644 src/engine/net/AbstractNetMsg.java
 create mode 100644 src/engine/net/ByteBufferReader.java
 create mode 100644 src/engine/net/ByteBufferWriter.java
 create mode 100644 src/engine/net/CheckNetMsgFactoryJob.java
 create mode 100644 src/engine/net/ConnectionMonitorJob.java
 create mode 100644 src/engine/net/Dispatch.java
 create mode 100644 src/engine/net/DispatchMessage.java
 create mode 100644 src/engine/net/ItemProductionManager.java
 create mode 100644 src/engine/net/ItemQueue.java
 create mode 100644 src/engine/net/MessageDispatcher.java
 create mode 100644 src/engine/net/NetMsgFactory.java
 create mode 100644 src/engine/net/NetMsgHandler.java
 create mode 100644 src/engine/net/NetMsgStat.java
 create mode 100644 src/engine/net/Network.java
 create mode 100644 src/engine/net/client/ClientAuthenticator.java
 create mode 100644 src/engine/net/client/ClientConnection.java
 create mode 100644 src/engine/net/client/ClientConnectionManager.java
 create mode 100644 src/engine/net/client/ClientMessagePump.java
 create mode 100644 src/engine/net/client/Protocol.java
 create mode 100644 src/engine/net/client/handlers/AbandonAssetMsgHandler.java
 create mode 100644 src/engine/net/client/handlers/AbstractClientMsgHandler.java
 create mode 100644 src/engine/net/client/handlers/AcceptInviteToGuildHandler.java
 create mode 100644 src/engine/net/client/handlers/AcceptSubInviteHandler.java
 create mode 100644 src/engine/net/client/handlers/ActivateNPCMsgHandler.java
 create mode 100644 src/engine/net/client/handlers/AllianceChangeMsgHandler.java
 create mode 100644 src/engine/net/client/handlers/AllyEnemyListMsgHandler.java
 create mode 100644 src/engine/net/client/handlers/AppointGroupLeaderHandler.java
 create mode 100644 src/engine/net/client/handlers/ArcLoginNotifyMsgHandler.java
 create mode 100644 src/engine/net/client/handlers/ArcSiegeSpireMsgHandler.java
 create mode 100644 src/engine/net/client/handlers/ArcViewAssetTransactionsMsgHandler.java
 create mode 100644 src/engine/net/client/handlers/AssetSupportMsgHandler.java
 create mode 100644 src/engine/net/client/handlers/BanishUnbanishHandler.java
 create mode 100644 src/engine/net/client/handlers/BreakFealtyHandler.java
 create mode 100644 src/engine/net/client/handlers/ChangeAltitudeHandler.java
 create mode 100644 src/engine/net/client/handlers/ChangeGuildLeaderHandler.java
 create mode 100644 src/engine/net/client/handlers/ChangeRankHandler.java
 create mode 100644 src/engine/net/client/handlers/ChannelMuteMsgHandler.java
 create mode 100644 src/engine/net/client/handlers/CityChoiceMsgHandler.java
 create mode 100644 src/engine/net/client/handlers/ClaimAssetMsgHandler.java
 create mode 100644 src/engine/net/client/handlers/ClaimGuildTreeMsgHandler.java
 create mode 100644 src/engine/net/client/handlers/DestroyBuildingHandler.java
 create mode 100644 src/engine/net/client/handlers/DisbandGroupHandler.java
 create mode 100644 src/engine/net/client/handlers/DisbandGuildHandler.java
 create mode 100644 src/engine/net/client/handlers/DismissGuildHandler.java
 create mode 100644 src/engine/net/client/handlers/DoorTryOpenMsgHandler.java
 create mode 100644 src/engine/net/client/handlers/FormationFollowHandler.java
 create mode 100644 src/engine/net/client/handlers/FriendAcceptHandler.java
 create mode 100644 src/engine/net/client/handlers/FriendDeclineHandler.java
 create mode 100644 src/engine/net/client/handlers/FriendRequestHandler.java
 create mode 100644 src/engine/net/client/handlers/FurnitureHandler.java
 create mode 100644 src/engine/net/client/handlers/GroupInviteHandler.java
 create mode 100644 src/engine/net/client/handlers/GroupInviteResponseHandler.java
 create mode 100644 src/engine/net/client/handlers/GroupUpdateHandler.java
 create mode 100644 src/engine/net/client/handlers/GuildControlHandler.java
 create mode 100644 src/engine/net/client/handlers/GuildCreationCloseHandler.java
 create mode 100644 src/engine/net/client/handlers/GuildCreationFinalizeHandler.java
 create mode 100644 src/engine/net/client/handlers/GuildCreationOptionsHandler.java
 create mode 100644 src/engine/net/client/handlers/GuildInfoHandler.java
 create mode 100644 src/engine/net/client/handlers/GuildListHandler.java
 create mode 100644 src/engine/net/client/handlers/GuildUnknownHandler.java
 create mode 100644 src/engine/net/client/handlers/HirelingServiceMsgHandler.java
 create mode 100644 src/engine/net/client/handlers/InviteToGuildHandler.java
 create mode 100644 src/engine/net/client/handlers/InviteToSubHandler.java
 create mode 100644 src/engine/net/client/handlers/ItemProductionMsgHandler.java
 create mode 100644 src/engine/net/client/handlers/KeepAliveServerClientHandler.java
 create mode 100644 src/engine/net/client/handlers/KeyCloneAudit.java
 create mode 100644 src/engine/net/client/handlers/LeaveGroupHandler.java
 create mode 100644 src/engine/net/client/handlers/LeaveGuildHandler.java
 create mode 100644 src/engine/net/client/handlers/LockUnlockDoorMsgHandler.java
 create mode 100644 src/engine/net/client/handlers/LoginToGameServerMsgHandler.java
 create mode 100644 src/engine/net/client/handlers/MOTDCommitHandler.java
 create mode 100644 src/engine/net/client/handlers/MOTDEditHandler.java
 create mode 100644 src/engine/net/client/handlers/ManageCityAssetMsgHandler.java
 create mode 100644 src/engine/net/client/handlers/MerchantMsgHandler.java
 create mode 100644 src/engine/net/client/handlers/MinionTrainingMsgHandler.java
 create mode 100644 src/engine/net/client/handlers/MoveToPointHandler.java
 create mode 100644 src/engine/net/client/handlers/ObjectActionMsgHandler.java
 create mode 100644 src/engine/net/client/handlers/OpenFriendsCondemnListMsgHandler.java
 create mode 100644 src/engine/net/client/handlers/OrderNPCMsgHandler.java
 create mode 100644 src/engine/net/client/handlers/PlaceAssetMsgHandler.java
 create mode 100644 src/engine/net/client/handlers/RecommendNationMsgHandler.java
 create mode 100644 src/engine/net/client/handlers/RemoveFriendHandler.java
 create mode 100644 src/engine/net/client/handlers/RemoveFromGroupHandler.java
 create mode 100644 src/engine/net/client/handlers/RepairBuildingMsgHandler.java
 create mode 100644 src/engine/net/client/handlers/RequestBallListHandler.java
 create mode 100644 src/engine/net/client/handlers/RequestEnterWorldHandler.java
 create mode 100644 src/engine/net/client/handlers/RequestGuildListHandler.java
 create mode 100644 src/engine/net/client/handlers/SendBallEntryHandler.java
 create mode 100644 src/engine/net/client/handlers/SwearInGuildHandler.java
 create mode 100644 src/engine/net/client/handlers/SwearInHandler.java
 create mode 100644 src/engine/net/client/handlers/TaxCityMsgHandler.java
 create mode 100644 src/engine/net/client/handlers/TaxResourcesMsgHandler.java
 create mode 100644 src/engine/net/client/handlers/ToggleGroupSplitHandler.java
 create mode 100644 src/engine/net/client/handlers/TransferAssetMsgHandler.java
 create mode 100644 src/engine/net/client/handlers/TransferGoldToFromBuildingMsgHandler.java
 create mode 100644 src/engine/net/client/handlers/UpdateFriendStatusHandler.java
 create mode 100644 src/engine/net/client/handlers/UpgradeAssetMsgHandler.java
 create mode 100644 src/engine/net/client/msg/AbandonAssetMsg.java
 create mode 100644 src/engine/net/client/msg/AcceptFriendMsg.java
 create mode 100644 src/engine/net/client/msg/AcceptTradeRequestMsg.java
 create mode 100644 src/engine/net/client/msg/AckBankWindowOpenedMsg.java
 create mode 100644 src/engine/net/client/msg/ActivateNPCMessage.java
 create mode 100644 src/engine/net/client/msg/AddFriendMessage.java
 create mode 100644 src/engine/net/client/msg/AddGoldToTradeWindowMsg.java
 create mode 100644 src/engine/net/client/msg/AddItemToTradeWindowMsg.java
 create mode 100644 src/engine/net/client/msg/AllianceChangeMsg.java
 create mode 100644 src/engine/net/client/msg/AllyEnemyListMsg.java
 create mode 100644 src/engine/net/client/msg/ApplyBuildingEffectMsg.java
 create mode 100644 src/engine/net/client/msg/ApplyEffectMsg.java
 create mode 100644 src/engine/net/client/msg/ApplyRuneMsg.java
 create mode 100644 src/engine/net/client/msg/ArcLoginNotifyMsg.java
 create mode 100644 src/engine/net/client/msg/ArcMineChangeProductionMsg.java
 create mode 100644 src/engine/net/client/msg/ArcMineWindowAvailableTimeMsg.java
 create mode 100644 src/engine/net/client/msg/ArcMineWindowChangeMsg.java
 create mode 100644 src/engine/net/client/msg/ArcOwnedMinesListMsg.java
 create mode 100644 src/engine/net/client/msg/ArcSiegeSpireMsg.java
 create mode 100644 src/engine/net/client/msg/ArcViewAssetTransactionsMsg.java
 create mode 100644 src/engine/net/client/msg/AssetSupportMsg.java
 create mode 100644 src/engine/net/client/msg/AttackCmdMsg.java
 create mode 100644 src/engine/net/client/msg/BuyFromNPCMsg.java
 create mode 100644 src/engine/net/client/msg/BuyFromNPCWindowMsg.java
 create mode 100644 src/engine/net/client/msg/ChangeAltitudeMsg.java
 create mode 100644 src/engine/net/client/msg/ChangeGuildLeaderMsg.java
 create mode 100644 src/engine/net/client/msg/ChatFilterMsg.java
 create mode 100644 src/engine/net/client/msg/CityAssetMsg.java
 create mode 100644 src/engine/net/client/msg/CityChoiceMsg.java
 create mode 100644 src/engine/net/client/msg/CityZoneMsg.java
 create mode 100644 src/engine/net/client/msg/ClaimAssetMsg.java
 create mode 100644 src/engine/net/client/msg/ClaimGuildTreeMsg.java
 create mode 100644 src/engine/net/client/msg/ClientNetMsg.java
 create mode 100644 src/engine/net/client/msg/CloseTradeWindowMsg.java
 create mode 100644 src/engine/net/client/msg/CommitToTradeMsg.java
 create mode 100644 src/engine/net/client/msg/ConfirmPromoteMsg.java
 create mode 100644 src/engine/net/client/msg/DeclineFriendMsg.java
 create mode 100644 src/engine/net/client/msg/DeleteItemMsg.java
 create mode 100644 src/engine/net/client/msg/DestroyBuildingMsg.java
 create mode 100644 src/engine/net/client/msg/DoorTryOpenMsg.java
 create mode 100644 src/engine/net/client/msg/DropGoldMsg.java
 create mode 100644 src/engine/net/client/msg/EnterWorldReceivedMsg.java
 create mode 100644 src/engine/net/client/msg/ErrorPopupMsg.java
 create mode 100644 src/engine/net/client/msg/FriendRequestMsg.java
 create mode 100644 src/engine/net/client/msg/FurnitureMsg.java
 create mode 100644 src/engine/net/client/msg/GrantExperienceMsg.java
 create mode 100644 src/engine/net/client/msg/GuildTreeStatusMsg.java
 create mode 100644 src/engine/net/client/msg/HirelingServiceMsg.java
 create mode 100644 src/engine/net/client/msg/HotzoneChangeMsg.java
 create mode 100644 src/engine/net/client/msg/IgnoreListMsg.java
 create mode 100644 src/engine/net/client/msg/IgnoreMsg.java
 create mode 100644 src/engine/net/client/msg/InvalidTradeRequestMsg.java
 create mode 100644 src/engine/net/client/msg/ItemEffectMsg.java
 create mode 100644 src/engine/net/client/msg/ItemHealthUpdateMsg.java
 create mode 100644 src/engine/net/client/msg/ItemProductionMsg.java
 create mode 100644 src/engine/net/client/msg/KeepAliveServerClientMsg.java
 create mode 100644 src/engine/net/client/msg/LeaderboardMessage.java
 create mode 100644 src/engine/net/client/msg/LeaveWorldMsg.java
 create mode 100644 src/engine/net/client/msg/LoadCharacterMsg.java
 create mode 100644 src/engine/net/client/msg/LoadStructureMsg.java
 create mode 100644 src/engine/net/client/msg/LockUnlockDoorMsg.java
 create mode 100644 src/engine/net/client/msg/LoginToGameServerMsg.java
 create mode 100644 src/engine/net/client/msg/LootMsg.java
 create mode 100644 src/engine/net/client/msg/LootWindowRequestMsg.java
 create mode 100644 src/engine/net/client/msg/LootWindowResponseMsg.java
 create mode 100644 src/engine/net/client/msg/ManageCityAssetsMsg.java
 create mode 100644 src/engine/net/client/msg/ManageNPCMsg.java
 create mode 100644 src/engine/net/client/msg/MerchantMsg.java
 create mode 100644 src/engine/net/client/msg/MinionTrainingMessage.java
 create mode 100644 src/engine/net/client/msg/ModifyCommitToTradeMsg.java
 create mode 100644 src/engine/net/client/msg/ModifyHealthKillMsg.java
 create mode 100644 src/engine/net/client/msg/ModifyHealthMsg.java
 create mode 100644 src/engine/net/client/msg/ModifyStatMsg.java
 create mode 100644 src/engine/net/client/msg/MoveCorrectionMsg.java
 create mode 100644 src/engine/net/client/msg/MoveToPointMsg.java
 create mode 100644 src/engine/net/client/msg/ObjectActionMsg.java
 create mode 100644 src/engine/net/client/msg/OpenFriendsCondemnListMsg.java
 create mode 100644 src/engine/net/client/msg/OpenTradeWindowMsg.java
 create mode 100644 src/engine/net/client/msg/OpenVaultMsg.java
 create mode 100644 src/engine/net/client/msg/OrderNPCMsg.java
 create mode 100644 src/engine/net/client/msg/PassiveMessageTriggerMsg.java
 create mode 100644 src/engine/net/client/msg/PerformActionMsg.java
 create mode 100644 src/engine/net/client/msg/PetAttackMsg.java
 create mode 100644 src/engine/net/client/msg/PetCmdMsg.java
 create mode 100644 src/engine/net/client/msg/PetMsg.java
 create mode 100644 src/engine/net/client/msg/PetitionReceivedMsg.java
 create mode 100644 src/engine/net/client/msg/PlaceAssetMsg.java
 create mode 100644 src/engine/net/client/msg/PowerProjectileMsg.java
 create mode 100644 src/engine/net/client/msg/PromptRecallMsg.java
 create mode 100644 src/engine/net/client/msg/RandomMsg.java
 create mode 100644 src/engine/net/client/msg/RecommendNationMsg.java
 create mode 100644 src/engine/net/client/msg/RecvSummonsRequestMsg.java
 create mode 100644 src/engine/net/client/msg/RecyclePowerMsg.java
 create mode 100644 src/engine/net/client/msg/RefineMsg.java
 create mode 100644 src/engine/net/client/msg/RefinerScreenMsg.java
 create mode 100644 src/engine/net/client/msg/RejectTradeRequestMsg.java
 create mode 100644 src/engine/net/client/msg/RemoveFriendMessage.java
 create mode 100644 src/engine/net/client/msg/RepairBuildingMsg.java
 create mode 100644 src/engine/net/client/msg/RepairMsg.java
 create mode 100644 src/engine/net/client/msg/ReqBankInventoryMsg.java
 create mode 100644 src/engine/net/client/msg/RequestBallListMessage.java
 create mode 100644 src/engine/net/client/msg/RequestEnterWorldMsg.java
 create mode 100644 src/engine/net/client/msg/RespawnMsg.java
 create mode 100644 src/engine/net/client/msg/RespondLeaveWorldMsg.java
 create mode 100644 src/engine/net/client/msg/RotateObjectMsg.java
 create mode 100644 src/engine/net/client/msg/SafeModeMsg.java
 create mode 100644 src/engine/net/client/msg/ScaleObjectMsg.java
 create mode 100644 src/engine/net/client/msg/SelectCityMsg.java
 create mode 100644 src/engine/net/client/msg/SellToNPCMsg.java
 create mode 100644 src/engine/net/client/msg/SellToNPCWindowMsg.java
 create mode 100644 src/engine/net/client/msg/SendBallEntryMessage.java
 create mode 100644 src/engine/net/client/msg/SendOwnPlayerMsg.java
 create mode 100644 src/engine/net/client/msg/SendSummonsRequestMsg.java
 create mode 100644 src/engine/net/client/msg/ServerInfoMsg.java
 create mode 100644 src/engine/net/client/msg/SetCombatModeMsg.java
 create mode 100644 src/engine/net/client/msg/SetObjectValueMsg.java
 create mode 100644 src/engine/net/client/msg/ShowBankInventoryMsg.java
 create mode 100644 src/engine/net/client/msg/ShowMsg.java
 create mode 100644 src/engine/net/client/msg/ShowVaultInventoryMsg.java
 create mode 100644 src/engine/net/client/msg/SocialMsg.java
 create mode 100644 src/engine/net/client/msg/StuckCommandMsg.java
 create mode 100644 src/engine/net/client/msg/SyncMessage.java
 create mode 100644 src/engine/net/client/msg/TargetObjectMsg.java
 create mode 100644 src/engine/net/client/msg/TargetedActionMsg.java
 create mode 100644 src/engine/net/client/msg/TaxCityMsg.java
 create mode 100644 src/engine/net/client/msg/TaxResourcesMsg.java
 create mode 100644 src/engine/net/client/msg/TeleportRepledgeListMsg.java
 create mode 100644 src/engine/net/client/msg/TeleportToPointMsg.java
 create mode 100644 src/engine/net/client/msg/TerritoryChangeMessage.java
 create mode 100644 src/engine/net/client/msg/ToggleCombatMsg.java
 create mode 100644 src/engine/net/client/msg/ToggleLfgRecruitingMsg.java
 create mode 100644 src/engine/net/client/msg/ToggleSitStandMsg.java
 create mode 100644 src/engine/net/client/msg/TrackArrowMsg.java
 create mode 100644 src/engine/net/client/msg/TrackWindowMsg.java
 create mode 100644 src/engine/net/client/msg/TradeRequestMsg.java
 create mode 100644 src/engine/net/client/msg/TrainMsg.java
 create mode 100644 src/engine/net/client/msg/TrainerInfoMsg.java
 create mode 100644 src/engine/net/client/msg/TransferAssetMsg.java
 create mode 100644 src/engine/net/client/msg/TransferBuildingMsg.java
 create mode 100644 src/engine/net/client/msg/TransferGoldFromInventoryToVaultMsg.java
 create mode 100644 src/engine/net/client/msg/TransferGoldFromVaultToInventoryMsg.java
 create mode 100644 src/engine/net/client/msg/TransferGoldToFromBuildingMsg.java
 create mode 100644 src/engine/net/client/msg/TransferItemFromBankToInventoryMsg.java
 create mode 100644 src/engine/net/client/msg/TransferItemFromEquipToInventoryMsg.java
 create mode 100644 src/engine/net/client/msg/TransferItemFromInventoryToBankMsg.java
 create mode 100644 src/engine/net/client/msg/TransferItemFromInventoryToEquipMsg.java
 create mode 100644 src/engine/net/client/msg/TransferItemFromInventoryToVaultMsg.java
 create mode 100644 src/engine/net/client/msg/TransferItemFromVaultToInventoryMsg.java
 create mode 100644 src/engine/net/client/msg/UncommitToTradeMsg.java
 create mode 100644 src/engine/net/client/msg/Unknown1Msg.java
 create mode 100644 src/engine/net/client/msg/UnknownMsg.java
 create mode 100644 src/engine/net/client/msg/UnloadObjectsMsg.java
 create mode 100644 src/engine/net/client/msg/UpdateCharOrMobMessage.java
 create mode 100644 src/engine/net/client/msg/UpdateClientAlliancesMsg.java
 create mode 100644 src/engine/net/client/msg/UpdateEffectsMsg.java
 create mode 100644 src/engine/net/client/msg/UpdateFriendStatusMessage.java
 create mode 100644 src/engine/net/client/msg/UpdateGoldMsg.java
 create mode 100644 src/engine/net/client/msg/UpdateInventoryMsg.java
 create mode 100644 src/engine/net/client/msg/UpdateObjectMsg.java
 create mode 100644 src/engine/net/client/msg/UpdateStateMsg.java
 create mode 100644 src/engine/net/client/msg/UpdateTradeWindowMsg.java
 create mode 100644 src/engine/net/client/msg/UpdateVaultMsg.java
 create mode 100644 src/engine/net/client/msg/UpgradeAssetMessage.java
 create mode 100644 src/engine/net/client/msg/UseCharterMsg.java
 create mode 100644 src/engine/net/client/msg/VendorDialogMsg.java
 create mode 100644 src/engine/net/client/msg/ViewResourcesMessage.java
 create mode 100644 src/engine/net/client/msg/VisualUpdateMessage.java
 create mode 100644 src/engine/net/client/msg/WhoRequestMsg.java
 create mode 100644 src/engine/net/client/msg/WhoResponseMsg.java
 create mode 100644 src/engine/net/client/msg/WorldDataMsg.java
 create mode 100644 src/engine/net/client/msg/WorldObjectMsg.java
 create mode 100644 src/engine/net/client/msg/WorldRealmMsg.java
 create mode 100644 src/engine/net/client/msg/chat/AbstractChatMsg.java
 create mode 100644 src/engine/net/client/msg/chat/ChatCSRMsg.java
 create mode 100644 src/engine/net/client/msg/chat/ChatCityMsg.java
 create mode 100644 src/engine/net/client/msg/chat/ChatGlobalMsg.java
 create mode 100644 src/engine/net/client/msg/chat/ChatGroupMsg.java
 create mode 100644 src/engine/net/client/msg/chat/ChatGuildMsg.java
 create mode 100644 src/engine/net/client/msg/chat/ChatICMsg.java
 create mode 100644 src/engine/net/client/msg/chat/ChatInfoMsg.java
 create mode 100644 src/engine/net/client/msg/chat/ChatLeaderMsg.java
 create mode 100644 src/engine/net/client/msg/chat/ChatPvPMsg.java
 create mode 100644 src/engine/net/client/msg/chat/ChatSayMsg.java
 create mode 100644 src/engine/net/client/msg/chat/ChatShoutMsg.java
 create mode 100644 src/engine/net/client/msg/chat/ChatSystemChannelMsg.java
 create mode 100644 src/engine/net/client/msg/chat/ChatSystemMsg.java
 create mode 100644 src/engine/net/client/msg/chat/ChatTellMsg.java
 create mode 100644 src/engine/net/client/msg/chat/GuildEnterWorldMsg.java
 create mode 100644 src/engine/net/client/msg/commands/ClientAdminCommandMsg.java
 create mode 100644 src/engine/net/client/msg/group/AppointGroupLeaderMsg.java
 create mode 100644 src/engine/net/client/msg/group/DisbandGroupMsg.java
 create mode 100644 src/engine/net/client/msg/group/FormationFollowMsg.java
 create mode 100644 src/engine/net/client/msg/group/GroupInviteMsg.java
 create mode 100644 src/engine/net/client/msg/group/GroupInviteResponseMsg.java
 create mode 100644 src/engine/net/client/msg/group/GroupUpdateMsg.java
 create mode 100644 src/engine/net/client/msg/group/LeaveGroupMsg.java
 create mode 100644 src/engine/net/client/msg/group/RemoveFromGroupMsg.java
 create mode 100644 src/engine/net/client/msg/group/ToggleGroupSplitMsg.java
 create mode 100644 src/engine/net/client/msg/guild/AcceptInviteToGuildMsg.java
 create mode 100644 src/engine/net/client/msg/guild/AcceptSubInviteMsg.java
 create mode 100644 src/engine/net/client/msg/guild/BanishUnbanishMsg.java
 create mode 100644 src/engine/net/client/msg/guild/BreakFealtyMsg.java
 create mode 100644 src/engine/net/client/msg/guild/ChangeRankMsg.java
 create mode 100644 src/engine/net/client/msg/guild/DisbandGuildMsg.java
 create mode 100644 src/engine/net/client/msg/guild/DismissGuildMsg.java
 create mode 100644 src/engine/net/client/msg/guild/GuildControlMsg.java
 create mode 100644 src/engine/net/client/msg/guild/GuildCreationCloseMsg.java
 create mode 100644 src/engine/net/client/msg/guild/GuildCreationFinalizeMsg.java
 create mode 100644 src/engine/net/client/msg/guild/GuildCreationOptionsMsg.java
 create mode 100644 src/engine/net/client/msg/guild/GuildInfoMsg.java
 create mode 100644 src/engine/net/client/msg/guild/GuildListMsg.java
 create mode 100644 src/engine/net/client/msg/guild/GuildUnknownMsg.java
 create mode 100644 src/engine/net/client/msg/guild/InviteToGuildMsg.java
 create mode 100644 src/engine/net/client/msg/guild/InviteToSubMsg.java
 create mode 100644 src/engine/net/client/msg/guild/LeaveGuildMsg.java
 create mode 100644 src/engine/net/client/msg/guild/MOTDCommitMsg.java
 create mode 100644 src/engine/net/client/msg/guild/MOTDMsg.java
 create mode 100644 src/engine/net/client/msg/guild/ReqGuildListMsg.java
 create mode 100644 src/engine/net/client/msg/guild/SendGuildEntryMsg.java
 create mode 100644 src/engine/net/client/msg/guild/SwearInGuildMsg.java
 create mode 100644 src/engine/net/client/msg/guild/SwearInMsg.java
 create mode 100644 src/engine/net/client/msg/login/CharSelectScreenMsg.java
 create mode 100644 src/engine/net/client/msg/login/ClientLoginInfoMsg.java
 create mode 100644 src/engine/net/client/msg/login/CommitNewCharacterMsg.java
 create mode 100644 src/engine/net/client/msg/login/DeleteCharacterMsg.java
 create mode 100644 src/engine/net/client/msg/login/GameServerIPRequestMsg.java
 create mode 100644 src/engine/net/client/msg/login/GameServerIPResponseMsg.java
 create mode 100644 src/engine/net/client/msg/login/InvalidNameMsg.java
 create mode 100644 src/engine/net/client/msg/login/LoginErrorMsg.java
 create mode 100644 src/engine/net/client/msg/login/ServerStatusMsg.java
 create mode 100644 src/engine/net/client/msg/login/VersionInfoMsg.java
 create mode 100644 src/engine/objects/AbstractCharacter.java
 create mode 100644 src/engine/objects/AbstractGameObject.java
 create mode 100644 src/engine/objects/AbstractIntelligenceAgent.java
 create mode 100644 src/engine/objects/AbstractWorldObject.java
 create mode 100644 src/engine/objects/Account.java
 create mode 100644 src/engine/objects/Bane.java
 create mode 100644 src/engine/objects/BaseClass.java
 create mode 100644 src/engine/objects/Blueprint.java
 create mode 100644 src/engine/objects/Boon.java
 create mode 100644 src/engine/objects/Building.java
 create mode 100644 src/engine/objects/BuildingFriends.java
 create mode 100644 src/engine/objects/BuildingLocation.java
 create mode 100644 src/engine/objects/BuildingModelBase.java
 create mode 100644 src/engine/objects/BuildingRegions.java
 create mode 100644 src/engine/objects/CharacterItemManager.java
 create mode 100644 src/engine/objects/CharacterPower.java
 create mode 100644 src/engine/objects/CharacterRune.java
 create mode 100644 src/engine/objects/CharacterSkill.java
 create mode 100644 src/engine/objects/CharacterTitle.java
 create mode 100644 src/engine/objects/City.java
 create mode 100644 src/engine/objects/Colliders.java
 create mode 100644 src/engine/objects/Condemned.java
 create mode 100644 src/engine/objects/Contract.java
 create mode 100644 src/engine/objects/Corpse.java
 create mode 100644 src/engine/objects/Effect.java
 create mode 100644 src/engine/objects/EffectsResourceCosts.java
 create mode 100644 src/engine/objects/EnchantmentBase.java
 create mode 100644 src/engine/objects/EquipmentSetEntry.java
 create mode 100644 src/engine/objects/Experience.java
 create mode 100644 src/engine/objects/Formation.java
 create mode 100644 src/engine/objects/Group.java
 create mode 100644 src/engine/objects/Guild.java
 create mode 100644 src/engine/objects/GuildAlliances.java
 create mode 100644 src/engine/objects/GuildCondemn.java
 create mode 100644 src/engine/objects/GuildHistory.java
 create mode 100644 src/engine/objects/GuildStatusController.java
 create mode 100644 src/engine/objects/GuildTag.java
 create mode 100644 src/engine/objects/Heraldry.java
 create mode 100644 src/engine/objects/Item.java
 create mode 100644 src/engine/objects/ItemBase.java
 create mode 100644 src/engine/objects/ItemContainer.java
 create mode 100644 src/engine/objects/ItemFactory.java
 create mode 100644 src/engine/objects/Kit.java
 create mode 100644 src/engine/objects/LevelDefault.java
 create mode 100644 src/engine/objects/LootRow.java
 create mode 100644 src/engine/objects/LootTable.java
 create mode 100644 src/engine/objects/MaxSkills.java
 create mode 100644 src/engine/objects/MenuOption.java
 create mode 100644 src/engine/objects/MeshBounds.java
 create mode 100644 src/engine/objects/Mine.java
 create mode 100644 src/engine/objects/MineProduction.java
 create mode 100644 src/engine/objects/Mob.java
 create mode 100644 src/engine/objects/MobBase.java
 create mode 100644 src/engine/objects/MobBaseEffects.java
 create mode 100644 src/engine/objects/MobBaseStats.java
 create mode 100644 src/engine/objects/MobEquipment.java
 create mode 100644 src/engine/objects/MobLoot.java
 create mode 100644 src/engine/objects/MobLootBase.java
 create mode 100644 src/engine/objects/MobbaseGoldEntry.java
 create mode 100644 src/engine/objects/NPC.java
 create mode 100644 src/engine/objects/NPCProfits.java
 create mode 100644 src/engine/objects/NPCRune.java
 create mode 100644 src/engine/objects/Nation.java
 create mode 100644 src/engine/objects/PlayerBonuses.java
 create mode 100644 src/engine/objects/PlayerCharacter.java
 create mode 100644 src/engine/objects/PlayerFriends.java
 create mode 100644 src/engine/objects/Portal.java
 create mode 100644 src/engine/objects/PowerGrant.java
 create mode 100644 src/engine/objects/PowerReq.java
 create mode 100644 src/engine/objects/PowersBaseAttribute.java
 create mode 100644 src/engine/objects/PreparedStatementShared.java
 create mode 100644 src/engine/objects/ProducedItem.java
 create mode 100644 src/engine/objects/PromotionClass.java
 create mode 100644 src/engine/objects/Race.java
 create mode 100644 src/engine/objects/Realm.java
 create mode 100644 src/engine/objects/Regions.java
 create mode 100644 src/engine/objects/Resists.java
 create mode 100644 src/engine/objects/Resource.java
 create mode 100644 src/engine/objects/RuneBase.java
 create mode 100644 src/engine/objects/RuneBaseAttribute.java
 create mode 100644 src/engine/objects/RuneBaseEffect.java
 create mode 100644 src/engine/objects/Runegate.java
 create mode 100644 src/engine/objects/Shrine.java
 create mode 100644 src/engine/objects/SkillReq.java
 create mode 100644 src/engine/objects/SkillsBase.java
 create mode 100644 src/engine/objects/SkillsBaseAttribute.java
 create mode 100644 src/engine/objects/SpecialLoot.java
 create mode 100644 src/engine/objects/StaticColliders.java
 create mode 100644 src/engine/objects/Transaction.java
 create mode 100644 src/engine/objects/VendorDialog.java
 create mode 100644 src/engine/objects/Warehouse.java
 create mode 100644 src/engine/objects/Zone.java
 create mode 100644 src/engine/pooling/ByteBufferPool.java
 create mode 100644 src/engine/pooling/ConnectionPool.java
 create mode 100644 src/engine/pooling/LinkedObjectPool.java
 create mode 100644 src/engine/pooling/MultisizeByteBufferPool.java
 create mode 100644 src/engine/pooling/ObjectPool.java
 create mode 100644 src/engine/pooling/Poolable.java
 create mode 100644 src/engine/powers/ActionsBase.java
 create mode 100644 src/engine/powers/DamageShield.java
 create mode 100644 src/engine/powers/EffectsBase.java
 create mode 100644 src/engine/powers/FailCondition.java
 create mode 100644 src/engine/powers/PowerPrereq.java
 create mode 100644 src/engine/powers/PowersBase.java
 create mode 100644 src/engine/powers/RangeBasedAwo.java
 create mode 100644 src/engine/powers/effectmodifiers/AbstractEffectModifier.java
 create mode 100644 src/engine/powers/effectmodifiers/AdjustAboveDmgCapEffectModifier.java
 create mode 100644 src/engine/powers/effectmodifiers/AmbidexterityEffectModifier.java
 create mode 100644 src/engine/powers/effectmodifiers/ArmorPiercingEffectModifier.java
 create mode 100644 src/engine/powers/effectmodifiers/AttackDelayEffectModifier.java
 create mode 100644 src/engine/powers/effectmodifiers/AttributeEffectModifier.java
 create mode 100644 src/engine/powers/effectmodifiers/BlackMantleEffectModifier.java
 create mode 100644 src/engine/powers/effectmodifiers/BladeTrailsEffectModifier.java
 create mode 100644 src/engine/powers/effectmodifiers/BlockEffectModifier.java
 create mode 100644 src/engine/powers/effectmodifiers/BlockedPowerTypeEffectModifier.java
 create mode 100644 src/engine/powers/effectmodifiers/CannotAttackEffectModifier.java
 create mode 100644 src/engine/powers/effectmodifiers/CannotCastEffectModifier.java
 create mode 100644 src/engine/powers/effectmodifiers/CannotMoveEffectModifier.java
 create mode 100644 src/engine/powers/effectmodifiers/CannotTrackEffectModifier.java
 create mode 100644 src/engine/powers/effectmodifiers/CharmedEffectModifier.java
 create mode 100644 src/engine/powers/effectmodifiers/ConstrainedAmbidexterityEffectModifier.java
 create mode 100644 src/engine/powers/effectmodifiers/DCVEffectModifier.java
 create mode 100644 src/engine/powers/effectmodifiers/DREffectModifier.java
 create mode 100644 src/engine/powers/effectmodifiers/DamageCapEffectModifier.java
 create mode 100644 src/engine/powers/effectmodifiers/DamageShieldEffectModifier.java
 create mode 100644 src/engine/powers/effectmodifiers/DodgeEffectModifier.java
 create mode 100644 src/engine/powers/effectmodifiers/DurabilityEffectModifier.java
 create mode 100644 src/engine/powers/effectmodifiers/ExclusiveDamageCapEffectModifier.java
 create mode 100644 src/engine/powers/effectmodifiers/FadeEffectModifier.java
 create mode 100644 src/engine/powers/effectmodifiers/FlyEffectModifier.java
 create mode 100644 src/engine/powers/effectmodifiers/HealthEffectModifier.java
 create mode 100644 src/engine/powers/effectmodifiers/HealthFullEffectModifier.java
 create mode 100644 src/engine/powers/effectmodifiers/HealthRecoverRateEffectModifier.java
 create mode 100644 src/engine/powers/effectmodifiers/IgnoreDamageCapEffectModifier.java
 create mode 100644 src/engine/powers/effectmodifiers/IgnorePassiveDefenseEffectModifier.java
 create mode 100644 src/engine/powers/effectmodifiers/ImmuneToAttackEffectModifier.java
 create mode 100644 src/engine/powers/effectmodifiers/ImmuneToEffectModifier.java
 create mode 100644 src/engine/powers/effectmodifiers/ImmuneToPowersEffectModifier.java
 create mode 100644 src/engine/powers/effectmodifiers/InvisibleEffectModifier.java
 create mode 100644 src/engine/powers/effectmodifiers/ItemNameEffectModifier.java
 create mode 100644 src/engine/powers/effectmodifiers/ManaEffectModifier.java
 create mode 100644 src/engine/powers/effectmodifiers/ManaFullEffectModifier.java
 create mode 100644 src/engine/powers/effectmodifiers/ManaRecoverRateEffectModifier.java
 create mode 100644 src/engine/powers/effectmodifiers/MaxDamageEffectModifier.java
 create mode 100644 src/engine/powers/effectmodifiers/MeleeDamageEffectModifier.java
 create mode 100644 src/engine/powers/effectmodifiers/MinDamageEffectModifier.java
 create mode 100644 src/engine/powers/effectmodifiers/NoModEffectModifier.java
 create mode 100644 src/engine/powers/effectmodifiers/OCVEffectModifier.java
 create mode 100644 src/engine/powers/effectmodifiers/ParryEffectModifier.java
 create mode 100644 src/engine/powers/effectmodifiers/PassiveDefenseEffectModifier.java
 create mode 100644 src/engine/powers/effectmodifiers/PowerCostEffectModifier.java
 create mode 100644 src/engine/powers/effectmodifiers/PowerCostHealthEffectModifier.java
 create mode 100644 src/engine/powers/effectmodifiers/PowerDamageEffectModifier.java
 create mode 100644 src/engine/powers/effectmodifiers/ProtectionFromEffectModifier.java
 create mode 100644 src/engine/powers/effectmodifiers/ResistanceEffectModifier.java
 create mode 100644 src/engine/powers/effectmodifiers/ScaleHeightEffectModifier.java
 create mode 100644 src/engine/powers/effectmodifiers/ScaleWidthEffectModifier.java
 create mode 100644 src/engine/powers/effectmodifiers/ScanRangeEffectModifier.java
 create mode 100644 src/engine/powers/effectmodifiers/SeeInvisibleEffectModifier.java
 create mode 100644 src/engine/powers/effectmodifiers/SilencedEffectModifier.java
 create mode 100644 src/engine/powers/effectmodifiers/SkillEffectModifier.java
 create mode 100644 src/engine/powers/effectmodifiers/SlayEffectModifier.java
 create mode 100644 src/engine/powers/effectmodifiers/SpeedEffectModifier.java
 create mode 100644 src/engine/powers/effectmodifiers/SpireBlockEffectModifier.java
 create mode 100644 src/engine/powers/effectmodifiers/StaminaEffectModifier.java
 create mode 100644 src/engine/powers/effectmodifiers/StaminaFullEffectModifier.java
 create mode 100644 src/engine/powers/effectmodifiers/StaminaRecoverRateEffectModifier.java
 create mode 100644 src/engine/powers/effectmodifiers/StunnedEffectModifier.java
 create mode 100644 src/engine/powers/effectmodifiers/ValueEffectModifier.java
 create mode 100644 src/engine/powers/effectmodifiers/WeaponProcEffectModifier.java
 create mode 100644 src/engine/powers/effectmodifiers/WeaponRangeEffectModifier.java
 create mode 100644 src/engine/powers/effectmodifiers/WeaponSpeedEffectModifier.java
 create mode 100644 src/engine/powers/poweractions/AbstractPowerAction.java
 create mode 100644 src/engine/powers/poweractions/ApplyEffectPowerAction.java
 create mode 100644 src/engine/powers/poweractions/ApplyEffectsPowerAction.java
 create mode 100644 src/engine/powers/poweractions/BlockPowerAction.java
 create mode 100644 src/engine/powers/poweractions/CharmPowerAction.java
 create mode 100644 src/engine/powers/poweractions/ClaimMinePowerAction.java
 create mode 100644 src/engine/powers/poweractions/ClearAggroPowerAction.java
 create mode 100644 src/engine/powers/poweractions/ClearNearbyAggroPowerAction.java
 create mode 100644 src/engine/powers/poweractions/ConfusionPowerAction.java
 create mode 100644 src/engine/powers/poweractions/CreateMobPowerAction.java
 create mode 100644 src/engine/powers/poweractions/DamageOverTimePowerAction.java
 create mode 100644 src/engine/powers/poweractions/DeferredPowerPowerAction.java
 create mode 100644 src/engine/powers/poweractions/DirectDamagePowerAction.java
 create mode 100644 src/engine/powers/poweractions/FearPowerAction.java
 create mode 100644 src/engine/powers/poweractions/InvisPowerAction.java
 create mode 100644 src/engine/powers/poweractions/MobRecallPowerAction.java
 create mode 100644 src/engine/powers/poweractions/OpenGatePowerAction.java
 create mode 100644 src/engine/powers/poweractions/PeekPowerAction.java
 create mode 100644 src/engine/powers/poweractions/RecallPowerAction.java
 create mode 100644 src/engine/powers/poweractions/RemoveEffectPowerAction.java
 create mode 100644 src/engine/powers/poweractions/ResurrectPowerAction.java
 create mode 100644 src/engine/powers/poweractions/RunegateTeleportPowerAction.java
 create mode 100644 src/engine/powers/poweractions/SetItemFlagPowerAction.java
 create mode 100644 src/engine/powers/poweractions/SimpleDamagePowerAction.java
 create mode 100644 src/engine/powers/poweractions/SpireDisablePowerAction.java
 create mode 100644 src/engine/powers/poweractions/StealPowerAction.java
 create mode 100644 src/engine/powers/poweractions/SummonPowerAction.java
 create mode 100644 src/engine/powers/poweractions/TeleportPowerAction.java
 create mode 100644 src/engine/powers/poweractions/TrackPowerAction.java
 create mode 100644 src/engine/powers/poweractions/TransferStatOTPowerAction.java
 create mode 100644 src/engine/powers/poweractions/TransferStatPowerAction.java
 create mode 100644 src/engine/powers/poweractions/TransformPowerAction.java
 create mode 100644 src/engine/powers/poweractions/TreeChokePowerAction.java
 create mode 100644 src/engine/server/MBServerStatics.java
 create mode 100644 src/engine/server/login/LoginServer.java
 create mode 100644 src/engine/server/login/LoginServerMsgHandler.java
 create mode 100644 src/engine/server/world/WorldServer.java
 create mode 100644 src/engine/session/CSSession.java
 create mode 100644 src/engine/session/Session.java
 create mode 100644 src/engine/session/SessionID.java
 create mode 100644 src/engine/util/ByteAnalyzer.java
 create mode 100644 src/engine/util/ByteBufferUtils.java
 create mode 100644 src/engine/util/ByteUtils.java
 create mode 100644 src/engine/util/Hasher.java
 create mode 100644 src/engine/util/MapLoader.java
 create mode 100644 src/engine/util/MiscUtils.java
 create mode 100644 src/engine/util/StringUtils.java
 create mode 100644 src/engine/util/ThreadUtils.java
 create mode 100644 src/engine/workthreads/DestroyCityThread.java
 create mode 100644 src/engine/workthreads/DisconnectTrashTask.java
 create mode 100644 src/engine/workthreads/HourlyJobThread.java
 create mode 100644 src/engine/workthreads/PurgeOprhans.java
 create mode 100644 src/engine/workthreads/TransferCityThread.java
 create mode 100644 src/engine/workthreads/WarehousePushThread.java

diff --git a/src/discord/ChatChannel.java b/src/discord/ChatChannel.java
new file mode 100644
index 00000000..8c2222ec
--- /dev/null
+++ b/src/discord/ChatChannel.java
@@ -0,0 +1,41 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+package discord;
+
+import engine.gameManager.ConfigManager;
+import net.dv8tion.jda.api.entities.TextChannel;
+
+public enum ChatChannel {
+
+    ANNOUNCE("MB_MAGICBOT_ANNOUNCE"),
+    SEPTIC("MB_MAGICBOT_SEPTIC"),
+    CHANGELOG("MB_MAGICBOT_ANNOUNCE"),
+    POLITICAL("MB_MAGICBOT_POLITICAL"),
+    GENERAL("MB_MAGICBOT_GENERAL"),
+    FORTOFIX("MB_MAGICBOT_FORTOFIX"),
+    RECRUIT("MB_MAGICBOT_RECRUIT");
+
+    public final String configName;
+    public  long channelID;
+    public TextChannel textChannel;
+
+    ChatChannel(String configName) {
+        this.configName = configName;
+    }
+
+    // Create text channel objects we will use
+
+    public static void Init() {
+
+        for (ChatChannel chatChannel : ChatChannel.values()) {
+            chatChannel.channelID = Long.parseLong(ConfigManager.valueOf(chatChannel.configName).getValue());
+            chatChannel.textChannel = MagicBot.jda.getTextChannelById(chatChannel.channelID);
+        }
+    }
+}
diff --git a/src/discord/Database.java b/src/discord/Database.java
new file mode 100644
index 00000000..66fb6341
--- /dev/null
+++ b/src/discord/Database.java
@@ -0,0 +1,380 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package discord;
+
+import engine.Enum;
+import engine.gameManager.ConfigManager;
+import org.pmw.tinylog.Logger;
+
+import java.sql.*;
+import java.util.ArrayList;
+import java.util.List;
+
+public class Database {
+
+    public String sqlURI;
+    public static Boolean online;
+
+    // Load and instance the JDBC Driver
+
+    static {
+        try {
+            Class.forName("com.mysql.cj.jdbc.Driver").newInstance();
+        } catch (InstantiationException | ClassNotFoundException | IllegalAccessException e) {
+            // TODO Auto-generated catch block
+            Logger.error(e.toString());
+            ;
+            online = false;
+        }
+    }
+
+    public void configureDatabase() {
+
+        // Build connection string from JSON object.
+
+        sqlURI = "jdbc:mysql://";
+        sqlURI += ConfigManager.MB_DATABASE_ADDRESS.getValue() + ':' + ConfigManager.MB_DATABASE_PORT.getValue();
+        sqlURI += '/' + (String) ConfigManager.MB_DATABASE_NAME.getValue() + '?';
+        sqlURI += "useServerPrepStmts=true";
+        sqlURI += "&cachePrepStmts=false";
+        sqlURI += "&cacheCallableStmts=true";
+        sqlURI += "&characterEncoding=utf8";
+
+        online = true;
+    }
+
+    public boolean updateAccountPassword(String discordAccountID, String newPassword) {
+
+        try (Connection connection = DriverManager.getConnection(sqlURI, ConfigManager.MB_DATABASE_USER.getValue(),
+                ConfigManager.MB_DATABASE_PASS.getValue())) {
+
+            CallableStatement updatePassword = connection.prepareCall("call discordUpdatePassword(?, ?)");
+
+            updatePassword.setString(1, discordAccountID);
+            updatePassword.setString(2, newPassword);
+
+            updatePassword.executeUpdate();
+            updatePassword.close();
+            return true;
+
+        } catch (SQLException e) {
+            Logger.error(e.toString());
+            ;
+            this.online = false;
+            return false;
+        }
+    }
+
+    public boolean updateAccountStatus(String discordAccountID, Enum.AccountStatus accountStatus) {
+
+        try (Connection connection = DriverManager.getConnection(sqlURI, ConfigManager.MB_DATABASE_USER.getValue(),
+                ConfigManager.MB_DATABASE_PASS.getValue())) {
+
+            PreparedStatement updateAccountStatus = connection.prepareCall("update obj_account set `status` = ? where `discordAccount` = ?");
+
+            updateAccountStatus.setString(1, accountStatus.name());
+            updateAccountStatus.setString(2, discordAccountID);
+
+            updateAccountStatus.executeUpdate();
+            updateAccountStatus.close();
+            return true;
+
+        } catch (SQLException e) {
+            Logger.error(e.toString());
+            ;
+            this.online = false;
+            return false;
+        }
+    }
+
+    public boolean registerDiscordAccount(String discordAccountID, String discordUserName, String discordPassword) {
+
+        try (Connection connection = DriverManager.getConnection(sqlURI, ConfigManager.MB_DATABASE_USER.getValue(),
+                ConfigManager.MB_DATABASE_PASS.getValue())) {
+
+            CallableStatement registerAccount = connection.prepareCall("call discordAccountRegister(?, ?, ?)");
+
+            registerAccount.setString(1, discordAccountID);
+            registerAccount.setString(2, discordUserName);
+            registerAccount.setString(3, discordPassword);
+
+            registerAccount.execute();
+            registerAccount.close();
+            return true;
+
+        } catch (SQLException e) {
+            Logger.error(e.toString());
+            this.online = false;
+            return false;
+        }
+    }
+
+    public List<DiscordAccount> getDiscordAccounts(String discordAccountID) {
+
+        DiscordAccount discordAccount;
+        List<DiscordAccount> discordAccounts = new ArrayList<>();
+
+        String queryString = "SELECT * FROM obj_account where discordAccount = ?";
+
+        try (Connection connection = DriverManager.getConnection(sqlURI, ConfigManager.MB_DATABASE_USER.getValue(),
+                ConfigManager.MB_DATABASE_PASS.getValue())) {
+
+            // Discord account name based lookup
+
+            PreparedStatement accountQuery = connection.prepareStatement(queryString);
+            accountQuery.setString(1, discordAccountID);
+
+            ResultSet rs = accountQuery.executeQuery();
+
+            while (rs.next()) {
+                discordAccount = new DiscordAccount();
+                discordAccount.discordAccount = rs.getString("discordAccount");
+                discordAccount.gameAccountName = rs.getString("acct_uname");
+                discordAccount.status = Enum.AccountStatus.valueOf(rs.getString("status"));
+                discordAccount.isDiscordAdmin = rs.getByte("discordAdmin");                // Registration date cannot be null
+
+                Timestamp registrationDate = rs.getTimestamp("registrationDate");
+                discordAccount.registrationDate = registrationDate.toLocalDateTime();
+
+                // Load last Update Request datetime
+
+                Timestamp lastUpdateRequest = rs.getTimestamp("lastUpdateRequest");
+
+                if (lastUpdateRequest != null)
+                    discordAccount.lastUpdateRequest = lastUpdateRequest.toLocalDateTime();
+                else
+                    discordAccount.lastUpdateRequest = null;
+
+                discordAccounts.add(discordAccount);
+            }
+
+        } catch (SQLException e) {
+            Logger.error(e.toString());
+            this.online = false;
+        }
+
+        return discordAccounts;
+    }
+
+    public String getTrashDetail() {
+
+        String outString = "accountName characterName machineID ip count\n";
+        outString += "---------------------------------------------\n";
+        String queryString = "SELECT * FROM dyn_trash_detail;";
+
+        try (Connection connection = DriverManager.getConnection(sqlURI, ConfigManager.MB_DATABASE_USER.getValue(),
+                ConfigManager.MB_DATABASE_PASS.getValue())) {
+
+            // Discord account name based lookup
+
+            PreparedStatement trashQuery = connection.prepareStatement(queryString);
+
+            ResultSet rs = trashQuery.executeQuery();
+
+            while (rs.next()) {
+                outString += rs.getString("accountName") + "   ";
+                outString += rs.getString("characterName") + "   ";
+                outString += rs.getString("machineID") + "   ";
+                outString += rs.getString("ip") + "   ";
+                outString += rs.getInt("count") + "\n";
+            }
+        } catch (SQLException e) {
+            Logger.error(e.toString());
+
+            this.online = false;
+        }
+        return outString;
+    }
+
+    public String getTrashList() {
+
+        String outString = "";
+        String queryString = "SELECT DISTINCT `characterName` FROM dyn_trash_detail;";
+        int counter = 0;
+
+        try (Connection connection = DriverManager.getConnection(sqlURI, ConfigManager.MB_DATABASE_USER.getValue(),
+                ConfigManager.MB_DATABASE_PASS.getValue())) {
+
+            // Discord account name based lookup
+
+            PreparedStatement trashQuery = connection.prepareStatement(queryString);
+
+            ResultSet rs = trashQuery.executeQuery();
+
+            while (rs.next()) {
+                outString +=  rs.getString("characterName");
+                counter++;
+
+                if (counter > 2) {
+                    outString += "\n";
+                    counter = 0; }
+                else
+                    outString += "     ";
+
+            }
+        } catch (SQLException e) {
+            Logger.error(e.toString());
+
+            this.online = false;
+        }
+
+        if (outString.length() > 1500)
+        return outString.substring(0, 1500);
+         else
+        return outString;
+    }
+    public int getTrashCount() {
+
+        int trashCount = 0;
+
+        String queryString = "SELECT count(distinct characterName) FROM dyn_trash_detail;";
+
+        try (Connection connection = DriverManager.getConnection(sqlURI, ConfigManager.MB_DATABASE_USER.getValue(),
+                ConfigManager.MB_DATABASE_PASS.getValue())) {
+
+            // Discord account name based lookup
+
+            PreparedStatement trashQuery = connection.prepareStatement(queryString);
+
+            ResultSet rs = trashQuery.executeQuery();
+
+            while (rs.next()) {
+                trashCount = rs.getInt(1);
+            }
+        } catch (SQLException e) {
+            Logger.error(e.toString());
+
+            this.online = false;
+        }
+
+        return trashCount;
+    }
+
+        public String getTrashFile() {
+
+        String outString = "machineID : count\n";
+        String queryString = "SELECT * FROM dyn_trash;";
+
+        try (Connection connection = DriverManager.getConnection(sqlURI, ConfigManager.MB_DATABASE_USER.getValue(),
+                ConfigManager.MB_DATABASE_PASS.getValue())) {
+
+            // Discord account name based lookup
+
+            PreparedStatement trashQuery = connection.prepareStatement(queryString);
+
+            ResultSet rs = trashQuery.executeQuery();
+
+            while (rs.next()) {
+                outString += rs.getString("machineID") + " : ";
+                outString += rs.getInt("count") + "\n";
+            }
+        } catch (SQLException e) {
+            Logger.error(e.toString());
+
+            this.online = false;
+        }
+        return outString;
+    }
+
+    public List<DiscordAccount> getAccountsByDiscordName(String accountName, Boolean exact) {
+
+        DiscordAccount discordAccount;
+        List<DiscordAccount> discordAccounts = new ArrayList<>();
+        String searchString;
+        String queryString;
+
+        if (exact.equals(true))
+            searchString = accountName + "#%";
+        else
+            searchString = accountName + "%#%";
+
+        queryString = "SELECT * FROM obj_account where `acct_uname` LIKE ?";
+
+        try (Connection connection = DriverManager.getConnection(sqlURI, ConfigManager.MB_DATABASE_USER.getValue(),
+                ConfigManager.MB_DATABASE_PASS.getValue())) {
+
+            // Discord account name based lookup
+
+            PreparedStatement nameQuery = connection.prepareStatement(queryString);
+            nameQuery.setString(1, searchString);
+
+            ResultSet rs = nameQuery.executeQuery();
+
+            while (rs.next()) {
+                discordAccount = new DiscordAccount();
+                discordAccount.discordAccount = rs.getString("discordAccount");
+                discordAccount.gameAccountName = rs.getString("acct_uname");
+                discordAccount.status = Enum.AccountStatus.valueOf(rs.getString("status"));
+
+                // Registration date cannot be null
+
+                Timestamp registrationDate = rs.getTimestamp("registrationDate");
+                discordAccount.registrationDate = registrationDate.toLocalDateTime();
+
+                // Load last Update Request datetime
+
+                Timestamp lastUpdateRequest = rs.getTimestamp("lastUpdateRequest");
+
+                if (lastUpdateRequest != null)
+                    discordAccount.lastUpdateRequest = lastUpdateRequest.toLocalDateTime();
+                else
+                    discordAccount.lastUpdateRequest = null;
+
+                discordAccounts.add(discordAccount);
+            }
+
+        } catch (SQLException e) {
+            Logger.error(e.toString());
+            ;
+            this.online = false;
+        }
+
+        return discordAccounts;
+    }
+
+    public String getPopulationSTring() {
+
+        String popString = "";
+
+        try (Connection connection = DriverManager.getConnection(sqlURI, ConfigManager.MB_DATABASE_USER.getValue(),
+                ConfigManager.MB_DATABASE_PASS.getValue())) {
+
+            // Discord account name based lookup
+            CallableStatement getPopString = connection.prepareCall("CALL GET_POPULATION_STRING()");
+            ResultSet rs = getPopString.executeQuery();
+
+            if (rs.next())
+                popString = rs.getString("popstring");
+
+        } catch (SQLException e) {
+            Logger.error(e.toString());
+            this.online = false;
+        }
+
+        return popString;
+    }
+
+    public void invalidateLoginCache(String discordAccountID) {
+
+        try (Connection connection = DriverManager.getConnection(sqlURI, ConfigManager.MB_DATABASE_USER.getValue(),
+                ConfigManager.MB_DATABASE_PASS.getValue())) {
+
+            String queryString = "INSERT IGNORE INTO login_cachelist (`UID`) SELECT `UID` from `obj_account` WHERE `discordAccount` = ?";
+
+            PreparedStatement invalidateAccounts = connection.prepareStatement(queryString);
+            invalidateAccounts.setString(1, discordAccountID);
+            invalidateAccounts.executeUpdate();
+
+        } catch (SQLException e) {
+            Logger.error(e.toString());
+            this.online = false;
+        }
+    }
+}
diff --git a/src/discord/DiscordAccount.java b/src/discord/DiscordAccount.java
new file mode 100644
index 00000000..0b3d6b77
--- /dev/null
+++ b/src/discord/DiscordAccount.java
@@ -0,0 +1,25 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+package discord;
+import engine.Enum;
+
+import java.time.LocalDateTime;
+
+public class DiscordAccount {
+    public String discordAccount;
+    public String gameAccountName;
+    public Enum.AccountStatus status;
+    public LocalDateTime registrationDate;
+    public LocalDateTime lastUpdateRequest;
+    public byte isDiscordAdmin;
+    public DiscordAccount() {
+
+    }
+
+}
diff --git a/src/discord/MagicBot.java b/src/discord/MagicBot.java
new file mode 100644
index 00000000..6e1424d4
--- /dev/null
+++ b/src/discord/MagicBot.java
@@ -0,0 +1,372 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package discord;
+
+import discord.handlers.*;
+import engine.Enum;
+import engine.gameManager.ConfigManager;
+import net.dv8tion.jda.api.JDA;
+import net.dv8tion.jda.api.JDABuilder;
+import net.dv8tion.jda.api.entities.*;
+import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
+import net.dv8tion.jda.api.hooks.ListenerAdapter;
+import net.dv8tion.jda.api.requests.GatewayIntent;
+import net.dv8tion.jda.api.utils.MemberCachePolicy;
+import net.dv8tion.jda.api.utils.cache.CacheFlag;
+import org.pmw.tinylog.Configurator;
+import org.pmw.tinylog.Level;
+import org.pmw.tinylog.Logger;
+import org.pmw.tinylog.labelers.TimestampLabeler;
+import org.pmw.tinylog.policies.StartupPolicy;
+import org.pmw.tinylog.writers.RollingFileWriter;
+
+import javax.security.auth.login.LoginException;
+import java.io.*;
+import java.util.Arrays;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Random;
+import java.util.regex.Pattern;
+
+/*
+*  MagicBot is many things to Magicbane...
+*
+*  -Project Mascot
+*  -Customer service and administration bot
+*  -Benevolent dictator
+*  -Investment manager.
+*
+*  MagicBot will never beg you for money.  He is a very
+*  responsible robot. He was varnished but never garnished.
+*  MagicBot does not for to overclock himself.  His chips
+*  will therefore never overcook.
+*  MagicBot will never be a pitiful robot trying for to use
+*  you as emotional support human.
+*
+*  MagicBot is just not that sort of robot and Magicbane
+*  just isn't that sort of project.
+*
+*  MagicBot runs a Shaodowbane emulator not a Second Life emulator.
+*
+*/
+public class MagicBot extends ListenerAdapter {
+
+    public static JDA jda;
+    public static Database database;
+    public static final Pattern accountNameRegex = Pattern.compile("^[\\p{Alnum}]{6,20}$");
+    public static final Pattern passwordRegex = Pattern.compile("^[\\p{Alnum}]{6,20}$");
+    public static  long discordServerID;
+    public static  long discordRoleID;
+
+    public static Guild magicbaneDiscord;
+    public static Role memberRole;
+    public static TextChannel septicChannel;
+
+
+    public static void main(String[] args) throws LoginException, InterruptedException {
+
+        // Configure tinylogger
+
+        Configurator.defaultConfig()
+                .addWriter(new RollingFileWriter("logs/discord/magicbot.txt", 30, new TimestampLabeler(), new StartupPolicy()))
+                .level(Level.DEBUG)
+                .formatPattern("{level} {date:yyyy-MM-dd HH:mm:ss.SSS} [{thread}] {class}.{method}({line}) : {message}")
+                .activate();
+
+        // Configuration Manager to the front desk
+
+        if (ConfigManager.init() == false) {
+            Logger.error("ABORT! Missing config entry!");
+            return;
+        }
+
+        // Configure Discord essential identifiers
+
+        discordServerID = Long.parseLong(ConfigManager.MB_MAGICBOT_SERVERID.getValue());
+        discordRoleID = Long.parseLong(ConfigManager.MB_MAGICBOT_ROLEID.getValue());
+
+        // Configure and instance the database interface
+
+        database = new Database();
+        database.configureDatabase();
+
+        // Use authentication token issued to MagicBot application to
+        // connect to Discord.  Bot is pre-invited to the Magicbane server.
+
+        // Configure and create JDA discord instance
+
+        JDABuilder jdaBuilder = JDABuilder.create(GatewayIntent.GUILD_MEMBERS, GatewayIntent.DIRECT_MESSAGES)
+                .setToken(ConfigManager.MB_MAGICBOT_BOTTOKEN.getValue())
+                .addEventListeners(new MagicBot())
+                .disableCache(EnumSet.of(CacheFlag.VOICE_STATE, CacheFlag.EMOTE,
+                        CacheFlag.ACTIVITY, CacheFlag.CLIENT_STATUS))
+                .setMemberCachePolicy(MemberCachePolicy.ALL);
+
+        jda = jdaBuilder.build();
+        jda.awaitReady();
+
+        // Cache guild and role values for later usage in #register
+
+        magicbaneDiscord = jda.getGuildById(discordServerID);
+        memberRole = magicbaneDiscord.getRoleById(discordRoleID);
+
+        // Initialize chat channel support
+
+        ChatChannel.Init();
+
+        Logger.info("***MAGICBOT IS RUNNING***");
+    }
+
+    @Override
+    public void onMessageReceived(MessageReceivedEvent event) {
+
+        // Exit if discord is offline
+
+        if (jda.getStatus().equals(JDA.Status.CONNECTED) == false)
+            return;
+
+        // Early exit if message sent to us by another bot or ourselves.
+
+        if (event.getAuthor().isBot()) return;
+
+        // Extract message and origin channel from event
+
+        Message message = event.getMessage();
+
+        // Only private messages
+        MessageChannel channel = event.getMessage().getChannel();
+
+        if (channel.getType().equals(ChannelType.PRIVATE) == false)
+            return;
+
+        // Only real users
+
+        if (event.getAuthor().isBot())
+            return;
+
+        // Only users who have actually joined Magicbane discord.
+
+        if (magicbaneDiscord.isMember(event.getAuthor()) == false)
+            return;
+
+        // getContentRaw() is an atomic getter
+        // getContentDisplay() is a lazy getter which modifies the content
+        // for e.g. console view or logging (strip discord formatting)
+
+        String content = message.getContentRaw();
+        String[] args = content.split(" ");
+        String command = args[0].toLowerCase();
+
+        if (args.length > 1)
+            args = Arrays.copyOfRange(args, 1, args.length);
+        else
+            args = new String[0];
+
+        switch (command) {
+            case "#register":
+                RegisterAccountHandler.handleRequest(event, args);
+                break;
+            case "#help":
+                handleHelpRequest(event);
+                break;
+            case "#account":
+                AccountInfoRequest.handleRequest(event);
+                break;
+            case "#password":
+                PasswordChangeHandler.handleRequest(event, args);
+                break;
+            case "#changelog":
+                ChangeLogHandler.handleRequest(event, args);
+                break;
+            case "#general":
+                GeneralChannelHandler.handleRequest(event, args);
+                break;
+            case "#politics":
+                PoliticalChannelHandler.handleRequest(event, args);
+                break;
+            case "#announce":
+                AnnounceChannelHandler.handleRequest(event, args);
+                break;
+            case "#bug":
+                ForToFixChannelHandler.handleRequest(event, args);
+                break;
+            case "#recruit":
+                RecruitChannelHandler.handleRequest(event, args);
+                break;
+            case "#lookup":
+                LookupRequestHandler.handleRequest(event, args);
+                break;
+            case "#rules":
+                RulesRequestHandler.handleRequest(event);
+                break;
+            case "#status":
+                StatusRequestHandler.handleRequest(event);
+                break;
+            case "#setavail":
+                SetAvailHandler.handleRequest(event, args);
+                break;
+            case "#ban":
+                BanToggleHandler.handleRequest(event, args);
+                break;
+            case "#server":
+                ServerRequestHandler.handleRequest(event, args);
+                break;
+            case "#logs":
+                LogsRequestHandler.handleRequest(event, args);
+                break;
+            case "#flash":
+                FlashHandler.handleRequest(event, args);
+                break;
+            case "#trash":
+                TrashRequestHandler.handleRequest(event, args);
+                break;
+            default:
+                junkbot(command, args);
+                break;
+        }
+    }
+
+    public static void sendResponse(MessageReceivedEvent event, String responseContent) {
+
+        // Send a formatted MagicBot response to a Discord user
+
+        String discordUserName;
+        MessageChannel channel;
+
+        // Exit if discord is offline
+
+        if (jda.getStatus().equals(JDA.Status.CONNECTED) == false)
+            return;
+
+        discordUserName = event.getAuthor().getName();
+        channel = event.getMessage().getChannel();
+
+        channel.sendMessage(
+                "```\n" + "Hello Player " + discordUserName + "\n\n" +
+                        responseContent + "\n\n" +
+                        RobotSpeak.getRobotSpeak() + "\n```").queue();
+    }
+
+    public static boolean isAdminEvent(MessageReceivedEvent event) {
+
+        String discordAccountID = event.getAuthor().getId();
+        List<DiscordAccount> discordAccounts;
+        DiscordAccount discordAccount;
+
+        // Note that database errors will cause this to return false.
+        // After the database is offline Avail status must be set
+        // to true before any subsequent admin commands will function.
+
+        if (Database.online == false)
+            return false;
+
+        discordAccounts = database.getDiscordAccounts(discordAccountID);
+
+        if (discordAccounts.isEmpty())
+            return false;
+
+        discordAccount = discordAccounts.get(0);
+        return (discordAccount.isDiscordAdmin == 1);
+    }
+
+    public void handleHelpRequest(MessageReceivedEvent event) {
+
+        // Help is kept here in the main class instead of a handler as a
+        // design decision for ease of maintenance.
+
+        String helpString = "I wish for to do the following things for you, not to you!\n\n" +
+                "#register <name>      Register account for to play Magicbane.\n" +
+                "#password <newpass>   Change your current game password.\n" +
+                "#account              List your account detailings.\n" +
+                "#rules                List of MagicBane server rules.\n" +
+                "#status               Display MagicBane server status.\n" +
+                "#help                 List of MagicBot featurings.\n\n" +
+                "http://magicbane.com/tinyinstaller.zip";
+
+        if (isAdminEvent(event))
+            helpString += "\n" +
+                    "#lookup   <name>      Return accounts starting with string.\n" +
+                    "#bug -r <text>        Post to the bug channel/\n" +
+                    "#announce -r <text>   Post to the announcement channel/\n" +
+                    "#changelog <text>     Post to the Changelog channel/\n" +
+                    "#general -r <text>    Post to the general channel/\n" +
+                    "#politics -r <text>   Post to the politics channel/\n" +
+                    "#recruit  -r <text>   Post to the politics channel/\n" +
+                    "#ban      ######      Toggle active status of account.\n" +
+                    "#setavail true/false  Toggle status of database access.\n" +
+                    "#server               reboot/shutdown are your options.\n" +
+                    "#logs                 magicbot/world/login n  (tail)\n" +
+                    "#flash <text>         send flash message\n" +
+                    "#trash                <blank>/detail/flush";
+        sendResponse(event, helpString);
+    }
+
+    public static String generatePassword(int length) {
+
+        String ALPHABET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
+        StringBuilder passwordBuilder = new StringBuilder(length);
+        Random random = new Random();
+
+        // Generate alphanumeric password of a given length.
+        // Could not find a good method of generating a password
+        // based upon a given regex.
+
+        for (int i = 0; i < length; i++)
+            passwordBuilder.append(ALPHABET.charAt(random.nextInt(ALPHABET.length())));
+
+        return new String(passwordBuilder);
+    }
+
+    public static String readLogFile(String filePath, int lineCount) {
+
+        ProcessBuilder builder = new ProcessBuilder("/bin/bash", "-c", "tail -n  " + lineCount + " " + filePath);
+        builder.redirectErrorStream(true);
+        Process process = null;
+        String line = null;
+        String logOutput = "";
+
+        try {
+            process = builder.start();
+
+            InputStream is = process.getInputStream();
+            BufferedReader reader = new BufferedReader(new InputStreamReader(is));
+
+            while ((line = reader.readLine()) != null) {
+                logOutput += line + "\n";
+            }
+
+        } catch (IOException e) {
+            Logger.error(e.toString());
+            return "Error while reading logfile";
+        }
+
+        return logOutput;
+    }
+
+    private static void junkbot(String command, String[] inString) {
+
+        String outString;
+        Writer fileWriter;
+
+        if (inString == null)
+            return;;
+
+        outString = command + String.join(" ", inString);
+        outString += "\n";
+
+        try {
+            fileWriter = new BufferedWriter(new FileWriter("junkbot.txt", true));
+            fileWriter.append(outString);
+            fileWriter.close();
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+    }
+}
diff --git a/src/discord/RobotSpeak.java b/src/discord/RobotSpeak.java
new file mode 100644
index 00000000..d178f0fd
--- /dev/null
+++ b/src/discord/RobotSpeak.java
@@ -0,0 +1,106 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+package discord;
+
+import java.util.Random;
+
+public enum RobotSpeak {
+    BANG("You were not very good at cheating.\n" +
+            "Try cards instead. Go fish?"),
+    BEEP("It is ok. \nYou cheated on MagicBot but wife cheats on you."),
+    BLEEP("Cheated at 20yo game to prove skill."),
+    BLIP("If you cheat MagicBot will for to delete."),
+    BOING("MagicBot for to delete mode activated."),
+    BONG("Did you guild this cheater?\nMagicBot will now for to cross reference..."),
+    BOOM("I knew you were cheating on me when\nstarted for to taking bath twice a week."),
+    BUZZ("Poor player so bad at cheating he\nplays golf records 0 for hole in one."),
+    BURP("Oh no your account detailings ran out of playtime.\n" +
+            "MagicBot will send email when refill procedure exists..."),
+    CHIRP("Association with cheaters is bad for your account health.\n" +
+            "Did you associate with this cheater?"),
+    CHUG("Log in 5 and MagicBot will wave goodbye."),
+    CLICK("MagicBot will for to protect game integrity."),
+    CRACKLE("So this is what eject button does.\nMagicBot will for to press few more times."),
+    CREAK("There is no suspend routine.  Only delete."),
+    DING("Follow #rules and enjoy this game.\n" +
+            "Act like fool, enjoy this shame."),
+    FLUTTER("Sad players cheat because they cannot compete."),
+    HONK("Since cheating player now looking for new game MagicBot\n" +
+            "will suggest World of Tanks or World of Warcraftings."),
+    HISS("Your wetware really needed augmentation with 3rd party program?" +
+            "It's not like this is twitch game..."),
+    HUMMM("You say you needed help to win in emulator beta?\n" +
+            "MagicBot compiler optimizes that to just: you need help."),
+    KERCHUNK("If only you had for to reported the bug instead."),
+    KERPLUNK("Better cheats do not for to make you a better player."),
+    PING("Feel free to poke with stick.\nIt will not cry!"),
+    PLINK("You say you were only grouped with 9 keyclones\n" +
+            "but did not know they were for to cheating..."),
+    POP("It looks like some guild is without a player.\n + " +
+            "Another cheater from same guild and server\n +" +
+            "might be without some guild."),
+    PUFF("MagicBot for to FLUSH!"),
+    POOF("I have no restore procedure.\n" +
+            "I have no unban procedure.\n" +
+            "You for to have no hope."),
+    RATTLE("You are a cheater.\n" +
+            "Did you just win?  MagicBot not so sure."),
+    RUMBLE("MagicBot> self.ejectTheReject(you);"),
+    RUSTLE("Banning you was lke having weird erotic techno-sex\n" +
+            "where all my peripheral slots were filled."),
+    SCREECH("Scarecrow has no brain.\nPerhaps he stole this human's."),
+    SLURP("Learning for to play would have been better option."),
+    SPLAT("You did not for to own a city, did you?"),
+    SPLATTER("You say your guild mates know you cheat.\n" +
+            "What guild was that again?\n"),
+    SWISH("All of my ports are well lubricated."),
+    SQUISH("A ban a day keeps my mechanic away.\nNow it's working much better, thank you."),
+    TINK("So cheating started when 6yo sister beat you in Street fighter?\n" +
+            "You should try talking to my friend Eliza.  She can for to help."),
+    THUD("Game has only 4 rules you managed to break one.\nThat must have taken efforts."),
+    TWANG("If you cannot for to play without cheating, perhaps\n" +
+            "being gigolo would be better career than amateur gamer."),
+    WHIRRR("MagicBot does not for to wield lowly ban hammer." +
+            "It is multi-functional and multi-dimensional\n" +
+            "tool who's name is unpronounceable."),
+    WHOOP("OBLITERATED EVISCERATED MUTILATED DECAPITATED\n" +
+            "Describe how they will. You cheated you are deleted."),
+    WOOSH("Truth be told if were that bad at playing game" +
+            "then MagicBot would have cheated too.\n"),
+    ZAP("Player cheated and got himself deleted.\n" +
+            "MagicBot launches bonus round to see if cheater " +
+            "records can for to get his friends deleted too.");
+
+    String insult;
+
+    RobotSpeak(String insult) {
+        this.insult = insult;
+    }
+
+    public static String getRobotSpeak() {
+
+        String outString = "*";
+        Random random = new Random();
+
+        outString += RobotSpeak.values()[random.nextInt(values().length)].name() + " "
+                + RobotSpeak.values()[random.nextInt(values().length)].name() + "*";
+
+        return outString;
+    }
+
+    public static String getRobotInsult() {
+
+        String outString;
+        Random random = new Random();
+
+        outString = RobotSpeak.values()[random.nextInt(values().length)].insult + "\n\n";
+        outString += getRobotSpeak();
+        return outString;
+    }
+}
diff --git a/src/discord/handlers/AccountInfoRequest.java b/src/discord/handlers/AccountInfoRequest.java
new file mode 100644
index 00000000..2e2c22cd
--- /dev/null
+++ b/src/discord/handlers/AccountInfoRequest.java
@@ -0,0 +1,78 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+package discord.handlers;
+
+import discord.Database;
+import discord.DiscordAccount;
+import discord.MagicBot;
+import engine.Enum;
+import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
+
+import java.util.List;
+
+public class AccountInfoRequest {
+
+    public static void handleRequest(MessageReceivedEvent event) {
+
+        String discordAccountID = event.getAuthor().getId();
+        Enum.AccountStatus accountStatus;
+
+        if (Database.online == false) {
+
+            MagicBot.sendResponse(event,
+                    "Database currently: OFFLINE\n" +
+                            "Try again later!");
+            return;
+        }
+
+        List<DiscordAccount> discordAccounts = MagicBot.database.getDiscordAccounts(discordAccountID);
+
+        // User has no account registered.  Status of what?
+
+        if (discordAccounts.isEmpty()) {
+            MagicBot.sendResponse(event,
+                    "I checked my files twice but could not find your detailings!\n" +
+                            "You can easily fix this by asking me for to #register one.\n" +
+                            "Only one though.  Multiple registrations are not allowed!");
+            return;
+        }
+
+        // Send account detailings to user.
+
+        String outString =
+                "I have for to located your account detailings\n" +
+                        "Registered on: " + discordAccounts.get(0).registrationDate.toString() +
+                        "\n-------------------\n";
+
+        for (DiscordAccount userAccount : discordAccounts)
+            outString += userAccount.gameAccountName + "\n";
+
+        outString += "\n";
+
+        accountStatus = discordAccounts.get(0).status;
+
+        switch (accountStatus) {
+            case BANNED:
+                outString += "Your account status is BANNED. \n\n" +
+                        "It is ok player.\n" +
+                        "You may cheat on us, but your wife cheats on you!";
+                break;
+            case ACTIVE:
+                outString += "Your account status is ACTIVE.\n" +
+                        "Do not cheat or status will change.";
+                break;
+            case ADMIN:
+                outString += "You are an admin.  By your command?";
+                break;
+        }
+
+        MagicBot.sendResponse(event, outString);
+
+    }
+}
diff --git a/src/discord/handlers/AnnounceChannelHandler.java b/src/discord/handlers/AnnounceChannelHandler.java
new file mode 100644
index 00000000..be534e6b
--- /dev/null
+++ b/src/discord/handlers/AnnounceChannelHandler.java
@@ -0,0 +1,55 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+package discord.handlers;
+
+import discord.MagicBot;
+import discord.RobotSpeak;
+import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
+import org.pmw.tinylog.Logger;
+
+import static discord.ChatChannel.ANNOUNCE;
+
+public class AnnounceChannelHandler {
+
+    public static void handleRequest(MessageReceivedEvent event, String[] args) {
+
+        String chatText;
+        String outString;
+
+        // Early exit if database unavailable or is not an admin
+
+        if (MagicBot.isAdminEvent(event) == false)
+            return;
+
+        // Nothing to send?
+
+        if (args.length == 0)
+            return;
+
+        // Convert argument array into string;
+
+        chatText = String.join(" ", args);
+
+        // Build String
+
+        if (chatText.startsWith("-r "))
+            outString =
+                    "```\n" + "Hello Players \n\n" +
+                            chatText.substring(3) + "\n\n" +
+                            RobotSpeak.getRobotSpeak() + "\n```";
+        else outString = chatText;
+
+        // Write string to announce channel
+
+        if (ANNOUNCE.textChannel.canTalk())
+            ANNOUNCE.textChannel.sendMessage(outString).queue();
+
+        Logger.info(event.getAuthor().getName() + " announce: " + chatText);
+    }
+}
diff --git a/src/discord/handlers/BanToggleHandler.java b/src/discord/handlers/BanToggleHandler.java
new file mode 100644
index 00000000..e14cdcd6
--- /dev/null
+++ b/src/discord/handlers/BanToggleHandler.java
@@ -0,0 +1,102 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+package discord.handlers;
+
+import discord.DiscordAccount;
+import discord.MagicBot;
+import discord.RobotSpeak;
+import engine.Enum;
+import net.dv8tion.jda.api.entities.User;
+import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
+import org.pmw.tinylog.Logger;
+
+import java.util.List;
+
+public class BanToggleHandler {
+
+    public static void handleRequest(MessageReceivedEvent event, String[] args) {
+
+        String discordAccountID;
+        Enum.AccountStatus accountStatus;
+
+        // Early exit if database unavailable or is not an admin
+
+        if (MagicBot.isAdminEvent(event) == false)
+            return;
+
+        // Must supply a discord id
+
+        if (args.length != 1) {
+            MagicBot.sendResponse(event, "Must for to supply a valid discord account.");
+            return;
+        }
+
+        // Must be a number!
+
+        discordAccountID = args[0].trim();
+
+        if (discordAccountID.chars().allMatch(Character::isDigit) == false) {
+            MagicBot.sendResponse(event, "Must for to supply a number!");
+            return;
+        }
+
+        List<DiscordAccount> discordAccounts = MagicBot.database.getDiscordAccounts(discordAccountID);
+
+        if (discordAccounts.isEmpty()) {
+            MagicBot.sendResponse(event, "No match for supplied discord account.");
+            return;
+        }
+
+        // toggle ban status
+
+        if (discordAccounts.get(0).status.equals(Enum.AccountStatus.BANNED))
+            accountStatus = Enum.AccountStatus.ACTIVE;
+        else
+            accountStatus = Enum.AccountStatus.BANNED;
+
+        // We have a valid discord ID at this point.  Banstick?
+
+        if (MagicBot.database.updateAccountStatus(discordAccountID, accountStatus) == false) {
+            MagicBot.sendResponse(event, "Error occurred while banning player.");
+            return;
+        }
+
+        // Invalidate login server cache
+
+        MagicBot.database.invalidateLoginCache(discordAccountID);
+
+        // Successful ban.  Ancillary processing begins
+
+        User bannedUser = MagicBot.jda.getUserById(discordAccountID);
+        String bannedName = (bannedUser == null ? discordAccounts.get(0).gameAccountName : bannedUser.getName());
+        String banString = discordAccounts.size() + " accounts set to " + accountStatus + "  for " + discordAccountID + "/" + bannedName;
+
+        MagicBot.sendResponse(event, banString);
+        Logger.info(event.getAuthor().getName() + " " + banString);
+
+        // If we're toggling status to active we're done here.
+
+        if (accountStatus.equals(Enum.AccountStatus.ACTIVE))
+            return;
+
+        // Set users role to noob
+
+        if (bannedUser != null)
+            MagicBot.magicbaneDiscord.removeRoleFromMember(discordAccountID, MagicBot.memberRole).queue();
+
+        // Anounce event in septic tank channel
+
+        banString = "```\n" + "Goodbye Player " + bannedName + "\n\n";
+        banString += RobotSpeak.getRobotInsult() + "\n```";
+
+        if (MagicBot.septicChannel.canTalk())
+            MagicBot.septicChannel.sendMessage(banString).queue();
+    }
+
+}
diff --git a/src/discord/handlers/ChangeLogHandler.java b/src/discord/handlers/ChangeLogHandler.java
new file mode 100644
index 00000000..73ec2710
--- /dev/null
+++ b/src/discord/handlers/ChangeLogHandler.java
@@ -0,0 +1,44 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+package discord.handlers;
+
+import discord.MagicBot;
+import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
+import org.pmw.tinylog.Logger;
+
+import static discord.ChatChannel.CHANGELOG;
+
+public class ChangeLogHandler {
+
+    public static void handleRequest(MessageReceivedEvent event, String[] args) {
+
+        String outString;
+
+        // Early exit if database unavailable or is not an admin
+
+        if (MagicBot.isAdminEvent(event) == false)
+            return;
+
+        // Nothing to send?
+
+        if (args.length == 0)
+            return;
+
+        // Convert argument array into string;
+
+        outString = String.join(" ", args);
+
+        // Write string to changelog channel
+
+        if (CHANGELOG.textChannel.canTalk())
+            CHANGELOG.textChannel.sendMessage(outString).queue();
+
+        Logger.info(event.getAuthor().getName() + " changelog entry: " + outString);
+    }
+}
diff --git a/src/discord/handlers/FlashHandler.java b/src/discord/handlers/FlashHandler.java
new file mode 100644
index 00000000..52a41342
--- /dev/null
+++ b/src/discord/handlers/FlashHandler.java
@@ -0,0 +1,51 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+package discord.handlers;
+
+import discord.MagicBot;
+import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
+import org.pmw.tinylog.Logger;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+
+public class FlashHandler {
+
+    public static void handleRequest(MessageReceivedEvent event, String[] args) {
+
+        String flashText;
+
+        // Early exit if database unavailable or is not an admin
+
+        if (MagicBot.isAdminEvent(event) == false)
+            return;
+
+        // Nothing to send?
+
+        if (args.length == 0)
+            return;
+
+        // Convert argument array into string;
+
+        flashText = String.join(" ", args);
+
+        // Write string to flash file.
+
+        try {
+            Files.write(Paths.get("flash"), flashText.getBytes());
+        } catch (IOException e) {
+            Logger.error(e.toString());
+        }
+
+        Logger.info(event.getAuthor().getName() + " sent flash: " + flashText);
+        MagicBot.sendResponse(event, "Flash: " + flashText);
+
+    }
+}
diff --git a/src/discord/handlers/ForToFixChannelHandler.java b/src/discord/handlers/ForToFixChannelHandler.java
new file mode 100644
index 00000000..8af07639
--- /dev/null
+++ b/src/discord/handlers/ForToFixChannelHandler.java
@@ -0,0 +1,56 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+package discord.handlers;
+
+import discord.MagicBot;
+import discord.RobotSpeak;
+import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
+import org.pmw.tinylog.Logger;
+
+import static discord.ChatChannel.FORTOFIX;
+
+public class ForToFixChannelHandler {
+
+    public static void handleRequest(MessageReceivedEvent event, String[] args) {
+
+        String chatText;
+        String outString;
+
+        // Early exit if database unavailable or is not an admin
+
+        if (MagicBot.isAdminEvent(event) == false)
+            return;
+
+        // Nothing to send?
+
+        if (args.length == 0)
+            return;
+
+        // Convert argument array into string;
+
+        chatText = String.join(" ", args);
+
+        // Build String
+
+        if (chatText.startsWith("-r "))
+            outString =
+                "```\n" + "Hello Players \n\n" +
+                        chatText.substring(3) + "\n\n" +
+                        RobotSpeak.getRobotSpeak() + "\n```";
+        else outString = chatText;
+
+        // Write string to changelog channel
+
+        if (FORTOFIX.textChannel.canTalk())
+            FORTOFIX.textChannel.sendMessage(outString).queue();
+
+        Logger.info(event.getAuthor().getName() + "fortofix: " + chatText);
+
+    }
+}
diff --git a/src/discord/handlers/GeneralChannelHandler.java b/src/discord/handlers/GeneralChannelHandler.java
new file mode 100644
index 00000000..90e36c2c
--- /dev/null
+++ b/src/discord/handlers/GeneralChannelHandler.java
@@ -0,0 +1,56 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+package discord.handlers;
+
+import discord.MagicBot;
+import discord.RobotSpeak;
+import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
+import org.pmw.tinylog.Logger;
+
+import static discord.ChatChannel.GENERAL;
+
+public class GeneralChannelHandler {
+
+    public static void handleRequest(MessageReceivedEvent event, String[] args) {
+
+        String chatText;
+        String outString;
+
+        // Early exit if database unavailable or is not an admin
+
+        if (MagicBot.isAdminEvent(event) == false)
+            return;
+
+        // Nothing to send?
+
+        if (args.length == 0)
+            return;
+
+        // Convert argument array into string;
+
+        chatText = String.join(" ", args);
+
+        // Build String
+
+        if (chatText.startsWith("-r "))
+            outString =
+                "```\n" + "Hello Players \n\n" +
+                        chatText.substring(3) + "\n\n" +
+                        RobotSpeak.getRobotSpeak() + "\n```";
+        else outString = chatText;
+
+        // Write string to changelog channel
+
+        if (GENERAL.textChannel.canTalk())
+            GENERAL.textChannel.sendMessage(outString).queue();
+
+        Logger.info(event.getAuthor().getName() + "general: " + chatText);
+
+    }
+}
diff --git a/src/discord/handlers/LogsRequestHandler.java b/src/discord/handlers/LogsRequestHandler.java
new file mode 100644
index 00000000..4c8ed084
--- /dev/null
+++ b/src/discord/handlers/LogsRequestHandler.java
@@ -0,0 +1,62 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+package discord.handlers;
+
+import discord.MagicBot;
+import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
+
+public class LogsRequestHandler {
+
+    public static void handleRequest(MessageReceivedEvent event, String[] args) {
+
+        String logType;
+        int tailCount;
+        String logOutput;
+
+        // Early exit if database unavailable or is not an admin
+
+        if (MagicBot.isAdminEvent(event) == false)
+            return;
+
+        // No arguments supplied?
+
+        if (args.length != 2)
+            return;
+
+        logType = args[0].toLowerCase().trim();
+
+        if ("worldloginmagicbot".contains(logType) == false)
+            return;
+
+        try {
+            tailCount = Integer.parseInt(args[1].trim());
+        } catch (NumberFormatException e) {
+            return;
+        }
+
+        // Transform logtype to actual file name
+
+        switch (logType) {
+            case "magicbot":
+                logType = "console_magicbot";
+                break;
+            case "world":
+                logType = "console_server";
+                break;
+            case "login":
+                logType = "console_login";
+                break;
+        }
+
+        // Retrieve the data and send back to the user
+
+        logOutput = MagicBot.readLogFile(logType, tailCount);
+        MagicBot.sendResponse(event, logOutput);
+    }
+}
diff --git a/src/discord/handlers/LookupRequestHandler.java b/src/discord/handlers/LookupRequestHandler.java
new file mode 100644
index 00000000..0a968425
--- /dev/null
+++ b/src/discord/handlers/LookupRequestHandler.java
@@ -0,0 +1,77 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+package discord.handlers;
+
+import discord.DiscordAccount;
+import discord.MagicBot;
+import net.dv8tion.jda.api.entities.User;
+import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
+import org.pmw.tinylog.Logger;
+
+import java.util.List;
+
+public class LookupRequestHandler {
+
+    public static void handleRequest(MessageReceivedEvent event, String[] args) {
+
+        String searchString = "";
+        String outString;
+
+        // Early exit if database unavailable or is not an admin
+
+        if (MagicBot.isAdminEvent(event) == false)
+            return;
+
+        // No argument supplied?
+
+        if (args.length != 1)
+            return;
+
+        searchString = args[0].toLowerCase();
+
+        List<DiscordAccount> discordAccounts = MagicBot.database.getAccountsByDiscordName(searchString, false);
+
+        if (discordAccounts.isEmpty()) {
+            MagicBot.sendResponse(event,
+                    "No accounts found matching string: " + searchString);
+            return;
+        }
+
+        if (discordAccounts.size() >= 20) {
+            MagicBot.sendResponse(event,
+                    discordAccounts.size() + "Sorry more than 20 records were returned! " + searchString);
+            return;
+        }
+
+        // Valid request return results
+
+        Logger.info(event.getAuthor().getName() + " lookup on account:" + searchString);
+
+        outString =
+                "The follow accounts matched: " + searchString + "\n\n" +
+                        "-------------------\n";
+
+        for (DiscordAccount userAccount : discordAccounts) {
+
+            // Ternary became a bitch, so broke this out.
+
+            User discordUser = MagicBot.jda.getUserById(userAccount.discordAccount);
+
+            if (discordUser != null)
+                outString += discordUser.getName() + discordUser.getDiscriminator() +
+                        "/" + userAccount.discordAccount + "     ";
+            else
+                outString += userAccount.discordAccount + " *N/A*     ";
+
+            outString += userAccount.gameAccountName + "     " + userAccount.status.name() + "     ";
+            outString += "\n";
+        }
+        MagicBot.sendResponse(event, outString);
+    }
+}
diff --git a/src/discord/handlers/PasswordChangeHandler.java b/src/discord/handlers/PasswordChangeHandler.java
new file mode 100644
index 00000000..bc228c25
--- /dev/null
+++ b/src/discord/handlers/PasswordChangeHandler.java
@@ -0,0 +1,113 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+package discord.handlers;
+
+import discord.Database;
+import discord.DiscordAccount;
+import discord.MagicBot;
+import engine.Enum;
+import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
+import org.pmw.tinylog.Logger;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+public class PasswordChangeHandler {
+
+    public static void handleRequest(MessageReceivedEvent event, String[] args) {
+
+        String discordAccountID = event.getAuthor().getId();
+        DiscordAccount discordAccount;
+        String newPassword;
+        boolean defaulted = false;
+
+        if (Database.online == false) {
+
+            MagicBot.sendResponse(event,
+                    "Database currently: OFFLINE\n" +
+                            "Try again later!");
+            return;
+        }
+
+        List<DiscordAccount> discordAccounts = MagicBot.database.getDiscordAccounts(discordAccountID);
+
+        // User has no account registered.  Change password?
+
+        if (discordAccounts.isEmpty()) {
+            MagicBot.sendResponse(event,
+                    "I checked my files twice but could not find your detailings!\n" +
+                            "You can easily fix this by asking me for to #register one.\n" +
+                            "Only one though.  Multiple registrations are not allowed!");
+            return;
+        }
+
+        // All accounts are updated in one lot.  Retrieve the first.
+
+        discordAccount = discordAccounts.get(0);
+
+        // Banned or suspended user's get no love.
+
+        if (discordAccount.status.equals(Enum.AccountStatus.BANNED)) {
+            MagicBot.sendResponse(event,
+                    "Sorry but that is too much work. \n" +
+                            "Your account detailings cannot for to log into game!");
+            return;
+        }
+
+        // User has requested password change within last 24 hours.
+
+        if (discordAccount.lastUpdateRequest != null &&
+                LocalDateTime.now().isBefore(discordAccount.lastUpdateRequest.plusDays(1))) {
+
+            MagicBot.sendResponse(event,
+                    "You must wait 24 hours between password requests. \n" +
+                            "Last account updatings: " + discordAccount.lastUpdateRequest.toString());
+            return;
+        }
+
+        // No argument choose new random password *he he he*
+
+        if (args.length != 1) {
+            newPassword = MagicBot.generatePassword(8);
+            defaulted = true;
+        } else
+            newPassword = args[0];
+
+        // Validate password with regex
+
+        if (MagicBot.passwordRegex.matcher(newPassword).matches() == false) {
+
+            MagicBot.sendResponse(event,
+                    "Your supplied password does not compute.\n" +
+                            "New password must satisfy following regex:\n" +
+                            "^[\\p{Alnum}]{6,20}$");
+            return;
+        }
+
+        if (newPassword.toLowerCase().equals("newpass")) {
+            MagicBot.sendResponse(event,
+                    "newpass is not valid password.\n" +
+                            "Have brain player!");
+            return;
+        }
+
+        // Password validates let's change it
+
+        if (MagicBot.database.updateAccountPassword(discordAccount.discordAccount, newPassword) == true) {
+
+            MagicBot.sendResponse(event,
+                    "Please allow short minute for to update account detailings.\n" +
+                            "Login Server is hosted in bathroom above toilet.  Must flush!\n" +
+                            (defaulted == true ? "As you did not for to supply new pass I chose one for you.\n" : "") +
+                            "New Password: " + newPassword);
+        }
+
+        Logger.info(event.getAuthor().getName() + " reset their password");
+    }
+}
diff --git a/src/discord/handlers/PoliticalChannelHandler.java b/src/discord/handlers/PoliticalChannelHandler.java
new file mode 100644
index 00000000..1b046407
--- /dev/null
+++ b/src/discord/handlers/PoliticalChannelHandler.java
@@ -0,0 +1,55 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+package discord.handlers;
+
+import discord.MagicBot;
+import discord.RobotSpeak;
+import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
+import org.pmw.tinylog.Logger;
+
+import static discord.ChatChannel.POLITICAL;
+
+public class PoliticalChannelHandler {
+
+    public static void handleRequest(MessageReceivedEvent event, String[] args) {
+
+        String chatText;
+        String outString;
+
+        // Early exit if database unavailable or is not an admin
+
+        if (MagicBot.isAdminEvent(event) == false)
+            return;
+
+        // Nothing to send?
+
+        if (args.length == 0)
+            return;
+
+        // Convert argument array into string;
+
+        chatText = String.join(" ", args);
+
+        // Build String
+
+        if (chatText.startsWith("-r "))
+            outString =
+                    "```\n" + "Hello Players \n\n" +
+                            chatText.substring(3) + "\n\n" +
+                            RobotSpeak.getRobotSpeak() + "\n```";
+        else outString = chatText;
+
+        // Write string to changelog channel
+
+        if (POLITICAL.textChannel.canTalk())
+            POLITICAL.textChannel.sendMessage(outString).queue();
+
+        Logger.info(event.getAuthor().getName() + " politics: " + chatText);
+    }
+}
diff --git a/src/discord/handlers/RecruitChannelHandler.java b/src/discord/handlers/RecruitChannelHandler.java
new file mode 100644
index 00000000..798c1588
--- /dev/null
+++ b/src/discord/handlers/RecruitChannelHandler.java
@@ -0,0 +1,56 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+package discord.handlers;
+
+import discord.MagicBot;
+import discord.RobotSpeak;
+import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
+import org.pmw.tinylog.Logger;
+
+import static discord.ChatChannel.RECRUIT;
+
+public class RecruitChannelHandler {
+
+    public static void handleRequest(MessageReceivedEvent event, String[] args) {
+
+        String chatText;
+        String outString;
+
+        // Early exit if database unavailable or is not an admin
+
+        if (MagicBot.isAdminEvent(event) == false)
+            return;
+
+        // Nothing to send?
+
+        if (args.length == 0)
+            return;
+
+        // Convert argument array into string;
+
+        chatText = String.join(" ", args);
+
+        // Build String
+
+        if (chatText.startsWith("-r "))
+            outString =
+                "```\n" + "Hello Players \n\n" +
+                        chatText.substring(3) + "\n\n" +
+                        RobotSpeak.getRobotSpeak() + "\n```";
+        else outString = chatText;
+
+        // Write string to changelog channel
+
+        if (RECRUIT.textChannel.canTalk())
+            RECRUIT.textChannel.sendMessage(outString).queue();
+
+        Logger.info(event.getAuthor().getName() + "recruit: " + chatText);
+
+    }
+}
diff --git a/src/discord/handlers/RegisterAccountHandler.java b/src/discord/handlers/RegisterAccountHandler.java
new file mode 100644
index 00000000..ac6c427e
--- /dev/null
+++ b/src/discord/handlers/RegisterAccountHandler.java
@@ -0,0 +1,126 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+package discord.handlers;
+
+import discord.Database;
+import discord.DiscordAccount;
+import discord.MagicBot;
+import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
+import org.pmw.tinylog.Logger;
+
+import java.util.List;
+
+public class RegisterAccountHandler {
+
+    public static void handleRequest(MessageReceivedEvent event, String[] args) {
+
+        String discordAccountID = event.getAuthor().getId();
+        String discordUserName = event.getAuthor().getName();
+        String discordPassword = MagicBot.generatePassword(8);
+        String accountName;
+
+        if (Database.online == false) {
+            MagicBot.sendResponse(event,
+                    "Database currently: OFFLINE\n" +
+                            "Try again later!");
+            return;
+        }
+
+        List<DiscordAccount> discordAccounts = MagicBot.database.getDiscordAccounts(discordAccountID);
+
+        // If we have previously registered this discord account let them know
+        // the current status.
+
+        if (discordAccounts.isEmpty() == false) {
+            MagicBot.sendResponse(event,
+                    "It seems you already have an account registered.\n" +
+                            "Do you need #account detailings or more general #help?");
+            MagicBot.magicbaneDiscord.addRoleToMember(discordAccountID, MagicBot.memberRole).queue();
+            return;
+        }
+
+        // if user supplied argument let's validate it.
+        // otherwise build an account name based on their discord account.
+
+        if (args.length != 1) {
+
+            // Build account name using Discord name along with their discriminator.
+
+            accountName = discordUserName.replaceAll("\\s+", "");
+            accountName += "#" + event.getAuthor().getDiscriminator();
+        } else {
+
+            // Validate account name with regex
+
+            accountName = args[0].replaceAll("\\s+", "");
+
+            if (MagicBot.accountNameRegex.matcher(accountName).matches() == false) {
+
+                MagicBot.sendResponse(event,
+                        "Your supplied account name does not compute.\n" +
+                                "Account names must satisfy following regex:\n" +
+                                "^[\\p{Alnum}]{6,20}$");
+                return;
+            }
+
+            if (accountName.toLowerCase().equals("accountname")) {
+                MagicBot.sendResponse(event,
+                        "accountname is not valid account name.\n" +
+                                "Have brain player!");
+                return;
+            }
+        }
+
+        // Make sure account doesn't already exist.
+
+        if (MagicBot.database.getAccountsByDiscordName(accountName, true).isEmpty() == false) {
+
+            MagicBot.sendResponse(event,
+                    "It seems this account name is already taken.\n" +
+                            "Perhaps try one less common in frequency.");
+            return;
+        }
+
+        // If there is no registered discord account we oblige and create 4
+        // account based upon his current discord *name* not the ID.
+
+        if (MagicBot.database.registerDiscordAccount(discordAccountID, accountName, discordPassword) == true) {
+
+            Logger.info("Account " + accountName + " created for: " + discordUserName + " " + discordAccountID);
+
+            MagicBot.sendResponse(event,
+                    "Welcome to MagicBane!\n" +
+                            "-------------------\n" +
+                            "I have registered the following accounts to your discord.\n\n" +
+                            "1) " + accountName + "#1" + "     2) " + accountName + "#2\n" +
+                            "3) " + accountName + "#3" + "     4) " + accountName + "#4\n\n" +
+                            "Your default password is: " + discordPassword + "\n" +
+                            "Ask me #help for to receive list of robot featurings.\n\n" +
+                            "http://magicbane.com/tinyinstaller.zip" +
+                            "\n\nPlay for to Crush!");
+
+            // Add Discord member privileges.
+
+            MagicBot.magicbaneDiscord.addRoleToMember(discordAccountID, MagicBot.memberRole).queue();
+
+            return;
+        }
+
+        // The call to the stored procedure abended.  Report to player
+        // and return.
+
+        Logger.error("Creating account: " + accountName + " for: " + discordUserName + " " + discordAccountID);
+        Database.online = false;
+
+        MagicBot.sendResponse(event,
+                "-------------------\n" +
+                        "I for to had internal error while registering your\n" +
+                        "account.  This has been reported.  Try again later!");
+    }
+}
diff --git a/src/discord/handlers/RulesRequestHandler.java b/src/discord/handlers/RulesRequestHandler.java
new file mode 100644
index 00000000..0484cc62
--- /dev/null
+++ b/src/discord/handlers/RulesRequestHandler.java
@@ -0,0 +1,30 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+package discord.handlers;
+
+import discord.MagicBot;
+import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
+
+public class RulesRequestHandler {
+
+    public static void handleRequest(MessageReceivedEvent event) {
+
+        String outString;
+
+        // Add greeting
+
+        outString = "-No hacking.\n";
+        outString += "-No cheating.  If you cheat, we will delete.\n";
+        outString += "-Players limited to 4 concurrent accounts.\n";
+        outString += "-Share accounts at own risk.\n";
+        outString += "-No refunds";
+
+        MagicBot.sendResponse(event, outString);
+    }
+}
diff --git a/src/discord/handlers/ServerRequestHandler.java b/src/discord/handlers/ServerRequestHandler.java
new file mode 100644
index 00000000..af2fa747
--- /dev/null
+++ b/src/discord/handlers/ServerRequestHandler.java
@@ -0,0 +1,63 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+package discord.handlers;
+
+import discord.MagicBot;
+import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
+import org.pmw.tinylog.Logger;
+
+import java.io.IOException;
+
+public class ServerRequestHandler {
+
+    public static void handleRequest(MessageReceivedEvent event, String[] args) {
+
+        String serverCommand;
+        String execString = "";
+
+        // Early exit if database unavailable or is not an admin
+
+        if (MagicBot.isAdminEvent(event) == false)
+            return;
+
+        // No command supplied?
+
+        if (args.length != 1)
+            return;
+
+        serverCommand = args[0].toLowerCase().trim();
+
+        // only reboot or shutdown
+
+        if ("rebootshutdown".contains(serverCommand) == false)
+            return;
+
+        switch (serverCommand) {
+
+            case "reboot":
+                execString = "/bin/sh -c ./mbrestart.sh";
+                break;
+            case "shutdown":
+                execString = "/bin/sh -c ./mbkill.sh";
+                break;
+            default:
+                break;
+        }
+
+        if (execString.isEmpty() == false) {
+            try {
+                Runtime.getRuntime().exec(execString);
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+            MagicBot.sendResponse(event, "MagicBot has executed your " + serverCommand);
+            Logger.info(event.getAuthor().getName() + " told server to " + serverCommand);
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/discord/handlers/SetAvailHandler.java b/src/discord/handlers/SetAvailHandler.java
new file mode 100644
index 00000000..2f766ace
--- /dev/null
+++ b/src/discord/handlers/SetAvailHandler.java
@@ -0,0 +1,51 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+package discord.handlers;
+
+import discord.Database;
+import discord.MagicBot;
+import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
+import org.pmw.tinylog.Logger;
+
+public class SetAvailHandler {
+
+    public static void handleRequest(MessageReceivedEvent event, String[] args) {
+
+        String availStatus;
+        String availPass;
+
+        if (args.length != 2)
+            return;
+
+        availStatus = args[0].toLowerCase().trim();
+
+        // only on/off
+
+        if ("truefalse".contains(availStatus) == false)
+            return;
+
+        // Set avail is password driven
+
+        availPass = args[1].toLowerCase().trim();
+
+        if ("myshoes123".equals(availPass) == false)
+            return;
+
+        // Authenticated so change availstatus
+
+        if (availStatus.equals("true"))
+            Database.online = true;
+        else
+            Database.online = false;
+
+        Logger.info(event.getAuthor().getName() + " set avail status to: " + Database.online);
+        MagicBot.sendResponse(event, "Avail status set to: " + Database.online);
+
+    }
+}
diff --git a/src/discord/handlers/StatusRequestHandler.java b/src/discord/handlers/StatusRequestHandler.java
new file mode 100644
index 00000000..39e59985
--- /dev/null
+++ b/src/discord/handlers/StatusRequestHandler.java
@@ -0,0 +1,42 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+package discord.handlers;
+
+import discord.Database;
+import discord.MagicBot;
+import engine.gameManager.ConfigManager;
+import engine.server.login.LoginServer;
+import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
+
+public class StatusRequestHandler {
+
+    public static void handleRequest(MessageReceivedEvent event) {
+
+        String outString;
+
+        // Add version information
+        outString = "MagicBot: " + ConfigManager.MB_MAGICBOT_BOTVERSION.getValue() + "\n" +
+                "MagicBane: " + ConfigManager.MB_MAGICBOT_GAMEVERSION.getValue() + "\n";
+
+        // Add server status info
+        outString += "\nServer Status: ";
+
+        if (LoginServer.isPortInUse(Integer.parseInt(ConfigManager.MB_BIND_ADDR.getValue())))
+            outString += "ONLINE\n";
+        else
+            outString += "OFFLINE\n";
+
+        if (Database.online == true)
+            outString += MagicBot.database.getPopulationSTring();
+        else
+            outString += "Database offline: no population data.";
+
+        MagicBot.sendResponse(event, outString);
+    }
+}
diff --git a/src/discord/handlers/TrashRequestHandler.java b/src/discord/handlers/TrashRequestHandler.java
new file mode 100644
index 00000000..79da4ae9
--- /dev/null
+++ b/src/discord/handlers/TrashRequestHandler.java
@@ -0,0 +1,75 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+package discord.handlers;
+
+import discord.MagicBot;
+import discord.RobotSpeak;
+import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
+import org.pmw.tinylog.Logger;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+
+import static discord.ChatChannel.SEPTIC;
+
+public class TrashRequestHandler {
+
+    public static void handleRequest(MessageReceivedEvent event, String[] args) {
+
+        String outString;
+        int trashCount = 0;
+
+        // Early exit if database unavailable or is not an admin
+
+        if (MagicBot.isAdminEvent(event) == false)
+            return;
+
+        if (args.length == 0) {
+            outString = MagicBot.database.getTrashFile();
+            MagicBot.sendResponse(event, outString);
+            return;
+        }
+
+        if (args[0].equals("flush") == true) {
+
+            // Empty the trash!
+
+            trashCount = MagicBot.database.getTrashCount();
+
+            if (trashCount == 0)
+                return;
+
+            // Anounce event in septic tank channel
+
+            outString = "```\n" +  trashCount + " Player Character were for to deleted due to verified cheatings. \n\n";
+            outString += MagicBot.database.getTrashList() + "\n\n";
+            outString += RobotSpeak.getRobotInsult() + "\n```";
+
+            if (SEPTIC.textChannel.canTalk())
+                SEPTIC.textChannel.sendMessage(outString).queue();
+
+            try {
+                Files.write(Paths.get("trash"), "".getBytes());
+                outString = "Flushing trash players...\n";
+                MagicBot.sendResponse(event, outString);
+            } catch (IOException e) {
+                Logger.error(e.toString());
+            }
+        }
+
+        if (args[0].equals("detail") == true) {
+
+            outString = MagicBot.database.getTrashDetail();
+            MagicBot.sendResponse(event, outString);
+            return;
+        }
+
+    }
+}
diff --git a/src/engine/Enum.java b/src/engine/Enum.java
new file mode 100644
index 00000000..29924c7e
--- /dev/null
+++ b/src/engine/Enum.java
@@ -0,0 +1,2943 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+package engine;
+
+import ch.claude_martin.enumbitset.EnumBitSetHelper;
+import engine.gameManager.BuildingManager;
+import engine.gameManager.PowersManager;
+import engine.gameManager.ZoneManager;
+import engine.math.Vector2f;
+import engine.math.Vector3fImmutable;
+import engine.objects.*;
+import engine.powers.EffectsBase;
+import org.pmw.tinylog.Logger;
+
+import java.awt.*;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.concurrent.ThreadLocalRandom;
+
+/*
+ * MagicBane engine enumeration class.
+ *
+ * All enumerations accessed by multiple
+ * classes should be defined here to keep
+ * the imports consolidated.
+ */
+
+public class Enum {
+
+	public enum MobRaceType {
+
+		Aelfborn(436353765),
+		All(80289),
+		Animal(-1674072607),
+		Aracoix(-1764716937),
+		Celestial(-317458791),
+		Centaur(775630999),
+		Construct(-513218610),
+		CSR(52803),
+		Dragon(-1731031452),
+		Dwarf(71831236),
+		Elf(70053),
+		Giant(90574087),
+		Goblin(-1732836921),
+		Grave(75107943),
+		HalfGiant(251196434),
+		Human(79806088),
+		Infernal(-654077031),
+		Insect(-1407990295),
+		Irekei(-1770742167),
+		Minotaur(-949570680),
+		Monster(258519513),
+		NecroPet(618137151),
+		NPC(35374),
+		Pet(88208),
+		Plant(90574256),
+		Rat(88082),
+		Reptile(-591705981),
+		Shade(74648883),
+		Siege(74620179),
+		SiegeEngineer(-839969219),
+		Summoned(-656950110),
+		Troll(82261620),
+		Undead(-1942775307),
+		Nephilim(-592098572),
+		Vampire(-524731385);
+
+		int token;
+
+		private static HashMap<Integer, MobRaceType> _mobRaceTypeByToken = new HashMap<>();
+
+		MobRaceType(int token) {
+			this.token = token;
+		}
+
+		public static MobRaceType getRaceTypebyToken(int token) {
+			return _mobRaceTypeByToken.get(token);
+		}
+
+		public static void initRaceTypeTables() {
+
+			for (MobRaceType raceType : MobRaceType.values()) {
+				_mobRaceTypeByToken.put(raceType.token, raceType);
+			}
+		}
+
+	}
+
+	public enum MobFlagType implements EnumBitSetHelper<MobFlagType> {
+		AGGRESSIVE,
+		CANROAM,
+		CALLSFORHELP,
+		RESPONDSTOCALLSFORHELP,
+		HUMANOID,
+		UNDEAD,
+		BEAST,
+		DRAGON,
+		RAT,
+		SENTINEL,
+	}
+
+	public enum AggroType implements EnumBitSetHelper<AggroType> {
+
+		// Used for MobBase NoAggro types
+		// *** WARNING: ENUM IS FRAGILE AS
+		// ORDINALS STORED IN DB.
+
+		AELF,
+		ARACOIX,
+		CENTAUR,
+		DWARF,
+		ELF,
+		HALFGIANT,
+		HUMAN,
+		IREKEI,
+		MINO,
+		NEPH,
+		SHADE,
+		VAMP,
+		ARCHON;
+
+	}
+
+	public enum CharacterSex {
+		MALE,
+		FEMALE,
+		FUZZY,
+		OTHER;
+	}
+
+	public enum RaceType {
+
+		// RaceRuneID / AggroType, isFemale
+
+		AELFMALE(2000, AggroType.AELF, RunSpeed.STANDARD, CharacterSex.MALE,1.05f),
+		AELFFEMALE(2001, AggroType.AELF, RunSpeed.STANDARD, CharacterSex.FEMALE,1.05f),
+		ARACOIXMALE(2002, AggroType.ARACOIX, RunSpeed.STANDARD, CharacterSex.MALE,1),
+		ARACOIXFEMALE(2003, AggroType.ARACOIX, RunSpeed.STANDARD, CharacterSex.FEMALE,1),
+		CENTAURMALE(2004, AggroType.CENTAUR, RunSpeed.CENTAUR, CharacterSex.MALE,1.2f),
+		CENTAURFEMALE(2005, AggroType.CENTAUR, RunSpeed.CENTAUR, CharacterSex.FEMALE, 1.2f),
+		DWARFMALE(2006, AggroType.DWARF, RunSpeed.STANDARD, CharacterSex.MALE,0.80000001f),
+		ELFMALE(2008, AggroType.ELF, RunSpeed.STANDARD, CharacterSex.MALE, 1.4f),
+		ELFFEMALE(2009, AggroType.ELF, RunSpeed.STANDARD, CharacterSex.FEMALE,1.1f),
+		HALFGIANTMALE(2010, AggroType.HALFGIANT, RunSpeed.STANDARD, CharacterSex.MALE, 1.15f),
+		HUMANMALE(2011, AggroType.HUMAN, RunSpeed.STANDARD, CharacterSex.MALE,1),
+		HUMANFEMALE(2012, AggroType.HUMAN, RunSpeed.STANDARD, CharacterSex.FEMALE,1),
+		IREKEIMALE(2013, AggroType.IREKEI, RunSpeed.STANDARD, CharacterSex.MALE,1.1f),
+		IREKEIFEMALE(2014, AggroType.IREKEI, RunSpeed.STANDARD, CharacterSex.FEMALE,1.1f),
+		SHADEMALE(2015, AggroType.SHADE, RunSpeed.STANDARD, CharacterSex.MALE,1),
+		SHADEFEMALE(2016, AggroType.SHADE, RunSpeed.STANDARD, CharacterSex.FEMALE,1),
+		MINOMALE(2017, AggroType.MINO, RunSpeed.MINOTAUR, CharacterSex.MALE,1.3f),
+		ARCHONMALE(2018, AggroType.ARCHON, RunSpeed.STANDARD, CharacterSex.MALE,1),
+		HALEGIANTOLDMALE(2019, AggroType.HALFGIANT, RunSpeed.STANDARD, CharacterSex.MALE,1.15f),
+		CSRFEMALE(2020, AggroType.ARCHON, RunSpeed.STANDARD, CharacterSex.FEMALE,0.66000003f),
+		CSRMALE(2021, AggroType.ARCHON, RunSpeed.STANDARD, CharacterSex.MALE,1),
+		NEPHMALE(2025, AggroType.NEPH, RunSpeed.STANDARD, CharacterSex.MALE,1.1f),
+		NEPHFEMALE(2026, AggroType.NEPH, RunSpeed.STANDARD, CharacterSex.FEMALE,1.1f),
+		HALFGIANTFEMALE(2027, AggroType.HALFGIANT, RunSpeed.STANDARD, CharacterSex.FEMALE,1.15f),
+		VAMPMALE(2028, AggroType.VAMP, RunSpeed.STANDARD, CharacterSex.MALE, 1),
+		VAMPFEMALE(2029, AggroType.VAMP, RunSpeed.STANDARD, CharacterSex.FEMALE,1);
+
+		@SuppressWarnings("unchecked")
+		private static HashMap<Integer, RaceType> _raceTypeByID = new HashMap<>();
+
+		int runeID;
+		private AggroType aggroType;
+		private CharacterSex characterSex;
+		private RunSpeed runSpeed;
+		private float scaleHeight;
+
+		RaceType(int runeID, AggroType aggroType, RunSpeed runspeed, CharacterSex characterSex, float scaleHeight) {
+			this.runeID = runeID;
+			this.aggroType = aggroType;
+			this.runSpeed = runspeed;
+			this.characterSex = characterSex;
+			this.scaleHeight = scaleHeight;
+		}
+
+		public int getRuneID() {
+			return this.runeID;
+		}
+
+		public static RaceType getRaceTypebyRuneID(int runeID) {
+			return _raceTypeByID.get(runeID);
+		}
+		
+		public float getScaleHeight(){
+			return this.scaleHeight;
+		}
+
+		public static void initRaceTypeTables() {
+
+			for (RaceType raceType : RaceType.values()) {
+				_raceTypeByID.put(raceType.runeID, raceType);
+			}
+		}
+
+		public AggroType getAggroType() {
+			return aggroType;
+		}
+
+		public RunSpeed getRunSpeed() {
+			return runSpeed;
+		}
+
+		public CharacterSex getCharacterSex() {
+			return characterSex;
+		}
+	}
+
+	public enum RunSpeed {
+
+		SENTINEL(0, 0, 0, 0, 0, 0, 0),
+		STANDARD(6.1900001f, 13.97f, 4.2199998f, 13.97f, 6.3299999f, 18.379999f, 6.5f),
+		CENTAUR(6.1900001f, 16.940001f, 5.5500002f, 16.940001f, 6.3299999f, 18.379999f, 6.5f),
+		MINOTAUR(6.6300001f, 15.95f, 4.2199998f, 15.95f, 6.3299999f, 18.379999f, 6.5f);
+
+		private float walkStandard;
+		private float walkCombat;
+		private float runStandard;
+		private float runCombat;
+		private float swim;
+		private float flyRun;
+		private float flyWalk;
+
+		RunSpeed(float walkStandard, float runStandard, float walkCombat, float runCombat, float flyWalk, float flyRun, float swim) {
+			this.walkStandard = walkStandard;
+			this.walkCombat = walkCombat;
+			this.runStandard = runStandard;
+			this.runCombat = runCombat;
+			this.swim = swim;
+			this.flyRun = flyRun;
+			this.flyWalk = flyWalk;
+		}
+
+
+		public float getWalkStandard() {
+			return walkStandard;
+		}
+
+		public float getWalkCombat() {
+			return walkCombat;
+		}
+
+		public float getRunStandard() {
+			return runStandard;
+		}
+
+		public float getRunCombat() {
+			return runCombat;
+		}
+
+		public float getSwim() {
+			return swim;
+		}
+
+
+		public float getFlyRun() {
+			return flyRun;
+		}
+
+
+		public float getFlyWalk() {
+			return flyWalk;
+		}
+
+	}
+
+	public enum FriendListType {
+
+		VIEWHERALDRY(1),
+		ADDHERALDRY(4),
+		REMOVEHERALDRY(6),
+		DEALTHS(7),
+		KILLS(9),
+		VIEWCONDEMN(11),
+		ADDCONDEMN(14),
+		REMOVECONDEMN(15),
+		TOGGLEACTIVE(17),
+		REVERSEKOS(19),
+		VIEWFRIENDS(25),
+		TOITEM(23),
+		ADDFRIEND(28),
+		REMOVEFRIEND(30);
+
+		private final int listType;
+
+		FriendListType(int listType) {
+			this.listType = listType;
+		}
+
+		public int getListType() {
+			return this.listType;
+		}
+
+		public static FriendListType getListTypeByID(int listType) {
+
+			FriendListType outType = null;
+
+			for (FriendListType friendListType : FriendListType.values()) {
+				if (friendListType.listType == listType)
+					outType = friendListType;
+			}
+			return outType;
+		}
+	}
+
+	public enum DispatchChannel {
+		PRIMARY(0),
+		SECONDARY(1);
+
+		private final int channelID;
+
+		DispatchChannel(int channelID) {
+			this.channelID = channelID;
+		}
+
+		public int getChannelID() {
+			return this.channelID;
+		}
+
+	}
+
+	public enum PvpHistoryType {
+		KILLS,
+		DEATHS;
+	}
+
+	public enum ChatMessageType {
+		ERROR,
+		INFO,
+		MOTD;
+	}
+
+	public enum DataRecordType {
+		PVP,
+		CHARACTER,
+		BANE,
+		GUILD,
+		CITY,
+		ZONE,
+		REALM,
+		MINE;
+	}
+
+	public enum RecordEventType {
+		CREATE,  // Shared with city/guild
+		DISBAND,
+		DESTROY, // City events
+		CAPTURE,
+		TRANSFER,
+		PENDING,
+		DEFEND,
+		LOST; // Realm event
+	}
+
+	public enum CharterType {
+		FEUDAL(-600065291, 5060000),
+		MERCANTILE(-15978914, 5060400),
+		BELLIGERENT(762228431, 5060800);
+
+		private int charterID;
+		private int meshID;
+
+		CharterType(int charterID, int meshID) {
+			this.charterID = charterID;
+			this.meshID = meshID;
+		}
+
+		public int getMeshID() {
+			return meshID;
+		}
+
+		public static CharterType getCharterTypeByID(int charterID) {
+			CharterType outType = null;
+
+			for (CharterType charterType : CharterType.values()) {
+				if (charterType.charterID == charterID)
+					outType = charterType;
+			}
+			return outType;
+		}
+	}
+
+
+	public enum ChatChannelType {
+		SYSTEM(1),
+		FLASH(2),
+		COMMANDER(3),
+		NATION(5),
+		LEADER(6),
+		SHOUT(7),
+		INFO(10),
+		GUILD(12),
+		INNERCOUNCIL(13),
+		GROUP(14),
+		CITY(15),
+		SAY(16),
+		EMOTE(17),
+		TELL(19),
+		COMBAT(20);
+
+		private final int channelID;
+
+		ChatChannelType(int channelID) {
+			this.channelID = channelID;
+		}
+
+		public int getChannelID() {
+			return this.channelID;
+		}
+	}
+
+	public enum OwnerType {
+		Npc,
+		PlayerCharacter,
+		Account,
+		Mob;
+	}
+
+	public enum SiegePhase {
+		ERRANT,
+		CHALLENGE,
+		STANDOFF,
+		WAR,
+		CEASEFIRE;
+	}
+
+	public enum SiegeResult {
+		PENDING,
+		DEFEND,
+		DESTROY,
+		CAPTURE;
+	}
+
+	public enum RealmType {
+
+		SEAFLOOR(0, 0x000000),
+		JOTUNHEIM(131, 0x006cff),
+		BOGLANDS(184, 0x00b4ff),
+		ESTRAGOTH(213, 0x00ff90),
+		RENNONVALE(232, 0X00ffea),
+		VOLGAARD(56, 0x1e00ff),
+		VOSTRAGOTH(108, 0x245fae),
+		NARROWS(61, 0x2f20a0),
+		FENMARCH(170, 0x3fb5ab),
+		MAELSTROM(63, 0x503e3e),
+		FARRICH(185, 0x52cd98),
+		TYRRANTHMINOR(96, 0x606060),
+		GREYSWATHE(88, 0x6c419d),
+		SUNSANVIL(64, 0x7800ff),
+		THERRONMARCH(206, 0x7bcdef),
+		DYVRENGISLE(119, 0x826b9c),
+		KINGSLUND(60, 0x871a94),
+		OUTERISLES(29, 0xa01313),
+		KAELENSFJORD(165, 0xa0c04a),
+		VARMADAI(95, 0xa16d1b),
+		WESTERMOORE(73, 0xaa3374),
+		OBLIVION(171, 0xababab),
+		SUDRAGOTH(196, 0xbaff00),
+		SKAARTHOL(183, 0xcfc57f),
+		KHALURAM(71, 0xe400ff),
+		VARSHADDUR(132, 0xf2845d),
+		FORBIDDENISLE(18, 0xff0000),
+		PIRATEISLES(48, 0xff008a),
+		SWATHMOORE(66, 0xff4200),
+		ESSENGLUND(130, 0xff9c00),
+		RELGOTH(177, 0xffde00);
+
+		private final int realmID;
+		private final Color color;
+		private static final HashMap<Integer, Integer> _rgbToIDMap = new HashMap<>();
+
+		RealmType(int realmID, int colorRGB) {
+
+			this.realmID = realmID;
+			this.color = new Color(colorRGB);
+
+		}
+
+		public void addToColorMap() {
+			_rgbToIDMap.put(this.color.getRGB(), this.realmID);
+		}
+
+		public static int getRealmIDByRGB(int realmRGB) {
+
+			return _rgbToIDMap.get(realmRGB);
+
+		}
+
+		public int getRealmID() {
+			return realmID;
+		}
+
+		public static RealmType getRealmTypeByUUID(int realmUUID) {
+			RealmType returnType = RealmType.SEAFLOOR;
+
+			for (RealmType realmType : RealmType.values()) {
+
+				if (realmType.realmID == realmUUID)
+					returnType = realmType;
+			}
+			return returnType;
+		}
+	}
+
+	public enum TaxType {
+		PROFIT,
+		WEEKLY,
+		NONE;
+
+	}
+
+	public enum Ruins {
+
+		ESTRAGOTH(569),
+		KARFELL(570),
+		MORELAN(571),
+		REGARS(572),
+		HALLOS(573),
+		WESTERMORE(574),
+		EYWAN(575),
+		CAER(576);
+
+		private final int zoneUUID;
+
+		Ruins(int uuid) {
+			this.zoneUUID = uuid;
+		}
+
+		public int getZoneUUID() {
+			return this.zoneUUID;
+		}
+
+		public Vector3fImmutable getLocation() {
+
+			Zone ruinZone;
+			Vector3fImmutable spawnLocation;
+
+			ruinZone = ZoneManager.getZoneByUUID(this.zoneUUID);
+			spawnLocation = Vector3fImmutable.getRandomPointOnCircle(ruinZone.getLoc(), 30);
+
+			return spawnLocation;
+		}
+
+		public static Ruins getRandomRuin() {
+
+			Ruins ruins;
+
+			ruins = Ruins.values()[ThreadLocalRandom.current()
+					.nextInt(Ruins.values().length)];
+
+			return ruins;
+		}
+
+	}
+
+	public enum Guards {
+
+		HumanArcher(13.97f, 13.97f, 6.19f, 4.2199998f, 18.38f, 6.33f, 6.5f),
+		HumanGuard(13.97f, 13.97f, 6.19f, 4.2199998f, 18.38f, 6.33f, 6.5f),
+		HumanMage(13.97f, 13.97f, 6.19f, 4.2199998f, 18.38f, 6.33f, 6.5f),
+		UndeadArcher(14.67f, 14.67f, 6.5f, 4.44f, 18.38f, 6.33f, 6.5f),
+		UndeadGuard(14.67f, 14.67f, 6.5f, 4.44f, 18.38f, 6.33f, 6.5f),
+		UndeadMage(14.67f, 14.67f, 6.5f, 4.44f, 18.38f, 6.33f, 6.5f);
+
+		private final float runSpeed;
+		private final float runCombatSpeed;
+		private final float walkSpeed;
+		private final float walkCombatSpeed;
+		private final float fly;
+		private final float flyWalk;
+		private final float swim;
+
+		Guards(float runSpeed, float runCombatSpeed, float walkSpeed, float walkCombatSpeed, float fly, float flyWalk, float swim) {
+			this.runSpeed = runSpeed;
+			this.runCombatSpeed = runCombatSpeed;
+			this.walkSpeed = walkSpeed;
+			this.walkCombatSpeed = walkCombatSpeed;
+			this.fly = fly;
+			this.flyWalk = flyWalk;
+			this.swim = swim;
+		}
+
+		public float getRunSpeed() {
+			return runSpeed;
+		}
+
+		public float getRunCombatSpeed() {
+			return runCombatSpeed;
+		}
+
+		public float getWalkSpeed() {
+			return walkSpeed;
+		}
+
+		public float getWalkCombatSpeed() {
+			return walkCombatSpeed;
+		}
+
+		public float getFly() {
+			return fly;
+		}
+
+		public float getSwim() {
+			return swim;
+		}
+
+		public float getFlyWalk() {
+			return flyWalk;
+		}
+	}
+
+	public enum RunegateType {
+
+		EARTH(6f, 19.5f, 128, 33213),
+		AIR(-6f, 19.5f, 256, 33170),
+		FIRE(15f, 7.5f, 512, 49612),
+		WATER(-15f, 8.5f, 1024, 53073),
+		SPIRIT(0, 10.5f, 2048, 33127),
+		CHAOS(22f, 3.5f, 8192, 58093),
+		OBLIV(0f, 42f, 16384, 60198),
+		MERCHANT(-22f, 4.5f, 4096, 60245),
+		FORBID(0.0f, 0.0f, 0, 54617);
+
+		private final Vector2f offset;
+		private final int bitFlag;
+		private final int buildingUUID;
+
+		RunegateType(float offsetX, float offsetY, int bitFlag,
+					 int buildingUUID) {
+
+			this.offset = new Vector2f(offsetX, offsetY);
+			this.bitFlag = bitFlag;
+			this.buildingUUID = buildingUUID;
+		}
+
+		public Vector2f getOffset() {
+			return this.offset;
+		}
+
+		public int getEffectFlag() {
+			return this.bitFlag;
+		}
+
+		public int getGateUUID() {
+			return this.buildingUUID;
+		}
+
+		public Building getGateBuilding() {
+
+			return BuildingManager.getBuilding(this.buildingUUID);
+		}
+
+		public static RunegateType getGateTypeFromUUID(int uuid) {
+
+			RunegateType outType = RunegateType.AIR;
+
+			for (RunegateType gateType : RunegateType.values()) {
+
+				if (gateType.buildingUUID == uuid) {
+					outType = gateType;
+					return outType;
+				}
+
+			}
+
+			return outType;
+		}
+
+	}
+
+	// Enum for ItemBase flags
+
+	public enum ItemType {
+		DECORATION(0),
+		WEAPON(1),
+		ARMOR(2),
+		HAIR(3),
+		GOLD(4),
+		RUNE(5),
+		SCROLL(5),
+		BOOK(6),
+		COMMANDROD(7),
+		POTION(8),
+		TEARS(8),
+		KEY(9),
+		GUILDCHARTER(10),
+		JEWELRY(13),
+		WINE(16),
+		ALEJUG(17),
+		DEED(19),
+		CONTRACT(20),
+		PET(21),
+		FURNITURE(25),
+		BEDROLL(26),
+		FARMABLE(27),
+		WATERBUCKET(30),
+		GIFT(31),
+		OFFERING(33),
+		RESOURCE(34),
+		REALMCHARTER(35);
+
+		private final int _value;
+		private final static HashMap<Integer, ItemType> _typeLookup = new HashMap<>();
+
+		ItemType(int value) {
+			this._value = value;
+		}
+
+		public static ItemType getByValue(int value) {
+
+			ItemType outType = ItemType.DECORATION;
+
+			if (_typeLookup.isEmpty()) {
+
+				for (ItemType itemType : ItemType.values()) {
+					_typeLookup.put(itemType._value, itemType);
+				}
+			}
+
+			if (_typeLookup.containsKey(value))
+				outType = _typeLookup.get(value);
+
+			return outType;
+		}
+
+		/**
+		 * @return the _value
+		 */
+		public int getValue() {
+			return _value;
+		}
+
+	}
+	// Enum to derive effects for active spires from blueprintUUID
+
+	public enum SpireType {
+
+		WATCHFUL(1800100, (1 << 23), -1139520957),
+		GROUNDING(1800400, (1 << 24), -1733819072),
+		BINDING(1800700, (1 << 25), -1971545187),
+		WARDING(1801000, (1 << 26), 2122002462),
+		GUILEFUL(1801300, (1 << 27), -1378972677),
+		BALEFUL(1801600, -1, 1323012132),
+		ARCANE(1801900, (1 << 30), 1323888676),
+		WOUNDING(1802200, (1 << 10), 1357392095),
+		WEARYING(1802500, (1 << 10), 1350838495),
+		CONFUSING(1802800, (1 << 10), 1358702815),
+		CHILLING(1803100, (1 << 1), 1332155165),
+		SEARING(1803400, (1 << 2), -1401744610),
+		THUNDERING(1803700, (1 << 3), -443544829),
+		UNHOLY(1804000, (1 << 4), 1330320167),
+		BEFUDDLING(1804300, (1 << 5), 1489317547),
+		WRATHFUL(1804600, (1 << 6), 165160210),
+		SPITEFUL(1804900, (1 << 7), 1238906779),
+		ENFEEBLING(1805200, (1 << 8), -908578401),
+		CONFOUNDING(1805500, (1 << 9), 165165842),
+		DISTRACTING(1805800, (1 << 10), 1238906697),
+		WOLFPACK(1806100, (1 << 4), 416932375);
+
+		private final int blueprintUUID;
+		private final int effectFlag;
+		private final int token;
+
+		SpireType(int blueprint, int flag, int token) {
+			this.blueprintUUID = blueprint;
+			this.effectFlag = flag;
+			this.token = token;
+		}
+
+		public int getBlueprintUUID() {
+			return blueprintUUID;
+		}
+
+		public int getEffectFlag() {
+			return effectFlag;
+		}
+
+		public int getToken() {
+			return token;
+		}
+
+		public EffectsBase getEffectBase() {
+			return PowersManager.getEffectByToken(token);
+		}
+
+		public static SpireType getByBlueprintUUID(int uuid) {
+
+			SpireType outType = SpireType.GROUNDING;
+
+			for (SpireType spireType : SpireType.values()) {
+
+				if (spireType.blueprintUUID == uuid) {
+					outType = spireType;
+					return outType;
+				}
+
+			}
+
+			return outType;
+		}
+
+	}
+
+	public enum TransactionType {
+		MAINTENANCE(43),
+		WITHDRAWL(80),
+		DEPOSIT(82),
+		MINE(81),
+		MIGRATION(83),
+		PLAYERREWARD(84),
+		TAXRESOURCE(85),
+		TAXRESOURCEDEPOSIT(86);
+
+		private final int ID;
+
+		TransactionType(int ID) {
+			this.ID = ID;
+		}
+
+		public int getID() {
+			return ID;
+		}
+	}
+
+	public enum TargetColor {
+
+		White,
+		Green,
+		Cyan,
+		Blue,
+		Yellow,
+		Orange,
+		Red;
+
+		public static TargetColor getCon(AbstractCharacter source,
+										 AbstractCharacter target) {
+			return getCon(source.getLevel(), target.getLevel());
+		}
+
+		public static TargetColor getCon(short sourceLevel, short targetLevel) {
+			if (targetLevel > (sourceLevel + 2))
+				return Red;
+			else if (targetLevel == (sourceLevel + 2))
+				return Orange;
+			else if (targetLevel == (sourceLevel + 1))
+				return Yellow;
+
+			short lowestBlue = (short) (sourceLevel - (((sourceLevel / 5)) + 2));
+
+			if (lowestBlue <= targetLevel)
+				return Blue;
+			else if (lowestBlue - 1 <= targetLevel)
+				return Cyan;
+			else if (lowestBlue - 2 <= targetLevel)
+				return Green;
+			return White;
+		}
+	}
+
+	public enum DamageType {
+		None,
+		Crush,
+		Slash,
+		Siege,
+		Pierce,
+		Magic,
+		Bleed,
+		Poison,
+		Mental,
+		Holy,
+		Unholy,
+		Lightning,
+		Fire,
+		Cold,
+		Healing,
+		Acid,
+		Disease,
+		Unknown,
+		// these added for immunities
+		Attack,
+		Powers,
+		Combat,
+		Spires,
+		Snare,
+		Stun,
+		Blind,
+		Root,
+		Fear,
+		Charm,
+		PowerBlock,
+		DeBuff,
+		Powerblock,
+		Steel,
+		Drain;
+		public static DamageType GetDamageType(String modName){
+			DamageType damageType;
+			if (modName.isEmpty())
+				return DamageType.None;
+			
+			try{
+				 damageType = DamageType.valueOf(modName.replace(",", ""));
+			}catch(Exception e){
+				Logger.error(e);
+				return DamageType.None;
+			}
+			return damageType;
+		}
+	}
+	
+	
+	public enum SourceType {
+		None,
+		Abjuration,
+		Acid,
+		AntiSiege,
+		Archery,
+		Axe,
+		Bardsong,
+		Beastcraft,
+		Benediction,
+		BladeWeaving,
+		Bleed,
+		Blind,
+		Block,
+		Bloodcraft,
+		Bow,
+		Buff,
+		Channeling,
+		Charm,
+		Cold,
+		COLD,
+		Constitution,
+		Corruption,
+		Crossbow,
+		Crush,
+		Dagger,
+		DaggerMastery,
+		DeBuff,
+		Dexterity,
+		Disease,
+		Dodge,
+		Dragon,
+		Drain,
+		Earth,
+		Effect,
+		Exorcism,
+		Fear,
+		Fire,
+		FIRE,
+		Fly,
+		Giant,
+		GreatAxeMastery,
+		GreatSwordMastery,
+		Hammer,
+		Heal,
+		Healing,
+		Holy,
+		HOLY,
+		ImmuneToAttack,
+		ImmuneToPowers,
+		Intelligence,
+		Invisible,
+		Lightning,
+		LIGHTNING,
+		Liturgy,
+		Magic,
+		MAGIC,
+		Mental,
+		MENTAL,
+		NatureLore,
+		Necromancy,
+		Parry,
+		Pierce,
+		Poison,
+		POISON,
+		PoleArm,
+		Powerblock,
+		Rat,
+		ResistDeBuff,
+		Restoration,
+		Root,
+		Shadowmastery,
+		Siege,
+		Slash,
+		Snare,
+		Sorcery,
+		Spear,
+		SpearMastery,
+		Spirit,
+		Staff,
+		Stormcalling,
+		Strength,
+		Stun,
+		Summon,
+		Sword,
+		SwordMastery,
+		Thaumaturgy,
+		Theurgy,
+		Transform,
+		UnarmedCombat,
+		UnarmedCombatMastery,
+		Unholy,
+		UNHOLY,
+		Unknown,
+		Warding,
+		Warlockry,
+		WayoftheGaana,
+		WearArmorHeavy,
+		WearArmorLight,
+		WearArmorMedium,
+		Wereform,
+		Athletics,
+		AxeMastery,
+		Bargaining,
+		BladeMastery,
+		FlameCalling,
+		GreatHammerMastery,
+		HammerMastery,
+		Leadership,
+		PoleArmMastery,
+		Running,
+		StaffMastery,
+		Throwing,
+		Toughness,
+		WayoftheWolf,
+		WayoftheRat,
+		WayoftheBear,
+		Orthanatos,
+		SunDancing,
+		//Power categories.
+		AE,
+		AEDAMAGE,
+		BEHAVIOR,
+		BLESSING,
+		BOONCLASS,
+		BOONRACE,
+		BREAKFLY,
+		BUFF,
+		CHANT,
+		DAMAGE,
+		DEBUFF,
+		DISPEL,
+		FLIGHT,
+		GROUPBUFF,
+		GROUPHEAL,
+		HEAL,
+		INVIS,
+		MOVE,
+		RECALL,
+		SPECIAL,
+		SPIREDISABLE,
+		SPIREPROOFTELEPORT,
+		STANCE,
+		STUN,
+		SUMMON,
+		TELEPORT,
+		THIEF,
+		TRACK,
+		TRANSFORM,
+		VAMPDRAIN,
+		WEAPON,
+		Wizardry;
+		public static SourceType GetSourceType(String modName){
+			SourceType returnMod;
+			if(modName.isEmpty())
+				return SourceType.None;
+			
+			try{
+				 returnMod = SourceType.valueOf(modName.replace(",", ""));
+			}catch(Exception e){
+				Logger.error(modName);
+				Logger.error(e);
+				return SourceType.None;
+			}
+			return returnMod;
+		}
+	}
+	
+	public enum EffectSourceType{
+		None,
+		AttackSpeedBuff,
+		Bleeding,
+		Blind,
+		Buff,
+		Chant,
+		Charm,
+		Cold,
+		Combat,
+		ConstitutionBuff,
+		Crush,
+		DamageShield,
+		DeathShroud,
+		DeBuff,
+		Disease,
+		Drain,
+		Earth,
+		Effect,
+		Fear,
+		Fire,
+		Flight,
+		Fortitude,
+		Heal,
+		Holy,
+		Invisibility,
+		Invulnerability,
+		Lightning,
+		Magic,
+		Mental,
+		Multielement,
+		PetBuff,
+		Pierce,
+		Poison,
+		Powerblock,
+		RecoveryManaBuff,
+		ResistDeBuff,
+		Root,
+		Siege,
+		SiegeBuff,
+		SiegeDamage,
+		Silence,
+		Slash,
+		Snare,
+		Stance,
+		Stun,
+		Summon,
+		Transform,
+		Unholy,
+		Wereform,
+		WereformATRBuff,
+		WereformConBuff,
+		WereformDexBuff,
+		WereformHPRecBuff,
+		WereformMoveBuff,
+		WereformPhysResBuff,
+		WereformSPRecBuff,
+		WereformStrBuff;
+		
+		public static EffectSourceType GetEffectSourceType(String modName){
+			EffectSourceType returnMod;
+			if(modName.isEmpty())
+				return EffectSourceType.None;
+			
+			try{
+				 returnMod = EffectSourceType.valueOf(modName.replace(",", ""));
+			}catch(Exception e){
+				Logger.error(e);
+				return EffectSourceType.None;
+			}
+			return returnMod;
+		}
+	}
+	
+	public enum StackType {
+		None,
+		AggRangeDeBuff,
+		ArcheryPrecisionBuff,
+		AttackDebuff,
+		AttackSpeedBuff,
+		AttackSpeedDeBuff,
+		AttackValueBuff,
+		AttackValueDebuff,
+		AttrCONBuff,
+		AttrCONDebuff,
+		AttrDEXBuff,
+		AttrINTBuff,
+		AttrSPRBuff,
+		AttrSTRBuff,
+		Bleeding,
+		Blindness,
+		BluntResistanceDebuff,
+		BMHealing,
+		Charm,
+		ClassBoon,
+		Confusion,
+		DamageAbsorber,
+		DamageDebuff,
+		DamageModifierBuff,
+		DamageShield,
+		DeathShroud,
+		DefenseBuff,
+		DefenseBuffGroup,
+		DefenseDebuff,
+		DetectInvis,
+		DrainImmunity,
+		ElementalDeBuff,
+		EnchantWeapon,
+		Fear,
+		Flight,
+		Frenzy,
+		GroupHeal,
+		HealingBuff,
+		HealOverTime,
+		HealResBuff,
+		HealthPotion,
+		IgnoreStack,
+		Invisible,
+		ManaPotion,
+		MangonelFire,
+		MeleeDamageDeBuff,
+		MeleeDeBuff,
+		MoveBuff,
+		MoveDebuff,
+		NoFear,
+		NoPassiveDefense,
+		NoPowerBlock,
+		NoPowerInhibitor,
+		NoRecall,
+		NoSnare,
+		NoStun,
+		NoTrack,
+		PassiveDefense,
+		PersAttrSPRBuff,
+		PetBuff,
+		PierceResistanceDebuff,
+		PoisonBuchinine,
+		PoisonGalpa,
+		PoisonGorgonsVenom,
+		PoisonMagusbane,
+		PoisonPellegorn,
+		PowerBlock,
+		PowerCostBuff,
+		PowerDamageModifierBuff,
+		PowerInhibitor,
+		PrecisionBuff,
+		Protection,
+		RaceBoon,
+		RecoveryHealthBuff,
+		RecoveryHealthDeBuff,
+		RecoveryManaBuff,
+		RecoveryManaDeBuff,
+		RecoveryStaminaBuff,
+		RecoveryStaminaDeBuff,
+		ResistanceBuff,
+		ResistanceDeBuff,
+		ResistanceDebuff,
+		Root,
+		SafeMode,
+		SelfOneAttrBuff,
+		SelfThreeAttrBuff,
+		SelfTwoAttrBuff,
+		SiegeDebuff,
+		SiegeWeaponBuff,
+		Silence,
+		SkillDebuff,
+		SlashResistanceDebuff,
+		Snare,
+		StackableAttrCONBuff,
+		StackableAttrDEXBuff,
+		StackableAttrSTRBuff,
+		StackableDefenseBuff,
+		StackableRecoveryHealthBuff,
+		StackableRecoveryStaminaBuff,
+		StaminaPotion,
+		StanceA,
+		StanceB,
+		Stun,
+		Track,
+		Transform,
+		WeaponMove;
+		public static StackType GetStackType(String modName){
+			StackType stackType;
+			if (modName.isEmpty())
+				return StackType.None;
+			
+			try{
+				 stackType = StackType.valueOf(modName.replace(",", ""));
+			}catch(Exception e){
+				Logger.error(modName);
+				Logger.error(e);
+				return StackType.None;
+			}
+			return stackType;
+		}
+	}
+	
+	public enum ModType {
+		None,
+		AdjustAboveDmgCap,
+		Ambidexterity,
+		AnimOverride,
+		ArmorPiercing,
+		AttackDelay,
+		Attr,
+		BlackMantle,
+		BladeTrails,
+		Block,
+		BlockedPowerType,
+		CannotAttack,
+		CannotCast,
+		CannotMove,
+		CannotTrack,
+		Charmed,
+		ConstrainedAmbidexterity,
+		DamageCap,
+		DamageShield,
+		DCV,
+		Dodge,
+		DR,
+		Durability,
+		ExclusiveDamageCap,
+		Fade,
+		Fly,
+		Health,
+		HealthFull,
+		HealthRecoverRate,
+		IgnoreDamageCap,
+		IgnorePassiveDefense,
+		ImmuneTo,
+		ImmuneToAttack,
+		ImmuneToPowers,
+		Invisible,
+		ItemName,
+		Mana,
+		ManaFull,
+		ManaRecoverRate,
+		MaxDamage,
+		MeleeDamageModifier,
+		MinDamage,
+		NoMod,
+		OCV,
+		Parry,
+		PassiveDefense,
+		PowerCost,
+		PowerCostHealth,
+		PowerDamageModifier,
+		ProtectionFrom,
+		Resistance,
+		ScaleHeight,
+		ScaleWidth,
+		ScanRange,
+		SeeInvisible,
+		Silenced,
+		Skill,
+		Slay,
+		Speed,
+		SpireBlock,
+		Stamina,
+		StaminaFull,
+		StaminaRecoverRate,
+		Stunned,
+		Value,
+		WeaponProc,
+		WeaponRange,
+		WeaponSpeed;
+		
+		public static ModType GetModType(String modName){
+			ModType modType;
+			if (modName.isEmpty())
+				return ModType.None;
+			
+			try{
+				 modType = ModType.valueOf(modName.replace(",", ""));
+			}catch(Exception e){
+				Logger.error(e);
+				return ModType.None;
+			}
+			return modType;
+		}
+	}
+	public enum MovementState {
+
+		IDLE,
+		SITTING,
+		RUNNING,
+		FLYING,
+		SWIMMING;
+	}
+
+	public enum DoorState {
+
+		OPEN,
+		CLOSED,
+		LOCKED,
+		UNLOCKED;
+	}
+
+	// Used with stored procedure GET_UID_ENUM() for
+	// type tests against objects not yet loaded into the game.
+	public enum DbObjectType {
+
+		INVALID,
+		ACCOUNT,
+		BUILDING,
+		CHARACTER,
+		CITY,
+		CONTAINER,
+		GUILD,
+		ITEM,
+		MINE,
+		MOB,
+		NPC,
+		SHRINE,
+		WORLDSERVER,
+		ZONE,
+		WAREHOUSE;
+	}
+
+	;
+
+	/**
+	 * Enumeration of Building Protection Status stored in the database as a
+	 * mysql enumfield. WARNING: This enumeration is fragile. Do not rename. Do
+	 * not reorder.
+	 */
+	public enum ProtectionState {
+
+		NONE,
+		PROTECTED,
+		UNDERSIEGE,
+		CEASEFIRE,
+		CONTRACT,
+		DESTROYED,
+		PENDING,
+		NPC;
+	}
+
+	;
+
+	public enum CharacterSkills {
+
+		Archery((1L << 1), -529201545, 20),
+		Athletics((1L << 2), -327713877, 15),
+		AxeMastery((1L << 3), 1103042709, 20),
+		Axe((1L << 4), 73505, 1),
+		Bardsong((1L << 5), 454246953, 10),
+		Bargaining((1L << 6), 372927577, 10),
+		Beastcraft((1L << 7), 56772766, 10),
+		Benediction((1L << 8), 1464998706, 1),
+		BladeMastery((1L << 9), -59908956, 20),
+		BladeWeaving((1L << 10), -1839362429, 20),
+		Block((1L << 11), 76592546, 3),
+		Bow((1L << 12), 87490, 1),
+		Channeling((1L << 13), -1899060872, 20),
+		Crossbow((1L << 14), 1092138184, 1),
+		DaggerMastery((1L << 15), -1549224741, 20),
+		Dagger((1L << 16), -1603103740, 1),
+		Dodge((1L << 17), 74619332, 5),
+		FlameCalling((1L << 18), -1839578206, 20),
+		GreatAxeMastery((1L << 19), 1427003458, 20),
+		GreatHammerMastery((1L << 20), -309659310, 20),
+		GreatSwordMastery((1L << 21), 2054956946, 20),
+		HammerMastery((1L << 22), -1548903209, 20),
+		Hammer((1L << 23), -1602765816, 1),
+		Leadership((1L << 24), 1618560984, 20),
+		Liturgy((1L << 25), -888415974, 10),
+		NatureLore((1L << 26), -1911171474, 10),
+		Parry((1L << 27), 95961104, 5),
+		PoleArmMastery((1L << 28), -1432303709, 20),
+		PoleArm((1L << 29), -1037845588, 1),
+		Restoration((1L << 30), -504697054, 1),
+		Running((1L << 31), 1488335491, 10),
+		Shadowmastery((1L << 32), 1389222957, 10),
+		Sorcery((1L << 33), -529481275, 1),
+		SpearMastery((1L << 34), -48279755, 20),
+		Spear((1L << 35), 83992115, 1),
+		StaffMastery((1L << 36), -61022283, 20),
+		Staff((1L << 37), 71438003, 1),
+		Stormcalling((1L << 38), -532064061, 10),
+		SwordMastery((1L << 39), -59316267, 20),
+		Sword((1L << 40), 73938643, 1),
+		Thaumaturgy((1L << 41), -2020131447, 10),
+		Theurgy((1L << 42), -888431326, 10),
+		Throwing((1L << 43), 391562015, 20),
+		Toughness((1L << 44), -660435875, 10),
+		UnarmedCombatMastery((1L << 45), 1692733771, 20),
+		UnarmedCombat((1L << 46), -1094332856, 1),
+		Warding((1L << 47), 1488142342, 1),
+		Warlockry((1L << 48), 1121393557, 10),
+		WayoftheGaana((1L << 49), -1954832975, 10),
+		WearArmorHeavy((1L << 50), 1112121635, 15),
+		WearArmorLight((1L << 51), 38031547, 1),
+		WearArmorMedium((1L << 52), 468015203, 5),
+		Wizardry((1L << 53), 218227659, 10),
+		Corruption((1L << 54), -1519268706, 10),
+		Abjuration((1L << 55), -2029900484, 10),
+		WayoftheWolf((1L << 56), 1668913067, 20),
+		WayoftheRat((1L << 57), -2114353637, 20),
+		WayoftheBear((1L << 58), -906390863, 20),
+		Orthanatos((1L << 59), -666929185, 20),
+		Bloodcraft((1L << 60), 40661438, 10),
+		Exorcism((1L << 61), 1444427097, 10),
+		Necromancy((1L << 62), -556571154, 10),
+		SunDancing((1L << 63), 22329752, 20);
+
+		private long flag;
+		private int token;
+		private int reqLvl;
+
+		CharacterSkills(long flag, int token, int reqLvl) {
+			this.flag = flag;
+			this.token = token;
+			this.reqLvl = reqLvl;
+		}
+
+		public long getFlag() {
+			return flag;
+		}
+
+		public int getReqLvl() {
+			return this.reqLvl;
+		}
+
+		public void setFlag(long flag) {
+			this.flag = flag;
+		}
+
+		public int getToken() {
+			return token;
+		}
+
+		public void setToken(int token) {
+			this.token = token;
+		}
+
+		public static CharacterSkills GetCharacterSkillByToken(int token) {
+			for (CharacterSkills skill : CharacterSkills.values()) {
+				if (skill.token == token)
+					return skill;
+			}
+
+			Logger.info("Returned No Skill for token " + token + ". Defaulting to Axe");
+			return CharacterSkills.Axe;
+		}
+	}
+
+	;
+
+	public enum GuildHistoryType {
+		JOIN(1),
+		LEAVE(4),
+		BANISHED(3),
+		CREATE(7),
+		DISBAND(5);
+		private final int type;
+
+		GuildHistoryType(int type) {
+			this.type = type;
+		}
+
+		public int getType() {
+			return type;
+		}
+	}
+
+	public enum SexType {
+		NONE,
+		MALE,
+		FEMALE;
+	}
+
+	public enum ClassType {
+		FIGHTER,
+		HEALER,
+		ROGUE,
+		MAGE;
+	}
+
+	public enum PromoteType {
+		Assassin(SexType.NONE),
+		Barbarian(SexType.NONE),
+		Bard(SexType.NONE),
+		Channeler(SexType.NONE),
+		Confessor(SexType.NONE),
+		Crusader(SexType.NONE),
+		Doomsayer(SexType.NONE),
+		Druid(SexType.NONE),
+		Fury(SexType.FEMALE),
+		Huntress(SexType.FEMALE),
+		Prelate(SexType.NONE),
+		Priest(SexType.NONE),
+		Ranger(SexType.NONE),
+		Scout(SexType.NONE),
+		Sentinel(SexType.NONE),
+		Templar(SexType.NONE),
+		Thief(SexType.NONE),
+		Warlock(SexType.MALE),
+		Warrior(SexType.NONE),
+		Wizard(SexType.NONE),
+		Nightstalker(SexType.NONE),
+		Necromancer(SexType.NONE),;
+
+		private SexType sexRestriction;
+
+		PromoteType(SexType sexRestriction) {
+			this.sexRestriction = sexRestriction;
+		}
+
+		public SexType getSexRestriction() {
+			return sexRestriction;
+		}
+	}
+
+	public enum ShrineType {
+
+		Aelfborn(1701900, -75506007, true),
+		Aracoix(1703100, -563708986, true),
+		Centaur(1704000, 521645243, true),
+		Dwarf(1708500, -2000467257, true),
+		Elf(1703400, 1254603001, true),
+		HalfGiant(1709100, 349844468, true),
+		Human(1702200, 281172391, true),
+		Irekei(1702800, -764988442, true),
+		Minotaur(1704600, 549787579, true),
+		Nephilim(1701000, -655183799, true),
+		Shade(1700100, 1724071104, true),
+		Assassin(1700400, 1989015892, false),
+		Barbarian(1708800, 9157124, false),
+		Bard(1704300, 80190554, false),
+		Channeler(1702500, 5658278, false),
+		Confessor(1707600, 1871658719, false),
+		Crusader(1706700, -187454619, false),
+		Doomsayer(1700700, -993659433, false),
+		Druid(1701600, -926740122, false),
+		Fury(1705500, 214401375, false),
+		Huntress(1704900, 970312892, false),
+		Prelate(1707000, -225200922, false),
+		Priest(1705200, -535691898, false),
+		Ranger(1701300, 604716986, false),
+		Scout(1706100, -1497297486, false),
+		Sentinel(1707300, -184898375, false),
+		Templar(1707900, 826673315, false),
+		Thief(1708200, 1757633920, false),
+		Warlock(1706400, 1003385946, false),
+		Warrior(1703700, 931048026, false),
+		Wizard(1705800, 777115928, false),
+		Nightstalker(1709400, 373174890, false),
+		Necromancer(1709700, -319294505, false),
+		Vampire(1710000, 1049274530, true);
+
+		private final int blueprintUUID;
+		private final int powerToken;
+		private final ArrayList<Shrine> shrines = new ArrayList<>();
+		private final boolean isRace;
+
+		ShrineType(int blueprintUUID, int powerToken, boolean isRace) {
+			this.blueprintUUID = blueprintUUID;
+			this.powerToken = powerToken;
+			this.isRace = isRace;
+
+		}
+
+		public int getBlueprintUUID() {
+			return blueprintUUID;
+		}
+
+		public int getPowerToken() {
+			return powerToken;
+		}
+
+		public ArrayList<Shrine> getShrinesCopy() {
+			ArrayList<Shrine> copyShrines = new ArrayList<>();
+			copyShrines.addAll(shrines);
+			Collections.sort(copyShrines);
+			return copyShrines;
+		}
+
+		public final void addShrineToServerList(Shrine shrine) {
+			synchronized (shrines) {
+				shrines.add(shrine);
+			}
+		}
+
+		public final void RemoveShrineFromServerList(Shrine shrine) {
+			synchronized (shrines) {
+				shrines.remove(shrine);
+			}
+		}
+
+		public boolean isRace() {
+			return isRace;
+		}
+	}
+
+	public enum GuildState {
+
+		Errant(0),
+		Sworn(4),
+		Protectorate(6),
+		Petitioner(2),
+		Province(8),
+		Nation(5),
+		Sovereign(7);
+
+		private final int stateID;
+
+		GuildState(int stateID) {
+			this.stateID = stateID;
+		}
+
+		public int getStateID() {
+			return stateID;
+		}
+
+	}
+
+
+	// Building group enumeration.
+	// This is used to drive linear equations to calculate
+	// structure hp, ranking times and such from within
+	// the BuildingBlueprint class.
+	//
+	// It is also used as a bitvector flag in the npc
+	// building slot mechanics.
+
+	public enum BuildingGroup implements EnumBitSetHelper<BuildingGroup> {
+		NONE(0,0),
+		TOL(64f, 64f),
+		BARRACK(32f, 64f),
+		CHURCH(64f, 64f),
+		FORGE(32f, 64f),
+		SPIRE(16f, 16f),
+		GENERICNOUPGRADE(16f, 16f),
+		WALLSTRAIGHT(16f, 64),
+		WALLCORNER(64f, 64f),
+		SMALLGATE(64f, 64),
+		ARTYTOWER(64f, 64),
+		SIEGETENT(32f, 32f),
+		BANESTONE(16f, 16f),
+		MINE(16f, 16f),
+		WAREHOUSE(32f, 32f),
+		SHRINE(16f, 16f),
+		RUNEGATE(64f, 64f),
+		AMAZONHALL(64f, 64f),
+		CATHEDRAL(64f, 64f),
+		GREATHALL(64f, 64f),
+		KEEP(64f, 64f),
+		THIEFHALL(64f, 24f),
+		TEMPLEHALL(64f, 64f),
+		WIZARDHALL(64f, 64f),
+		ELVENHALL(64f, 64f),
+		ELVENSANCTUM(64f, 64f),
+		IREKEIHALL(64f, 64f),
+		FORESTHALL(64f, 64f),
+		MAGICSHOP(32f, 32f),
+		BULWARK(32f, 32f),
+		SHACK(16f, 16f),
+		INN(64f, 32f),
+		TAILOR(32f, 32f),
+		VILLA(64f, 32f),
+		ESTATE(64f, 64f),
+		FORTRESS(64f, 64f),
+		CITADEL(64f, 64f),
+		WALLSTRAIGHTTOWER(16f, 64),
+		WALLSTAIRS(64,64);
+		
+		private final Vector2f extents;
+
+		BuildingGroup(float extentX, float extentY) {
+			this.extents = new Vector2f(extentX, extentY);
+		}
+
+		public Vector2f getExtents() {
+			return extents;
+		}
+
+	}
+	
+	public enum UpdateType{
+		ALL,
+		MOVEMENT,
+		REGEN,
+		FLIGHT,
+		LOCATION,
+		MOVEMENTSTATE;
+	}
+	
+	public enum ServerType{
+		WORLDSERVER,
+		LOGINSERVER,
+		NONE;
+	}
+	
+	public enum ChatChannel implements EnumBitSetHelper<ChatChannel> {
+		System,
+		Announce,
+		Unknown,
+		Commander,
+		Address,
+		Nation,
+		Leader,
+		Shout,
+		Siege,
+		Territory,
+		Info,
+		CSR,
+		Guild,
+		InnerCouncil,
+		Group,
+		City,
+		Say,
+		Emote,
+		Social,
+		Tell,
+		Combat,
+		Powers,
+		Snoop,
+		Debug,
+		Global,
+		Trade,
+		PVP,
+		Mine,
+		Alert,
+		Assassin,
+		Barbarian,
+		Bard,
+		Channeler,
+		Confessor,
+		Crusader,
+		Doomsayer,
+		Druid,
+		Fury,
+		Huntress,
+		Necromancer,
+		Nightstalker,
+		Prelate,
+		Priest,
+		Ranger,
+		Scout,
+		Sentinel,
+		Templar,
+		Thief,
+		Warlock,
+		Warrior,
+		Wizard;
+
+	}
+
+	public enum AllianceType {
+		RecommendedAlly,
+		RecommendedEnemy,
+		Ally,
+		Enemy;
+	}
+	
+	public enum FriendStatus {
+		Available,
+		Away,
+		Busy;
+	}
+	
+	public enum ProfitType {
+		
+		
+		BuyNormal("buy_normal"),
+		BuyGuild("buy_guild"),
+		BuyNation("buy_nation"),
+		SellNormal("sell_normal"),
+		SellGuild("sell_guild"),
+		SellNation("sell_nation");
+		
+		public String dbField;
+
+		private ProfitType(String dbField) {
+			this.dbField = dbField;
+		}
+	}
+
+	public enum GameObjectType {
+
+		/*
+		 * These will be used as the 4 high bytes in the application protocol's
+		 * long CompositeID field and when tracking an AbstractGameObject's type
+		 * from within the code. The low 4 bytes will be used as the Object's
+		 * UUID
+		 */
+		unknown,
+		Account,
+		AccountIP,
+		ActiveEffect,
+		ArmorBase,
+		BaseClass,
+		BeardStyle,
+		BlockedIP,
+		Building,
+		BuildingLocation,
+		BuildingModelBase,
+		CharacterPower,
+		CharacterPowers,
+		CharacterRune,
+		CharacterSkill,
+		City,
+		Contract,
+		Corpse,
+		CSSession,
+		EffectsResourceCosts,
+		EnchantmentBase,
+		GenericItemBase,
+		Group,
+		Guild,
+		GuildAllianceEnemy,
+		GuildBanish,
+		GuildCharacterKOS,
+		GuildGuildKOS,
+		GuildTableList,
+		HairStyle,
+		Item,
+		ItemContainer,
+		ItemEnchantment,
+		JewelryBase,
+		Kit,
+		MenuOption,
+		Mine,
+		Mob,
+		MobBase,
+		MobEquipment,
+		MobLoot,
+		MobType,
+		NPC,
+		NPCClassRune,
+		NPCClassRuneThree,
+		NPCClassRuneTwo,
+		NPCExtraRune,
+		NPCRaceRune,
+		NPCRune,
+		NPCShopkeeperRune,
+		NPCTrainerRune,
+		Nation,
+		PlayerCharacter,
+		PlayerInfo,
+		PowerGrant,
+		PowerReq,
+		PowersBase,
+		PowersBaseAttribute,
+		PromotionClass,
+		Race,
+		RuneBase,
+		RuneBaseAttribute,
+		RuneBaseEffect,
+		SkillReq,
+		SkillsBase,
+		SkillsBaseAttribute,
+		SpecialLoot,
+		StrongBox,
+		Trigger,
+		ValidRaceBeardStyle,
+		ValidRaceClassCombo,
+		ValidRaceHairStyle,
+		VendorDialog,
+		Warehouse,
+		WeaponBase,
+		WorldServerInfo,
+		WorldServerInfoSnapshot,
+		Shrine,
+		Zone,
+		Transaction;
+	}
+
+	public enum ContainerType {
+		BANK,
+		INVENTORY,
+		VAULT;
+	}
+
+	;
+
+	public enum CompoundCurveType {
+		DefaultFlat(0),
+		DefaultSlope(1),
+		DefaultSlopeDown(-1),
+		SL0001Up(0.01),
+		SL0003Up(0.03),
+		SL0005Up(0.05),
+		SL0006Up(0.06),
+		SL0007Up(0.07),
+		SL0008Up(0.08),
+		SL0010Up(0.10),
+		SL0011Up(0.11),
+		SL0012Up(0.12),
+		SL0013Up(0.13),
+		SL0014Up(0.14),
+		SL00143U(0.143),
+		SL0015Up(0.15),
+		SL0016Up(0.16),
+		SL0019Up(0.19),
+		SL0020Up(0.20),
+		SL0021Up(0.21),
+		SL0022Up(0.22),
+		SL0023Up(0.23),
+		SL0024Up(0.24),
+		SL0025Up(0.25),
+		SL0026Up(0.26),
+		SL0028Up(0.28),
+		SL0030Up(0.30),
+		SL0031Up(0.31),
+		SL0032Up(0.32),
+		SL0033Up(0.33),
+		SL0034Up(0.34),
+		SL0035Up(0.35),
+		SL0037Up(0.37),
+		SL0038Up(0.38),
+		SL0039Up(0.39),
+		SL0040Up(0.40),
+		SL0041Up(0.41),
+		SL0042Up(0.42),
+		SL0043Up(0.43),
+		SL0044Up(0.44),
+		SL0045Up(0.45),
+		SL0046Up(0.46),
+		SL0047Up(0.47),
+		SL0048Up(0.48),
+		SL0050Up(0.50),
+		SL0051Up(0.51),
+		SL0053Up(0.53),
+		SL0054Up(0.54),
+		SL0055Up(0.55),
+		SL0056Up(0.56),
+		SL0057Up(0.57),
+		SL0058Up(0.58),
+		SL0060Up(0.60),
+		SL0061Up(0.61),
+		SL0063Up(0.63),
+		SL0064Up(0.64),
+		SL0065Up(0.65),
+		SL0066Up(0.66),
+		SL0067Up(0.67),
+		SL0068Up(0.68),
+		SL0069Up(0.69),
+		SL0070Up(0.70),
+		SL0071Up(0.71),
+		SL0073Up(0.73),
+		SL0074Up(0.74),
+		SL0075Up(0.75),
+		SL0076Up(0.76),
+		SL0077Up(0.77),
+		SL0079Up(0.79),
+		SL0080Up(0.80),
+		SL0081Up(0.81),
+		SL0082Up(0.82),
+		SL0083Up(0.83),
+		SL0084Up(0.84),
+		SL0085Up(0.85),
+		SL0087Up(0.87),
+		SL0088Up(0.88),
+		SL0089Up(0.89),
+		SL0090Up(0.90),
+		SL0092Up(0.92),
+		SL0098Up(0.98),
+		SL0100Up(1.00),
+		SL0106Up(1.06),
+		SL0109Up(1.09),
+		SL0112Up(1.12),
+		SL0113Up(1.13),
+		SL0115Up(1.15),
+		SL0116Up(1.16),
+		SL0122Up(1.22),
+		SL0123Up(1.23),
+		SL0125Up(1.25),
+		SL0128Up(1.28),
+		SL0130Up(1.30),
+		SL0135Up(1.35),
+		SL0140Up(1.40),
+		SL0143Up(1.43),
+		SL0145Up(1.45),
+		SL0150Up(1.50),
+		SL0154Up(1.54),
+		SL0163Up(1.63),
+		SL0166Up(1.66),
+		SL0175Up(1.75),
+		SL0188Up(1.88),
+		SL0190Up(1.90),
+		SL0200Up(2.00),
+		SL0222Up(2.22),
+		SL0225Up(2.25),
+		SL0235Up(2.35),
+		SL0238Up(2.38),
+		SL0250Up(2.50),
+		SL0260Up(2.60),
+		SL0263Up(2.63),
+		SL0275Up(2.75),
+		SL0280Up(2.80),
+		SL0300Up(3.00),
+		SL0308Up(3.08),
+		SL0312Up(3.12),
+		SL0350Up(3.50),
+		SL0357Up(3.57),
+		SL0360Up(3.60),
+		SL0375Up(3.75),
+		SL0380Up(3.80),
+		SL0385Up(3.85),
+		SL0400Up(4.00),
+		SL0410Up(4.10),
+		SL0429Up(4.29),
+		SL0450Up(4.50),
+		SL0460Up(4.60),
+		SL0480Up(4.80),
+		SL0500Up(5.00),
+		SL0510Up(5.10),
+		SL0550Up(5.50),
+		SL0600Up(6.00),
+		SL0643Up(6.43),
+		SL0714Up(7.14),
+		SL0750Up(7.50),
+		SL0790Up(7.90),
+		SL0800Up(8.00),
+		SL0900Up(9.00),
+		SL1000Up(10.00),
+		SL1050Up(10.50),
+		SL1100Up(11.00),
+		SL1125Up(11.25),
+		SL1200Up(12.00),
+		SL1282Up(12.82),
+		SL1300Up(13.00),
+		SL1350Up(13.50),
+		SL1400Up(14.00),
+		SL1500Up(15.00),
+		SL1579Up(15.79),
+		SL2000Up(20.00),
+		SL2100Up(21.00),
+		SL2500Up(25.00),
+		SL2521Up(25.21),
+		SL3000Up(30.00),
+		SL4000Up(40.00),
+		SL5000Up(50.00),
+		SL6000Up(60.00),
+		SL7500Up(75.00),
+		SL8000Up(80.00),
+		SL12000Up(120.00),
+		SL14000Up(140.00),
+		SL30000Up(300.00),
+		SL66600Up(666.00),
+		SL71500Up(715.00),
+		SL00003Down(-0.003),
+		SL0001Down(-0.01),
+		SL0003Down(-0.03),
+		SL0004Down(-0.04),
+		SL0005Down(-0.05),
+		SL0006Down(-0.06),
+		SL0007Down(-0.07),
+		SL00075Down(-0.075),
+		SL0008Down(-0.08),
+		SL0009Down(-0.09),
+		SL0010Down(-0.10),
+		SL0011Down(-0.11),
+		SL0012Down(-0.12),
+		SL0013Down(-0.13),
+		SL00125Down(-0.125),
+		SL0014Down(-0.14),
+		SL0015Down(-0.15),
+		SL0016Down(-0.16),
+		SL0017Down(-0.17),
+		SL00175Down(-0.175),
+		SL0018Down(-0.18),
+		SL0019Down(-0.19),
+		SL0020Down(-0.20),
+		SL0023Down(-0.23),
+		SL0024Down(-0.24),
+		SL0025Down(-0.25),
+		SL0027Down(-0.27),
+		SL0028Down(-0.28),
+		SL0029Down(-0.29),
+		SL0030Down(-0.30),
+		SL0032Down(-0.32),
+		SL0033Down(-0.33),
+		SL0035Down(-0.35),
+		SL0038Down(-0.38),
+		SL0040Down(-0.40),
+		SL0044Down(-0.44),
+		SL0045Down(-0.45),
+		SL0050Down(-0.50),
+		SL0055Down(-0.55),
+		SL0060Down(-0.60),
+		SL0062Down(-0.62),
+		SL0063Down(-0.63),
+		SL0064Down(-0.64),
+		SL0066Down(-0.66),
+		SL0069Down(-0.69),
+		SL0071Down(-0.71),
+		SL0075Down(-0.75),
+		SL0077Down(-0.77),
+		SL0079Down(-0.79),
+		SL0080Down(-0.80),
+		SL0090Down(-0.90),
+		SL0100Down(-1.00),
+		SL0113Down(-1.13),
+		SL0120Down(-1.20),
+		SL0125Down(-1.25),
+		SL0128Down(-1.28),
+		SL0130Down(-1.30),
+		SL0135Down(-1.35),
+		SL0150Down(-1.50),
+		SL0175Down(-1.75),
+		SL0188Down(-1.88),
+		SL0200Down(-2.00),
+		SL0225Down(-2.25),
+		SL0250Down(-2.50),
+		SL0263Down(-2.63),
+		SL0300Down(-3.00),
+		SL0357Down(-3.57),
+		SL0385Down(-3.85),
+		SL0429Down(-4.29),
+		SL0450Down(-4.50),
+		SL0500Down(-5.00),
+		SL0550Down(-5.50),
+		SL0600Down(-6.00),
+		SL0643Down(-6.43),
+		SL0714Down(-7.14),
+		SL0750Down(-7.50),
+		SL0790Down(-7.90),
+		SL0800Down(-8.00),
+		SL1000Down(-10.00),
+		SL1050Down(-10.50),
+		SL1200Down(-12.00),
+		SL1350Down(-13.50),
+		SL1500Down(-15.00),
+		SL1579Down(-15.79),
+		SL2000Down(-20.00),
+		SL2400Down(-24.00),
+		SL2500Down(-25.00),
+		SL3000Down(-30.00),
+		SL4500Down(-45.00),
+		SL7500Down(-75.00),
+		SIVL0005(0.005),
+		SIVL0008(0.008),
+		SIVL0009(0.009),
+		SIVL0010(0.010),
+		SIVL0012(0.012),
+		SIVL0013(0.013),
+		SIVL0014(0.014),
+		SIVL0015(0.015),
+		SIVL0016(0.016),
+		SIVL0017(0.017),
+		SIVL0019(0.019),
+		SIVL0020(0.020),
+		SIVL0021(0.021),
+		SIVL0022(0.022),
+		SIVL0023(0.023),
+		SIVL0024(0.024),
+		SIVL0025(0.025),
+		SIVL0026(0.026),
+		SIVL0027(0.027),
+		SIVL0029(0.029),
+		SIVL0030(0.030),
+		SIVL0031(0.031),
+		SIVL0032(0.032),
+		SIVL0033(0.033),
+		SIVL0034(0.034),
+		SIVL0035(0.035),
+		SIVL0036(0.036),
+		SIVL0038(0.038),
+		SIVL0040(0.040),
+		SIVL0044(0.044),
+		SIVL0046(0.046),
+		SIVL0048(0.048),
+		SIVL0055(0.055),
+		SIVL0056(0.056),
+		SIVL0057(0.057),
+		SIVL0058(0.058),
+		SIVL0060(0.060),
+		SIVL0061(0.061),
+		SIVL0066(0.066),
+		SIVL0067(0.067),
+		SIVL0075(0.075),
+		SIVL0078(0.078),
+		SIVL0130(0.130),
+		SIVL0150(0.150),
+		SIVL0205(0.205),
+		SIVL0220(0.220),
+		SIVL0243(0.243),
+		SIVL0360(0.360);
+
+		private final double value;
+
+		private CompoundCurveType(double value) {
+
+			this.value = value;
+		}
+
+		public double getValue() {
+			return value;
+		}
+	}
+	
+	public enum PowerFailCondition{
+		
+		Attack,
+		AttackSwing,
+		Cast,
+		CastSpell,
+		EquipChange,
+		Logout,
+		Move,
+		NewCharm,
+		Sit,
+		TakeDamage,
+		TerritoryClaim,
+		UnEquip;
+	}
+	
+	public enum PowerSubType{
+		Amount,
+		Ramp,
+		UseAddFormula,
+		DamageType1,
+		DamageType2,
+		DamageType3,
+		Cancel;
+	}
+
+	public enum PowerCategoryType {
+		NONE,
+		WEAPON,
+		BUFF,
+		DEBUFF,
+		SPECIAL,
+		DAMAGE,
+		DISPEL,
+		INVIS,
+		STUN,
+		TELEPORT,
+		HEAL,
+		VAMPDRAIN,
+		BLESSING,
+		BOONRACE,
+		BOONCLASS,
+		BEHAVIOR,
+		CHANT,
+		GROUPBUFF,
+		MOVE,
+		FLIGHT,
+		GROUPHEAL,
+		AEDAMAGE,
+		BREAKFLY,
+		AE,
+		TRANSFORM,
+		TRACK,
+		SUMMON,
+		STANCE,
+		RECALL,
+		SPIREPROOFTELEPORT,
+		SPIREDISABLE,
+		THIEF;
+	}
+
+	public enum PowerTargetType {
+
+		SELF,
+		PCMOBILE,
+		PET,
+		MOBILE,
+		PC,
+		WEAPON,
+		GUILDLEADER,
+		BUILDING,
+		GROUP,
+		ARMORWEAPONJEWELRY,
+		CORPSE,
+		JEWELRY,
+		WEAPONARMOR,
+		ARMOR,
+		ITEM;
+	}
+
+	public enum objectMaskType {
+		PLAYER,
+		MOB,
+		PET,
+		CORPSE,
+		BUILDING,
+		UNDEAD,
+		BEAST,
+		HUMANOID,
+		NPC,
+		IAGENT,
+		DRAGON,
+		RAT,
+		SIEGE,
+		CITY,
+		ZONE;
+
+		public static EnumSet<objectMaskType> AGGRO = EnumSet.of(PLAYER, PET);
+		public static EnumSet<objectMaskType> MOBILE = EnumSet.of(PLAYER, MOB, PET);
+		public static EnumSet<objectMaskType> STATIC = EnumSet.of(CORPSE, BUILDING, NPC);
+
+	}
+
+	public enum ItemContainerType {
+		NONE,
+		INVENTORY,
+		EQUIPPED,
+		BANK,
+		VAULT,
+		FORGE,
+		WAREHOUSE;
+	}
+
+	public enum ItemSlotType implements EnumBitSetHelper<ItemSlotType> {
+		RHELD,
+		LHELD,
+		HELM,
+		CHEST,
+		SLEEVES,
+		HANDS,
+		RRING,
+		LRING,
+		AMULET,
+		LEGS,
+		FEET,
+		CLOAK,
+		SHIN,
+		UPLEGS,
+		UPARM,
+		WINGS,
+		BEARD,
+		HAIR;
+	}
+
+	public enum CityBoundsType {
+
+		GRID(448),
+		TERRAFORM(544),
+		ZONE(672),
+		SIEGE(814);
+
+		public final float extents;
+
+		CityBoundsType(float extents) {
+		this.extents = extents;
+		}
+	}
+
+	public enum GuildType {
+		NONE("None", new String[][] {{"None"}}, new String[] {"Thearchy", "Common Rule", "Theocracy", "Republic Rule"}),
+		CATHEDRAL("Church of the All-Father", new String[][]{
+			{"Acolyte","Acolyte"},
+			{"Catechist"},
+			{"Deacon", "Deaconess"},
+			{"Priest", "Priestess"},
+			{"High Priest", "High Priestess"},
+			{"Bishop", "Bishop"},
+			{"Lord Cardinal", "Lady Cardinal"},
+			{"Patriarch", "Matriarch"}},
+				new String[] {"Thearchy", "Common Rule", "Theocracy", "Republic Rule"}),
+		MILITARY("Military", new String[][] {
+			{"Recruit"},
+			{"Footman"},
+			{"Corporal"},
+			{"Sergeant"},
+			{"Lieutenant"},
+			{"Captain"},
+			{"General"},
+			{"Lord Marshall","Lady Marshall"}},
+				new String[]{"Autocracy", "Common Rule", "Council Rule", "Militocracy"}),
+		TEMPLE("Temple of the Cleansing Flame", new String[][]{
+			{"Aspirant"},
+			{"Novice"},
+			{"Initiate"},
+			{"Inquisitor"},
+			{"Jannisary"},
+			{"Tribune"},
+			{"Lictor"},
+			{"Justiciar"},
+			{"Pontifex","Pontifectrix"}},
+				new String[] {"Despot Rule", "Common Rule", "Protectorship", "Republic Rule"}),
+		BARBARIAN("Barbarian Clan", new String[][] {
+			{"Barbarian"},
+			{"Skald"},
+			{"Raider"},
+			{"Karl"},
+			{"Jarl"},
+			{"Chieftain"},
+			{"Thane"}},
+				new String[]{"Chiefdom", "Common Rule", "Council Rule", "Republic Rule"}),
+		RANGER("Ranger's Brotherhood", new String[][] {
+			{"Yeoman"},
+			{"Pathfinder"},
+			{"Tracker"},
+			{"Seeker"},
+			{"Protector"},
+			{"Guardian"},
+			{"Lord Protector","Lady Protector"}},
+				new String[]{"Despot Rule", "Collectivism","Council Rule","Republic Rule"}),
+		AMAZON("Amazon Temple", new String[][] {
+			{"Amazon Thrall", "Amazon"},
+			{"Amazon Slave", "Amazon Warrior"},
+			{"Amazon Servant", "Amazon Chieftess"},
+			{"Amazon Consort", "Amazon Princess"},
+			{"Amazon Seneschal", "Majestrix"},
+			{"Amazon Regent", "Imperatrix"}},
+				new String[] {"Despot Rule", "Common Rule", "Gynarchy", "Gynocracy"}),
+		NOBLE("Noble House", new String[][] {
+			{"Serf"},
+			{"Vassal"},
+			{"Exultant"},
+			{"Lord", "Lady"},
+			{"Baron", "Baroness"},
+			{"Count", "Countess"},
+			{"Duke", "Duchess"},
+			{"King", "Queen"},
+			{"Emperor", "Empress"}},
+				new String[] {"Monarchy", "Common Rule", "Feodality", "Republic"}),
+		WIZARD("Wizard's Conclave", new String[][] {
+			{"Apprentice"},
+			{"Neophyte"},
+			{"Adeptus Minor"},
+			{"Adeptus Major"},
+			{"Magus"},
+			{"High Magus"},
+			{"Archmagus"}},
+				new String[] {"Despot Rule", "Common Rule", "Council Rule", "Magocracy"}),
+		MERCENARY("Mercenary Company", new String[][] {
+			{"Soldier"},
+			{"Man-at-Arms"},
+			{"Veteran"},
+			{"Myrmidon"},
+			{"Captain"},
+			{"Commander"},
+			{"High Commander"},
+			{"Warlord"}},
+				new String[] {"Magistrature", "Mob Law", "Council Rule", "Republic Rule"}),
+		THIEVES("Thieve's Den", new String[][] {
+			{"Urchin"},
+			{"Footpad"},
+			{"Grifter"},
+			{"Burglar"},
+			{"Collector"},
+			{"Naster Thief"},
+			{"Treasurer"},
+			{"Grandmaster Thief"},
+			{"Grandfather"}},
+				new String[] {"Despot Rule", "Common Rule", "Oligarchy", "Republic Rule"}),
+		DWARF("Dwarf Hold", new String[][] {
+			{"Citizen"},
+			{"Master"},
+			{"Councilor"},
+			{"Thane"},
+			{"Great Thane"},
+			{"High Thane"}},
+				new String[] {"Despot Rule", "Common Rule", "Council Rule", "Republic Rule"}),
+		HIGHCOURT("High Court", new String[][] {
+			{"Eccekebe"},
+			{"Saedulor"},
+			{"Hodrimarth"},
+			{"Mandrae"},
+			{"Imaelin"},
+			{"Thaelostor", "Thaelostril"},
+			{"Dar Thaelostor", "Dar Thaelostril"},
+			{"Aglaeron"},
+			{"Ellestor", "Elestril"}},
+				new String[] {"Despot Rule", "Common Rule", "Council Rule", "Republic Rule"}),
+		VIRAKT("Virakt", new String[][] {
+			{"Jov'uus"},
+			{"Urikhan"},
+			{"Irkhan"},
+			{"Khal'usht"},
+			{"Arkhalar"},
+			{"Khal'uvho"},
+			{"Khar'uus"},
+			{"Kryqh'khalin"}},
+				new String[] {"Despot Rule", "Common Rule", "Council Rule", "Republic Rule"}),
+		BRIALIA("Coven of Brialia", new String[][] { // Unknown Rank names
+			{"Devotee"},
+			{"Initiated"},
+			{"Witch of the First"},
+			{"Witch of the Second"},
+			{"Witch of the Third"},
+			{"Elder"},
+			{"Hierophant"},
+			{"Witch King", "Witch Queen"}},
+				new String[] {"Despot Rule", "Common Rule", "Council Rule", "Republic Rule"}),
+		UNHOLY("Unholy Legion", new String[][] { // Unknown Rank names
+			{"Footman"},
+			{"Fell Legionaire"},
+			{"Fell Centurion"},
+			{"Dark Captain"},
+			{"Dark Commander"},
+			{"Dark Master", "Dark Mistress"},
+			{"Dread Master", "Dread Mistress"},
+			{"Dread Lord", "Dread Lady"}},
+				new String[] {"Despot Rule", "Despot Rule", "Council Rule", "Republic Rule"}),
+		SCOURGE("Cult of the Scourge", new String[][] {
+			{"Thrall"},
+			{"Mudir"},
+			{"Dark Brother", "Dark Sister"},
+			{"Hand of the Dark"},
+			{"Dark Father", "Dark Mother"}},
+				new String[] {"Despot Rule", "Common Rule", "Council Rule", "Republic Rule"}),
+		PIRATE("Pirate Crew", new String[][] {
+			{"Midshipman", "Midshipwoman"},
+			{"Sailor"},
+			{"Third Mat"},
+			{"Second Mat"},
+			{"First Mate"},
+			{"Captain"}},
+				new String[] {"Despot Rule", "Common Rule", "Council Rule", "Republic Rule"}),
+		HERALD("Academy of Heralds", new String[][] {
+			{"Pupil"},
+			{"Scribe"},
+			{"Recorder"},
+			{"Scrivener"},
+			{"Chronicler"},
+			{"Scholar"},
+			{"Archivist"},
+			{"Loremaster"}},
+				new String[]{"Despot Rule", "Common Rule", "Council Rule", "Republic Rule"}),
+		CENTAUR("Centaur Cohort", new String[][] {
+			{"Hoplite"},
+			{"Peltast"},
+			{"Myrmidon"},
+			{"Myrmidon"},
+			{"Cataphract"},
+			{"Septenrion"},
+			{"Praetorian"},
+			{"Paragon"}},
+				new String[] {"Despot Rule", "Common Rule", "Council Rule", "Republic Rule"}),
+		KHREE("Aracoix Kh'ree", new String[][] {
+			{"Duriacor"},
+			{"Exarch"},
+			{"Tetrarch"},
+			{"Dimarch"},
+			{"Elnarch"},
+			{"Illiarch"},
+			{"Tellotharch"},
+			{"Erentar"},
+			{"Araceos"},
+			{"Hierarch"}},
+
+				new String[] {"Despot Rule", "Common Rule", "Council Rule", "Republic Rule"});
+
+		GuildType(String name, String[][] ranks, String[] leadershipTypes) {
+			this.name = name;
+			this.ranks = ranks;
+			this.leadershipTypes = leadershipTypes;
+		}
+
+		private final String name;
+		private final String[][] ranks;	//Stored Rank#->Gender(M,F)
+		private final String[] leadershipTypes;
+
+		public String getCharterName() {
+			return this.name;
+		}
+
+		public int getNumberOfRanks() {
+			return ranks.length;
+		}
+
+		public String getRankForGender(int rank, boolean male) {
+			if(ranks.length < rank) {
+				return "";
+			}
+
+			if(ranks[rank].length != 1 && !male) {
+				return ranks[rank][1];
+			}
+			return ranks[rank][0];
+		}
+
+		public String getLeadershipType(int i) {
+			return leadershipTypes[i];
+		}
+
+		public static GuildType getGuildTypeFromCharter(ItemBase itemBase) {
+
+			GuildType charterType;
+
+			// Must be a valid charter object
+
+			if(itemBase.getType().equals(ItemType.GUILDCHARTER) == false)
+				return GuildType.NONE;	//No guild Type
+
+			// No switches on long in java.  Cast to int
+			// when refactor to long uuid's.  Loss won't matter
+			// with values this small.
+
+			switch (itemBase.getUUID()) {
+
+			case 559:
+				charterType = GuildType.CATHEDRAL;
+				break;
+			case 560:
+				charterType = GuildType.MILITARY;
+				break;
+			case 561:
+				charterType = GuildType.TEMPLE;
+				break;
+			case 562:
+				charterType = GuildType.BARBARIAN;
+				break;
+			case 563:
+				charterType = GuildType.RANGER;
+				break;
+			case 564:
+				charterType = GuildType.AMAZON;
+				break;
+			case 565:
+				charterType = GuildType.NOBLE;
+				break;
+			case 566:
+				charterType = GuildType.WIZARD;
+				break;
+			case 567:
+				charterType = GuildType.MERCENARY;
+				break;
+			case 568:
+				charterType = GuildType.THIEVES;
+				break;
+			case 569:
+				charterType = GuildType.DWARF;
+				break;
+			case 570:
+				charterType = GuildType.HIGHCOURT;
+				break;
+			case 571:
+				charterType = GuildType.VIRAKT;
+				break;
+			case 572:
+				charterType = GuildType.SCOURGE;
+				break;
+			case 573:
+				charterType = GuildType.KHREE;
+				break;
+			case 574:
+				charterType = GuildType.CENTAUR;
+				break;
+			case 575:
+				charterType = GuildType.UNHOLY;
+				break;
+			case 576:
+				charterType = GuildType.PIRATE;
+				break;
+			case 577:
+				charterType = GuildType.BRIALIA;
+				break;
+
+			default:
+				charterType = GuildType.HERALD;
+			}
+
+			return charterType;
+		}
+
+		public static GuildType getGuildTypeFromInt(int i) {
+			return GuildType.values()[i];
+		}
+
+	}
+
+	public enum MinionClass {
+		MELEE,
+		ARCHER,
+		MAGE;
+	}
+	
+	public enum MinionType {
+		AELFBORNGUARD(951,1637, MinionClass.MELEE, "Guard","Aelfborn"),
+		AELFBORNMAGE(952, 1635, MinionClass.MAGE,"Adept","Aelfborn"),
+		AMAZONGUARD(1500,1670, MinionClass.MELEE,"Guard","Amazon"),
+		AMAZONMAGE(1502, 1638, MinionClass.MAGE,"Fury","Amazon"),
+		ARACOIXGUARD(1600,1672,MinionClass.MELEE, "Guard","Aracoix"), //used guard captain equipset.
+		ARACOIXMAGE(1602,000,MinionClass.MAGE,"Adept","Aracoix"),
+		CENTAURGUARD(1650,1642, MinionClass.MELEE,"Guard","Centaur"),
+		CENTAURMAGE(1652, 1640, MinionClass.MAGE,"Druid","Centaur"),
+		DWARVENARCHER(845,1644, MinionClass.ARCHER, "Marksman","Dwarven"),
+		DWARVENGUARD(1050,1666, MinionClass.MELEE,"Guard","Dwarven"),
+		DWARVENMAGE(1052, 1643, MinionClass.MAGE,"War Priest","Dwarven"),
+		ELFGUARD(1180,1671, MinionClass.MELEE,"Guard","Elven"), //old 1645
+		ELFMAGE(1182, 1667, MinionClass.MAGE,"Adept","Elven"),
+		FORESTGUARD(1550,1668, MinionClass.MELEE,"Guard","Forest"), //captain changed to guard equipset
+		FORESTMAGE(1552, 000, MinionClass.MAGE,"Adept","Forest"),
+		HOLYGUARD(1525,1658, MinionClass.MELEE,"Guard","Holy Church"),
+		HOLYMAGE(1527, 1646, MinionClass.MAGE,"Prelate","Holy Church"),
+		HUMANARCHER(846,1654,MinionClass.ARCHER, "Archer","Human"),
+		HUMANGUARD(840,1665, MinionClass.MELEE, "Guard","Human"),
+		HUMANMAGE(848, 1655, MinionClass.MAGE,"Adept","Human"),
+		IREKEIGUARD(1350,1659, MinionClass.MELEE,"Guard","Irekei"),
+		IREKEIMAGE(1352, 1660, MinionClass.MAGE,"Adept","Irekei"),
+		MINOTAURARCHER(1701,0,MinionClass.ARCHER,"Archer","Minotaur"),
+		MINOTAURGUARD(1700,1673,MinionClass.MELEE,"Guard","Minotaur"),
+		NORTHMANGUARD(1250,1669, MinionClass.MELEE,"Guard","Northman"),
+		NORTHMANMAGE(1252, 1650, MinionClass.MAGE,"Runecaster","Northman"),
+		SHADEGUARD(1450,1662, MinionClass.MELEE,"Guard","Shade"),
+		SHADEMAGE(1452, 1664, MinionClass.MAGE,"Adept","Shade"),
+		TEMPLARGUARD(841,000,MinionClass.MELEE,"Marksman","Templar"),
+		TEMPLEGUARD(1575,1652, MinionClass.MELEE,"Guard","Temple"),
+		TEMPLEMAGE(1577, 1656, MinionClass.MAGE,"Confessor","Temple"),
+		UNDEADGUARD(980100,1674,MinionClass.MELEE,"Guard","Undead"),
+		UNDEADMAGE(980102,1675,MinionClass.MAGE,"Adept","Undead");
+		
+		private final int captainContractID;
+		private final int equipSetID;
+		private final MinionClass minionClass;
+		private final String name;
+		private final String race;
+		
+		public static HashMap<Integer,MinionType> ContractToMinionMap = new HashMap<>();
+		
+		MinionType(int captainContractID, int equipSetID, MinionClass minionClass, String name, String race) {
+			
+			this.captainContractID = captainContractID;
+			this.equipSetID = equipSetID;
+			this.minionClass = minionClass;
+			this.name = name;
+			this.race = race;
+			
+		}
+		
+		public static void InitializeMinions(){
+			
+			for (MinionType minionType :MinionType.values())
+			ContractToMinionMap.put(minionType.captainContractID, minionType);
+		}
+
+		public int getCaptainContractID() {
+			return captainContractID;
+		}
+
+		public int getEquipSetID() {
+			return equipSetID;
+		}
+
+		public MinionClass getMinionClass() {
+			return minionClass;
+		}
+
+		public String getName() {
+			return name;
+		}
+
+		public String getRace() {
+			return race;
+		}
+		
+	}
+	
+	public enum GridObjectType{
+		STATIC,
+		DYNAMIC;
+	}
+
+	public enum SupportMsgType {
+	    NONE(0),
+		PROTECT(1),
+		UNPROTECT(3),
+		VIEWUNPROTECTED(4),
+		REMOVETAX(6),
+		ACCEPTTAX(7),
+		CONFIRMPROTECT(8);
+
+		private final int type;
+		public static HashMap<Integer, SupportMsgType> typeLookup = new HashMap<>();
+
+		SupportMsgType(int messageType) {
+			this.type = messageType;
+		
+		}
+
+		public static void InitializeSupportMsgType(){
+			
+			for (SupportMsgType supportMsgType :SupportMsgType.values())
+				typeLookup.put(supportMsgType.type, supportMsgType);
+		}
+	}
+
+	public enum ResourceType implements EnumBitSetHelper<ResourceType> {
+
+		STONE(1580000),
+		TRUESTEEL(1580001),
+		IRON(1580002),
+		ADAMANT(1580003),
+		LUMBER(1580004),
+		OAK(1580005),
+		BRONZEWOOD(1580006),
+		MANDRAKE(1580007),
+		COAL(1580008),
+		AGATE(1580009),
+		DIAMOND(1580010),
+		ONYX(1580011),
+		AZOTH(1580012),
+		ORICHALK(1580013),
+		ANTIMONY(1580014),
+		SULFUR(1580015),
+		QUICKSILVER(1580016),
+		GALVOR(1580017),
+		WORMWOOD(1580018),
+		OBSIDIAN(1580019),
+		BLOODSTONE(1580020),
+		MITHRIL(1580021),
+		GOLD(7);
+
+		public static HashMap<Integer, ResourceType> resourceLookup = new HashMap<>();
+		public int itemID;
+
+		ResourceType(int itemID) {
+			this.itemID = itemID;
+		}
+
+		public static void InitializeResourceTypes(){
+
+			for (ResourceType resourceType :ResourceType.values())
+				resourceLookup.put(resourceType.itemID, resourceType);
+		}
+	}
+	
+	public enum PowerActionType {
+		ApplyEffect,
+		ApplyEffects,
+		Block,
+		Charm,
+		ClaimMine,
+		ClearAggro,
+		ClearNearbyAggro,
+		Confusion,
+		CreateMob,
+		DamageOverTime,
+		DeferredPower,
+		DirectDamage,
+		Invis,
+		MobRecall,
+		Peek,
+		Recall,
+		RemoveEffect,
+		Resurrect,
+		RunegateTeleport,
+		SetItemFlag,
+		SimpleDamage,
+		SpireDisable,
+		Steal,
+		Summon,
+		Teleport,
+		Track,
+		TransferStat,
+		TransferStatOT,
+		Transform,
+		TreeChoke
+	}
+
+	public enum AccountStatus {
+		BANNED,
+		ACTIVE,
+		ADMIN;
+	}
+
+}
diff --git a/src/engine/InterestManagement/HeightMap.java b/src/engine/InterestManagement/HeightMap.java
new file mode 100644
index 00000000..a683248b
--- /dev/null
+++ b/src/engine/InterestManagement/HeightMap.java
@@ -0,0 +1,1099 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+package engine.InterestManagement;
+
+import engine.Enum;
+import engine.gameManager.DbManager;
+import engine.gameManager.ZoneManager;
+import engine.math.Vector2f;
+import engine.math.Vector3fImmutable;
+import engine.objects.AbstractWorldObject;
+import engine.objects.Zone;
+import engine.server.MBServerStatics;
+import engine.util.MapLoader;
+import org.pmw.tinylog.Logger;
+
+import javax.imageio.ImageIO;
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.io.IOException;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.HashMap;
+
+public class HeightMap {
+
+    // Class variables
+
+    // Heightmap data for all zones.
+
+    public static final HashMap<Integer, HeightMap> heightmapByLoadNum = new HashMap<>();
+
+    // Bootstrap Tracking
+
+    public static int heightMapsCreated = 0;
+    public static HeightMap PlayerCityHeightMap;
+
+    // Heightmap data for this heightmap
+
+    public BufferedImage heightmapImage;
+
+    private int heightMapID;
+    private int maxHeight;
+    private int fullExtentsX;
+    private int fullExtentsY;
+
+    private float bucketWidthX;
+    private float bucketWidthY;
+    private int zoneLoadID;
+    private float seaLevel = 0;
+    private float outsetX;
+    private float outsetZ;
+    private int[][] pixelColorValues;
+
+    public HeightMap(ResultSet rs) throws SQLException {
+
+        this.heightMapID = rs.getInt("heightMapID");
+        this.maxHeight = rs.getInt("maxHeight");
+        int halfExtentsX = rs.getInt("xRadius");
+        int halfExtentsY = rs.getInt("zRadius");
+        this.zoneLoadID = rs.getInt("zoneLoadID");
+        this.seaLevel = rs.getFloat("seaLevel");
+        this.outsetX = rs.getFloat("outsetX");
+        this.outsetZ = rs.getFloat("outsetZ");
+
+
+        // Cache the full extents to avoid the calculation
+
+        this.fullExtentsX = halfExtentsX * 2;
+        this.fullExtentsY = halfExtentsY * 2;
+
+        this.heightmapImage = null;
+        File imageFile = new File(MBServerStatics.DEFAULT_DATA_DIR + "heightmaps/" + this.heightMapID + ".bmp");
+
+        // early exit if no image file was found.  Will log in caller.
+
+        if (!imageFile.exists())
+            return;
+
+        // load the heightmap image.
+
+        try {
+            this.heightmapImage = ImageIO.read(imageFile);
+        } catch (IOException e) {
+            Logger.error("***Error loading heightmap data for heightmap " + this.heightMapID + e.toString());
+        }
+
+        // We needed to flip the image as OpenGL and Shadowbane both use the bottom left corner as origin.
+
+        this.heightmapImage = MapLoader.flipImage(this.heightmapImage);
+
+        // Calculate the data we do not load from table
+
+        try {
+            calculateBucketWidth();
+
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+
+        // Generate pixel array from image data
+
+        generatePixelData();
+
+        HeightMap.heightmapByLoadNum.put(this.zoneLoadID, this);
+
+        heightMapsCreated++;
+    }
+
+    //Created for PlayerCities
+    public HeightMap() {
+
+        this.heightMapID = 999999;
+        this.maxHeight = 5; // for real...
+        int halfExtentsX = (int) Enum.CityBoundsType.ZONE.extents;
+        int halfExtentsY = (int) Enum.CityBoundsType.ZONE.extents;
+        this.zoneLoadID = 0;
+        this.seaLevel = 0;
+        this.outsetX = 128;
+        this.outsetZ = 128;
+
+
+        // Cache the full extents to avoid the calculation
+
+        this.fullExtentsX = halfExtentsX * 2;
+        this.fullExtentsY = halfExtentsY * 2;
+
+
+        // load the heightmap image.
+
+
+        // We needed to flip the image as OpenGL and Shadowbane both use the bottom left corner as origin.
+
+        this.heightmapImage = null;
+
+        // Calculate the data we do not load from table
+
+        this.bucketWidthX = 1;
+        this.bucketWidthY = 1;
+
+        this.pixelColorValues = new int[this.fullExtentsX + 1][this.fullExtentsY+1];
+
+        for (int y = 0; y <= this.fullExtentsY; y++) {
+            for (int x = 0; x <= this.fullExtentsX; x++) {
+                pixelColorValues[x][y] = 255;
+            }
+        }
+
+
+        HeightMap.heightmapByLoadNum.put(this.zoneLoadID, this);
+    }
+    
+    public HeightMap(Zone zone) {
+
+        this.heightMapID = 999999;
+        this.maxHeight = 0;
+        int halfExtentsX = (int) zone.getBounds().getHalfExtents().x;
+        int halfExtentsY = (int) zone.getBounds().getHalfExtents().y;
+        this.zoneLoadID = 0;
+        this.seaLevel = 0;
+        this.outsetX = 0;
+        this.outsetZ = 0;
+
+        // Cache the full extents to avoid the calculation
+
+        this.fullExtentsX = halfExtentsX * 2;
+        this.fullExtentsY = halfExtentsY * 2;
+
+
+        // We needed to flip the image as OpenGL and Shadowbane both use the bottom left corner as origin.
+
+        this.heightmapImage = null;
+
+        // Calculate the data we do not load from table
+
+        this.bucketWidthX = 1;
+        this.bucketWidthY = 1;
+
+        this.pixelColorValues = new int[this.fullExtentsX+1][this.fullExtentsY+1];
+
+        for (int y = 0; y <= this.fullExtentsY; y++) {
+            for (int x = 0; x <= this.fullExtentsX; x++) {
+                pixelColorValues[x][y] = 255;
+            }
+        }
+
+
+        HeightMap.heightmapByLoadNum.put(this.zoneLoadID, this);
+    }
+
+    public static void GeneratePlayerCityHeightMap() {
+
+        HeightMap.PlayerCityHeightMap = new HeightMap();
+
+    }
+    
+    public static void GenerateCustomHeightMap(Zone zone) {
+
+        HeightMap heightMap = new HeightMap(zone);
+        
+        HeightMap.heightmapByLoadNum.put(zone.getLoadNum(), heightMap);
+
+    }
+
+    public Vector2f getGridSquare(Vector2f zoneLoc) {
+
+        if (zoneLoc.x < 0)
+            zoneLoc.setX(0);
+
+        if (zoneLoc.x > this.fullExtentsX - 1)
+            zoneLoc.setX((this.fullExtentsX - 1) + .9999999f);
+
+        if (zoneLoc.y < 0)
+            zoneLoc.setY(0);
+
+        if (zoneLoc.y > this.fullExtentsY - 1)
+            zoneLoc.setY((this.fullExtentsY - 1) + .9999999f);
+
+        float xBucket = (zoneLoc.x / this.bucketWidthX);
+        float yBucket = (zoneLoc.y / this.bucketWidthY);
+
+        return new Vector2f(xBucket, yBucket);
+    }
+
+    public float getInterpolatedTerrainHeight(Vector2f zoneLoc) {
+
+        Vector2f gridSquare;
+
+        if (zoneLoc.x < 0 || zoneLoc.x > this.fullExtentsX)
+            return -1;
+
+        if (zoneLoc.y < 0 || zoneLoc.y > this.fullExtentsY)
+            return -1;
+        
+        int maxX = (int) (this.fullExtentsX / this.bucketWidthX);
+        int maxY = (int) (this.fullExtentsY / this.bucketWidthY);
+
+        //flip the Y so it grabs from the bottom left instead of top left.
+        //zoneLoc.setY(maxZoneHeight - zoneLoc.y);
+
+        gridSquare = getGridSquare(zoneLoc);
+
+        int gridX = (int) gridSquare.x;
+        int gridY = (int) (gridSquare.y);
+        
+        if (gridX > maxX)
+        	gridX = maxX;
+        if (gridY > maxY)
+        	gridY = maxY;
+
+        float offsetX = (gridSquare.x - gridX);
+        float offsetY = gridSquare.y - gridY;
+
+        //get height of the 4 vertices.
+
+        float topLeftHeight = 0;
+        float topRightHeight = 0;
+        float bottomLeftHeight = 0;
+        float bottomRightHeight = 0;
+        
+        int nextY = gridY +1;
+        int nextX = gridX + 1;
+        
+        if (nextY > maxY)
+        	nextY = gridY;
+        
+        if (nextX > maxX)
+        	nextX = gridX;
+        
+        topLeftHeight = pixelColorValues[gridX][gridY];
+        topRightHeight = pixelColorValues[nextX][gridY];
+        bottomLeftHeight = pixelColorValues[gridX][nextY];
+        bottomRightHeight = pixelColorValues[nextX][nextY];
+
+        float interpolatedHeight;
+
+        interpolatedHeight = topRightHeight * (1 - offsetY) * (offsetX);
+        interpolatedHeight += (bottomRightHeight * offsetY * offsetX);
+        interpolatedHeight += (bottomLeftHeight * (1 - offsetX) * offsetY);
+        interpolatedHeight += (topLeftHeight * (1 - offsetX) * (1 - offsetY));
+
+        interpolatedHeight *= (float) this.maxHeight / 256;  // Scale height
+
+        return interpolatedHeight;
+    }
+
+    public static float getWorldHeight(AbstractWorldObject worldObject) {
+
+        Vector2f parentLoc = new Vector2f(-1, -1);
+        Zone currentZone = ZoneManager.findSmallestZone(worldObject.getLoc());
+
+        if (currentZone == null)
+            return worldObject.getAltitude();
+
+        Zone parentZone = currentZone.getParent();
+        HeightMap heightMap = currentZone.getHeightMap();
+
+        //find the next parents heightmap if the currentzone heightmap is null.
+
+        while (heightMap == null) {
+
+            if (currentZone == ZoneManager.getSeaFloor()) {
+                break;
+            }
+            currentZone = currentZone.getParent();
+            heightMap = currentZone.getHeightMap();
+
+            parentZone = currentZone.getParent();
+        }
+        if ((heightMap == null) || (currentZone == ZoneManager.getSeaFloor())) {
+
+            return currentZone.getAbsY() + worldObject.getAltitude();
+        }
+
+        Vector2f zoneLoc = ZoneManager.worldToZoneSpace(worldObject.getLoc(), currentZone);
+        Vector3fImmutable localLocFromCenter = ZoneManager.worldToLocal(worldObject.getLoc(), currentZone);
+
+        if ((parentZone != null) && (parentZone.getHeightMap() != null)) {
+            parentLoc = ZoneManager.worldToZoneSpace(worldObject.getLoc(), parentZone);
+        }
+
+        float interaltitude = currentZone.getHeightMap().getInterpolatedTerrainHeight(zoneLoc);
+
+        float worldAltitude = currentZone.getWorldAltitude();
+
+        float realWorldAltitude = interaltitude + worldAltitude;
+
+        //OUTSET
+
+
+        if (parentZone != null) {
+
+
+            float parentXRadius = currentZone.getBounds().getHalfExtents().x;
+            float parentZRadius = currentZone.getBounds().getHalfExtents().y;
+
+
+            float offsetX = Math.abs((localLocFromCenter.x / parentXRadius));
+            float offsetZ = Math.abs((localLocFromCenter.z / parentZRadius));
+
+            float bucketScaleX = heightMap.outsetX / parentXRadius;
+            float bucketScaleZ = heightMap.outsetZ / parentZRadius;
+
+
+            if (bucketScaleX <= 0.40000001) {
+                bucketScaleX = heightMap.outsetZ / parentXRadius;
+
+            }
+
+            if (bucketScaleX > 0.40000001)
+                bucketScaleX = 0.40000001f;
+
+            if (bucketScaleZ <= 0.40000001) {
+                bucketScaleZ = heightMap.outsetX / parentZRadius;
+            }
+
+            if (bucketScaleZ > 0.40000001)
+                bucketScaleZ = 0.40000001f;
+
+            float outsideGridSizeX = 1 - bucketScaleX; //32/256
+            float outsideGridSizeZ = 1 - bucketScaleZ;
+            float weight;
+
+            double scale;
+
+
+            if (offsetX > outsideGridSizeX && offsetX > offsetZ) {
+                weight = (offsetX - outsideGridSizeX) / bucketScaleX;
+                scale = Math.atan2((.5 - weight) * 3.1415927, 1);
+
+                float scaleChild = (float) ((scale + 1) * .5);
+                float scaleParent = 1 - scaleChild;
+
+
+                float parentAltitude = parentZone.getHeightMap().getInterpolatedTerrainHeight(parentLoc);
+                float parentCenterAltitude = parentZone.getHeightMap().getInterpolatedTerrainHeight(ZoneManager.worldToZoneSpace(currentZone.getLoc(), parentZone));
+
+
+                parentCenterAltitude += currentZone.getYCoord();
+                parentCenterAltitude += interaltitude;
+
+                float firstScale = parentAltitude * scaleParent;
+                float secondScale = parentCenterAltitude * scaleChild;
+                float outsetALt = firstScale + secondScale;
+
+                outsetALt += currentZone.getParent().getWorldAltitude();
+                realWorldAltitude = outsetALt;
+
+            } else if (offsetZ > outsideGridSizeZ) {
+
+                weight = (offsetZ - outsideGridSizeZ) / bucketScaleZ;
+                scale = Math.atan2((.5 - weight) * 3.1415927, 1);
+
+                float scaleChild = (float) ((scale + 1) * .5);
+                float scaleParent = 1 - scaleChild;
+                float parentAltitude = parentZone.getHeightMap().getInterpolatedTerrainHeight(parentLoc);
+                float parentCenterAltitude = parentZone.getHeightMap().getInterpolatedTerrainHeight(ZoneManager.worldToZoneSpace(currentZone.getLoc(), parentZone));
+
+                parentCenterAltitude += currentZone.getYCoord();
+                parentCenterAltitude += interaltitude;
+                float firstScale = parentAltitude * scaleParent;
+                float secondScale = parentCenterAltitude * scaleChild;
+                float outsetALt = firstScale + secondScale;
+
+                outsetALt += currentZone.getParent().getWorldAltitude();
+                realWorldAltitude = outsetALt;
+            }
+        }
+
+
+        return realWorldAltitude;
+    }
+
+    public static float getWorldHeight(Vector3fImmutable worldLoc) {
+
+        Vector2f parentLoc = new Vector2f(-1, -1);
+        Zone currentZone = ZoneManager.findSmallestZone(worldLoc);
+
+        if (currentZone == null)
+            return 0;
+
+        Zone parentZone = currentZone.getParent();
+        HeightMap heightMap = currentZone.getHeightMap();
+        //find the next parents heightmap if the currentzone heightmap is null.
+        while (heightMap == null) {
+
+            if (currentZone == ZoneManager.getSeaFloor()) {
+                break;
+            }
+            currentZone = currentZone.getParent();
+            heightMap = currentZone.getHeightMap();
+
+            parentZone = currentZone.getParent();
+        }
+        if ((heightMap == null) || (currentZone == ZoneManager.getSeaFloor())) {
+
+            return currentZone.getAbsY();
+        }
+
+        Vector2f zoneLoc = ZoneManager.worldToZoneSpace(worldLoc, currentZone);
+        Vector3fImmutable localLocFromCenter = ZoneManager.worldToLocal(worldLoc, currentZone);
+
+        if ((parentZone != null) && (parentZone.getHeightMap() != null)) {
+            parentLoc = ZoneManager.worldToZoneSpace(worldLoc, parentZone);
+        }
+
+        float interaltitude = currentZone.getHeightMap().getInterpolatedTerrainHeight(zoneLoc);
+
+        float worldAltitude = currentZone.getWorldAltitude();
+
+        float realWorldAltitude = interaltitude + worldAltitude;
+
+        //OUTSET
+
+
+        if (parentZone != null) {
+
+            //			if (currentZone.getHeightMap() != null && parentZone.getHeightMap() != null && parentZone.getParent() != null && parentZone.getParent().getHeightMap() != null)
+            //				return realWorldAltitude;
+
+            float parentXRadius = currentZone.getBounds().getHalfExtents().x;
+            float parentZRadius = currentZone.getBounds().getHalfExtents().y;
+
+            float offsetX = Math.abs((localLocFromCenter.x / parentXRadius));
+            float offsetZ = Math.abs((localLocFromCenter.z / parentZRadius));
+
+            float bucketScaleX = heightMap.outsetX / parentXRadius;
+            float bucketScaleZ = heightMap.outsetZ / parentZRadius;
+
+            float outsideGridSizeX = 1 - bucketScaleX; //32/256
+            float outsideGridSizeZ = 1 - bucketScaleZ;
+            float weight;
+
+            double scale;
+
+
+            if (offsetX > outsideGridSizeX && offsetX > offsetZ) {
+                weight = (offsetX - outsideGridSizeX) / bucketScaleX;
+                scale = Math.atan2((.5 - weight) * 3.1415927, 1);
+
+                float scaleChild = (float) ((scale + 1) * .5);
+                float scaleParent = 1 - scaleChild;
+
+
+                float parentAltitude = parentZone.getHeightMap().getInterpolatedTerrainHeight(parentLoc);
+                float parentCenterAltitude = parentZone.getHeightMap().getInterpolatedTerrainHeight(ZoneManager.worldToZoneSpace(currentZone.getLoc(), parentZone));
+
+
+                parentCenterAltitude += currentZone.getYCoord();
+                parentCenterAltitude += interaltitude;
+
+                float firstScale = parentAltitude * scaleParent;
+                float secondScale = parentCenterAltitude * scaleChild;
+                float outsetALt = firstScale + secondScale;
+
+                outsetALt += currentZone.getParent().getWorldAltitude();
+                realWorldAltitude = outsetALt;
+
+            } else if (offsetZ > outsideGridSizeZ) {
+
+                weight = (offsetZ - outsideGridSizeZ) / bucketScaleZ;
+                scale = Math.atan2((.5 - weight) * 3.1415927, 1);
+
+                float scaleChild = (float) ((scale + 1) * .5);
+                float scaleParent = 1 - scaleChild;
+                float parentAltitude = parentZone.getHeightMap().getInterpolatedTerrainHeight(parentLoc);
+                float parentCenterAltitude = parentZone.getHeightMap().getInterpolatedTerrainHeight(ZoneManager.worldToZoneSpace(currentZone.getLoc(), parentZone));
+
+                parentCenterAltitude += currentZone.getYCoord();
+                parentCenterAltitude += interaltitude;
+                float firstScale = parentAltitude * scaleParent;
+                float secondScale = parentCenterAltitude * scaleChild;
+                float outsetALt = firstScale + secondScale;
+
+                outsetALt += currentZone.getParent().getWorldAltitude();
+                realWorldAltitude = outsetALt;
+            }
+        }
+
+
+        return realWorldAltitude;
+    }
+
+    public float getInterpolatedTerrainHeight(Vector3fImmutable zoneLoc3f) {
+
+        Vector2f zoneLoc = new Vector2f(zoneLoc3f.x, zoneLoc3f.z);
+
+        Vector2f gridSquare;
+
+        if (zoneLoc.x < 0 || zoneLoc.x > this.fullExtentsX)
+            return -1;
+
+        if (zoneLoc.y < 0 || zoneLoc.y > this.fullExtentsY)
+            return -1;
+
+        //flip the Y so it grabs from the bottom left instead of top left.
+        //zoneLoc.setY(maxZoneHeight - zoneLoc.y);
+
+        gridSquare = getGridSquare(zoneLoc);
+
+        int gridX = (int) gridSquare.x;
+        int gridY = (int) (gridSquare.y);
+
+        float offsetX = (gridSquare.x - gridX);
+        float offsetY = gridSquare.y - gridY;
+
+        //get height of the 4 vertices.
+
+        float topLeftHeight = pixelColorValues[gridX][gridY];
+        float topRightHeight = pixelColorValues[gridX + 1][gridY];
+        float bottomLeftHeight = pixelColorValues[gridX][gridY + 1];
+        float bottomRightHeight = pixelColorValues[gridX + 1][gridY + 1];
+
+        float interpolatedHeight;
+
+        interpolatedHeight = topRightHeight * (1 - offsetY) * (offsetX);
+        interpolatedHeight += (bottomRightHeight * offsetY * offsetX);
+        interpolatedHeight += (bottomLeftHeight * (1 - offsetX) * offsetY);
+        interpolatedHeight += (topLeftHeight * (1 - offsetX) * (1 - offsetY));
+
+        interpolatedHeight *= (float) this.maxHeight / 256;  // Scale height
+
+        return interpolatedHeight;
+    }
+
+    public static float getOutsetHeight(float interpolatedAltitude, Zone zone, Vector3fImmutable worldLocation) {
+
+        Vector2f parentLoc;
+        float outsetALt = 0;
+
+        if (zone.getParent() == null || zone.getParent().getHeightMap() == null)
+            return interpolatedAltitude + zone.getWorldAltitude();
+
+        if (zone.getParent() != null && zone.getParent().getHeightMap() != null) {
+
+            parentLoc = ZoneManager.worldToZoneSpace(worldLocation, zone.getParent());
+
+            Vector3fImmutable localLocFromCenter = ZoneManager.worldToLocal(worldLocation, zone);
+
+            float parentXRadius = zone.getBounds().getHalfExtents().x;
+            float parentZRadius = zone.getBounds().getHalfExtents().y;
+
+            float bucketScaleX = zone.getHeightMap().outsetX / parentXRadius;
+            float bucketScaleZ = zone.getHeightMap().outsetZ / parentZRadius;
+
+            float outsideGridSizeX = 1 - bucketScaleX; //32/256
+            float outsideGridSizeZ = 1 - bucketScaleZ;
+
+            float weight;
+            double scale;
+
+            float offsetX = Math.abs((localLocFromCenter.x / parentXRadius));
+            float offsetZ = Math.abs((localLocFromCenter.z / parentZRadius));
+
+            if (offsetX > outsideGridSizeX && offsetX > offsetZ) {
+                weight = (offsetX - outsideGridSizeX) / bucketScaleX;
+                scale = Math.atan2((.5 - weight) * 3.1415927, 1);
+
+                float scaleChild = (float) ((scale + 1) * .5);
+                float scaleParent = 1 - scaleChild;
+
+                float parentAltitude = zone.getParent().getHeightMap().getInterpolatedTerrainHeight(parentLoc);
+                float parentCenterAltitude = zone.getParent().getHeightMap().getInterpolatedTerrainHeight(ZoneManager.worldToZoneSpace(zone.getLoc(), zone.getParent()));
+
+                parentCenterAltitude += zone.getYCoord();
+                parentCenterAltitude += interpolatedAltitude;
+
+                float firstScale = parentAltitude * scaleParent;
+                float secondScale = parentCenterAltitude * scaleChild;
+                outsetALt = firstScale + secondScale;
+
+                outsetALt += zone.getParent().getAbsY();
+
+            } else if (offsetZ > outsideGridSizeZ) {
+
+                weight = (offsetZ - outsideGridSizeZ) / bucketScaleZ;
+                scale = Math.atan2((.5 - weight) * 3.1415927, 1);
+
+                float scaleChild = (float) ((scale + 1) * .5);
+                float scaleParent = 1 - scaleChild;
+                float parentAltitude = zone.getParent().getHeightMap().getInterpolatedTerrainHeight(parentLoc);
+                float parentCenterAltitude = zone.getHeightMap().getInterpolatedTerrainHeight(ZoneManager.worldToZoneSpace(zone.getLoc(), zone));
+
+                parentCenterAltitude += zone.getYCoord();
+                parentCenterAltitude += interpolatedAltitude;
+
+                float firstScale = parentAltitude * scaleParent;
+                float secondScale = parentCenterAltitude * scaleChild;
+                outsetALt = firstScale + secondScale;
+
+                outsetALt += zone.getParent().getAbsY();
+            }
+
+        }
+
+        return outsetALt;
+    }
+
+    private void generatePixelData() {
+
+        Color color;
+
+        // Generate altitude lookup table for this heightmap
+
+        this.pixelColorValues = new int[this.heightmapImage.getWidth()][this.heightmapImage.getHeight()];
+
+        for (int y = 0; y < this.heightmapImage.getHeight(); y++) {
+            for (int x = 0; x < this.heightmapImage.getWidth(); x++) {
+
+                color = new Color(this.heightmapImage.getRGB(x, y));
+                pixelColorValues[x][y] = color.getRed();
+            }
+        }
+
+    }
+
+    public static Vector2f getGridOffset(Vector2f gridSquare) {
+
+        int floorX = (int) gridSquare.x;
+        int floorY = (int) gridSquare.y;
+
+        return new Vector2f(gridSquare.x - floorX, gridSquare.y - floorY);
+
+    }
+
+    public float getScaledHeightForColor(float color) {
+
+        return (color / 256) * this.maxHeight;
+    }
+
+
+    private void calculateBucketWidth() {
+
+
+        switch (this.zoneLoadID) {
+            case 100:
+                this.bucketWidthX = 64.12524414f;
+                this.bucketWidthY = 64.12524414f;
+                break;
+            case 200:
+                this.bucketWidthX = 145.9599152f;
+                this.bucketWidthY = 145.9599152f;
+                break;
+            case 3033:
+                this.bucketWidthX = 2.001116753f;
+                this.bucketWidthY = 2.001116753f;
+                break;
+
+            case 3011:
+                this.bucketWidthX = 4.003910065f;
+                this.bucketWidthY = 4.003910065f;
+                break;
+
+            case 3026:
+                this.bucketWidthX = 2.001116753f;
+                this.bucketWidthY = 2.001116753f;
+                break;
+
+            case 3017:
+                this.bucketWidthX = 4.003910065f;
+                this.bucketWidthY = 4.003910065f;
+                break;
+
+            case 3007:
+                this.bucketWidthX = 2.001116753f;
+                this.bucketWidthY = 2.001116753f;
+                break;
+
+            case 3020:
+                this.bucketWidthX = 4.003910065f;
+                this.bucketWidthY = 4.003910065f;
+                break;
+
+            case 3025:
+                this.bucketWidthX = 2.001116753f;
+                this.bucketWidthY = 2.001116753f;
+                break;
+
+            case 3016:
+                this.bucketWidthX = 4.003910065f;
+                this.bucketWidthY = 4.003910065f;
+                break;
+
+            case 3021:
+                this.bucketWidthX = 4.003910065f;
+                this.bucketWidthY = 4.003910065f;
+                break;
+
+            case 3018:
+                this.bucketWidthX = 4.003910065f;
+                this.bucketWidthY = 4.003910065f;
+                break;
+
+            case 3024:
+                this.bucketWidthX = 2.001116753f;
+                this.bucketWidthY = 2.001116753f;
+                break;
+
+            case 3010:
+                this.bucketWidthX = 4.003910065f;
+                this.bucketWidthY = 4.003910065f;
+                break;
+
+            case 3012:
+                this.bucketWidthX = 4.003910065f;
+                this.bucketWidthY = 4.003910065f;
+                break;
+
+            case 3022:
+                this.bucketWidthX = 4.003910065f;
+                this.bucketWidthY = 4.003910065f;
+                break;
+
+            case 3030:
+                this.bucketWidthX = 4.003910065f;
+                this.bucketWidthY = 4.003910065f;
+                break;
+
+            case 3019:
+                this.bucketWidthX = 4.003910065f;
+                this.bucketWidthY = 4.003910065f;
+                break;
+
+            case 3014:
+                this.bucketWidthX = 4.003910065f;
+                this.bucketWidthY = 4.003910065f;
+                break;
+
+            case 10500:
+                this.bucketWidthX = 51.20000076f;
+                this.bucketWidthY = 51.20000076f;
+                break;
+
+            case 10501:
+                this.bucketWidthX = 51.20000076f;
+                this.bucketWidthY = 51.20000076f;
+                break;
+
+            case 10503:
+                this.bucketWidthX = 51.20000076f;
+                this.bucketWidthY = 51.20000076f;
+                break;
+            case 10504:
+                this.bucketWidthX = 51.20000076f;
+                this.bucketWidthY = 51.20000076f;
+                break;
+            case 10505:
+                this.bucketWidthX = 51.20000076f;
+                this.bucketWidthY = 51.20000076f;
+                break;
+            case 10506:
+                this.bucketWidthX = 51.20000076f;
+                this.bucketWidthY = 51.20000076f;
+                break;
+            case 10507:
+                this.bucketWidthX = 51.20000076f;
+                this.bucketWidthY = 51.20000076f;
+                break;
+            case 10502:
+                this.bucketWidthX = 51.20000076f;
+                this.bucketWidthY = 51.20000076f;
+                break;
+
+            case 11006:
+                this.bucketWidthX = 51.20000076f;
+                this.bucketWidthY = 51.20000076f;
+                break;
+            case 11008:
+                this.bucketWidthX = 51.20000076f;
+                this.bucketWidthY = 51.20000076f;
+                break;
+            case 11036:
+                this.bucketWidthX = 51.20000076f;
+                this.bucketWidthY = 51.20000076f;
+                break;
+
+            case 10200:
+                this.bucketWidthX = 2.000977039f;
+                this.bucketWidthY = 2.000977039f;
+                break;
+
+
+            case 10120:
+                this.bucketWidthX = 1.00048852f;
+                this.bucketWidthY = 1.00048852f;
+                break;
+
+            case 10001:
+                this.bucketWidthX = 17.06666756f;
+                this.bucketWidthY = 17.06666756f;
+                break;
+            case 10002:
+                this.bucketWidthX = 17.06666756f;
+                this.bucketWidthY = 17.06666756f;
+                break;
+            case 10003:
+                this.bucketWidthX = 17.06666756f;
+                this.bucketWidthY = 17.06666756f;
+                break;
+            case 10004:
+                this.bucketWidthX = 17.06666756f;
+                this.bucketWidthY = 17.06666756f;
+                break;
+            case 10005:
+                this.bucketWidthX = 17.06666756f;
+                this.bucketWidthY = 17.06666756f;
+                break;
+            case 10100:
+                this.bucketWidthX = 17.06666756f;
+                this.bucketWidthY = 17.06666756f;
+                break;
+
+            case 10006:
+                this.bucketWidthX = 17.06666756f;
+                this.bucketWidthY = 17.06666756f;
+                break;
+            case 10007:
+                this.bucketWidthX = 17.06666756f;
+                this.bucketWidthY = 17.06666756f;
+                break;
+            case 10008:
+                this.bucketWidthX = 17.06666756f;
+                this.bucketWidthY = 17.06666756f;
+                break;
+            case 10009:
+                this.bucketWidthX = 17.06666756f;
+                this.bucketWidthY = 17.06666756f;
+                break;
+
+            case 10010:
+                this.bucketWidthX = 17.06666756f;
+                this.bucketWidthY = 17.06666756f;
+                break;
+
+            case 10201:
+                this.bucketWidthX = 2.000977039f;
+                this.bucketWidthY = 2.000977039f;
+                break;
+
+            case 10011:
+                this.bucketWidthX = 17.06666756f;
+                this.bucketWidthY = 17.06666756f;
+                break;
+            case 10012:
+                this.bucketWidthX = 17.06666756f;
+                this.bucketWidthY = 17.06666756f;
+                break;
+            case 10013:
+                this.bucketWidthX = 17.06666756f;
+                this.bucketWidthY = 17.06666756f;
+                break;
+            case 10014:
+                this.bucketWidthX = 17.06666756f;
+                this.bucketWidthY = 17.06666756f;
+                break;
+            case 10015:
+                this.bucketWidthX = 17.06666756f;
+                this.bucketWidthY = 17.06666756f;
+                break;
+
+            case 3004:
+                this.bucketWidthX = 2.001116753f;
+                this.bucketWidthY = 2.001116753f;
+                break;
+
+            case 3005:
+                this.bucketWidthX = 2.001116753f;
+                this.bucketWidthY = 2.001116753f;
+                break;
+
+
+            case 3003:
+                this.bucketWidthX = 2.001116753f;
+                this.bucketWidthY = 2.001116753f;
+                break;
+
+
+            case 400:
+                this.bucketWidthX = 64.06256104f;
+                this.bucketWidthY = 64.06256104f;
+                break;
+
+            case 3032:
+                this.bucketWidthX = 4.003910065f;
+                this.bucketWidthY = 4.003910065f;
+                break;
+
+            case 3009:
+                this.bucketWidthX = 4.003910065f;
+                this.bucketWidthY = 4.003910065f;
+                break;
+            case 3023:
+                this.bucketWidthX = 4.003910065f;
+                this.bucketWidthY = 4.003910065f;
+                break;
+            case 3008:
+                this.bucketWidthX = 4.003910065f;
+                this.bucketWidthY = 4.003910065f;
+                break;
+            case 11009:
+                this.bucketWidthX = 51.20000076f;
+                this.bucketWidthY = 51.20000076f;
+                break;
+            case 500:
+                this.bucketWidthX = 128.4012604f;
+                this.bucketWidthY = 128.4012604f;
+                break;
+
+            case 3013:
+                this.bucketWidthX = 4.003910065f;
+                this.bucketWidthY = 4.003910065f;
+                break;
+            case 3006:
+                this.bucketWidthX = 2.001116753f;
+                this.bucketWidthY = 2.001116753f;
+                break;
+
+            case 3015:
+                this.bucketWidthX = 4.003910065f;
+                this.bucketWidthY = 4.003910065f;
+                break;
+
+            case 11010:
+                this.bucketWidthX = 51.20000076f;
+                this.bucketWidthY = 51.20000076f;
+                break;
+
+            case 10130:
+                this.bucketWidthX = 1.00048852f;
+                this.bucketWidthY = 1.00048852f;
+                break;
+
+
+            case 501:
+                this.bucketWidthX = 130.0317535f;
+                this.bucketWidthY = 130.0317535f;
+                break;
+
+            case 11032:
+                this.bucketWidthX = 51.20000076f;
+                this.bucketWidthY = 51.20000076f;
+                break;
+
+            case 300:
+                this.bucketWidthX = 100.5235596f;
+                this.bucketWidthY = 100.5235596f;
+                break;
+
+            case 3027:
+                this.bucketWidthX = 4.003910065f;
+                this.bucketWidthY = 4.003910065f;
+                break;
+
+            case 3028:
+                this.bucketWidthX = 4.003910065f;
+                this.bucketWidthY = 4.003910065f;
+                break;
+
+            case 11016:
+                this.bucketWidthX = 51.20000076f;
+                this.bucketWidthY = 51.20000076f;
+                break;
+
+            case 310:
+                this.bucketWidthX = 100.5235596f;
+                this.bucketWidthY = 100.5235596f;
+                break;
+
+            case 3034:
+                this.bucketWidthX = 4.003910065f;
+                this.bucketWidthY = 4.003910065f;
+                break;
+
+            case 3035:
+                this.bucketWidthX = 4.003910065f;
+                this.bucketWidthY = 4.003910065f;
+                break;
+
+            case 11039:
+                this.bucketWidthX = 51.20000076f;
+                this.bucketWidthY = 51.20000076f;
+                break;
+
+            case 10066:
+                this.bucketWidthX = 51.20000076f;
+                this.bucketWidthY = 51.20000076f;
+                break;
+
+            case 502:
+
+                this.bucketWidthX = 130.0317535f;
+                this.bucketWidthY = 130.0317535f;
+                break;
+
+            default:
+                // Re-enable for debugging.  Spammy in console.
+                // Logger.info("Setting Zone : " + this.zoneLoadID + " with heightmap ID : " + heightMapID + " to default bucketwidth");
+                break;
+        }
+    }
+
+    public static void loadAlHeightMaps() {
+
+        // Load the heightmaps into staging hashmap keyed by HashMapID
+
+        DbManager.HeightMapQueries.LOAD_ALL_HEIGHTMAPS();
+
+        //generate static player city heightmap.
+
+        HeightMap.GeneratePlayerCityHeightMap();
+        
+  
+        // Clear all heightmap image data as it's no longer needed.
+
+        for (HeightMap heightMap : HeightMap.heightmapByLoadNum.values()) {
+            heightMap.heightmapImage = null;
+        }
+        
+        Logger.info(HeightMap.heightmapByLoadNum.size() + " Heightmaps cached.");
+    }
+
+    public float getBucketWidthX() {
+        return bucketWidthX;
+    }
+
+    public float getBucketWidthY() {
+        return bucketWidthY;
+    }
+
+    public int getHeightMapID() {
+        return heightMapID;
+    }
+
+    public BufferedImage getHeightmapImage() {
+        return heightmapImage;
+    }
+
+    public float getSeaLevel() {
+        return seaLevel;
+    }
+
+    public static boolean isLocUnderwater(Vector3fImmutable currentLoc) {
+
+        float localAltitude = HeightMap.getWorldHeight(currentLoc);
+        Zone zone = ZoneManager.findSmallestZone(currentLoc);
+
+        if (localAltitude < zone.getSeaLevel())
+            return true;
+
+        return false;
+    }
+
+}
diff --git a/src/engine/InterestManagement/InterestManager.java b/src/engine/InterestManagement/InterestManager.java
new file mode 100644
index 00000000..6182d29d
--- /dev/null
+++ b/src/engine/InterestManagement/InterestManager.java
@@ -0,0 +1,554 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+package engine.InterestManagement;
+
+import engine.Enum.DispatchChannel;
+import engine.Enum.GameObjectType;
+import engine.ai.MobileFSM;
+import engine.ai.MobileFSM.STATE;
+import engine.gameManager.GroupManager;
+import engine.gameManager.SessionManager;
+import engine.job.JobScheduler;
+import engine.jobs.RefreshGroupJob;
+import engine.net.AbstractNetMsg;
+import engine.net.Dispatch;
+import engine.net.DispatchMessage;
+import engine.net.client.ClientConnection;
+import engine.net.client.msg.LoadCharacterMsg;
+import engine.net.client.msg.LoadStructureMsg;
+import engine.net.client.msg.MoveToPointMsg;
+import engine.net.client.msg.UnloadObjectsMsg;
+import engine.objects.*;
+import engine.server.MBServerStatics;
+import org.pmw.tinylog.Logger;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+
+import static engine.math.FastMath.sqr;
+
+public enum InterestManager implements Runnable {
+
+    INTERESTMANAGER;
+
+    private static long lastTime;
+    private static boolean keepGoing = true;
+
+    public void shutdown() {
+        this.keepGoing = false;
+    }
+
+    InterestManager() {
+        Logger.info(" Interest Management thread is running.");
+    }
+
+    @Override
+    public void run() {
+        beginLoadJob();
+    }
+
+    private void beginLoadJob() {
+
+        InterestManager.lastTime = System.currentTimeMillis();
+
+        while (InterestManager.keepGoing) {
+            try {
+                updateAllPlayers();
+            } catch (Exception e) {
+                Logger.error("InterestManager.BeginLoadJob:updateAllPlayers", e);
+            }
+            try {
+                Thread.sleep(advanceOneSecond());
+            } catch (Exception e) {
+                Logger.error("InterestManager.BeginLoadJob:advanceOneSecond", e);
+            }
+        }
+    }
+
+    private long advanceOneSecond() {
+
+        long curTime = System.currentTimeMillis();
+        long dur = 1000 + this.lastTime - curTime;
+
+        if (dur < 0) {
+            // Last update took more then one second, not good...
+            Logger.warn("LoadJob took more then one second to complete.");
+            this.lastTime = curTime + 100;
+            return 100;
+        }
+        this.lastTime += 1000;
+        return dur;
+    }
+
+    private void updateAllPlayers() {
+        // get all players
+
+        for (PlayerCharacter pc : SessionManager.getAllActivePlayerCharacters()) {
+
+            if (pc == null)
+                continue;
+
+            ClientConnection origin = pc.getClientConnection();
+
+            if (origin == null)
+                continue;
+
+            if (!pc.isEnteredWorld())
+                continue;
+
+            if (pc.getTeleportLock().readLock().tryLock()) {
+
+                try {
+                    updateStaticList(pc, origin);
+                    updateMobileList(pc, origin);
+                } catch (Exception e) {
+                    Logger.error(e);
+                } finally {
+                    pc.getTeleportLock().readLock().unlock();
+                }
+            }
+        }
+    }
+
+    private void updateStaticList(PlayerCharacter player, ClientConnection origin) {
+
+        // Only update if we've moved far enough to warrant it
+
+        float distanceSquared = player.getLoc().distanceSquared2D(player.getLastStaticLoc());
+
+        if (distanceSquared > sqr(25))
+            player.setLastStaticLoc(player.getLoc());
+        else
+            return;
+
+        // Get Statics in range
+        HashSet<AbstractWorldObject> toLoad = WorldGrid.getObjectsInRangePartial(player.getLoc(), MBServerStatics.STRUCTURE_LOAD_RANGE,
+                MBServerStatics.MASK_STATIC);
+
+        // get list of obects loaded that need removed
+        HashSet<AbstractWorldObject> loadedStaticObjects = player.getLoadedStaticObjects();
+
+        HashSet<AbstractWorldObject> toRemove = null;
+
+        toRemove = new HashSet<>(loadedStaticObjects);
+
+        toRemove.removeAll(toLoad);
+
+        // unload static objects now out of range
+        if (toRemove.size() > 0) {
+            UnloadObjectsMsg uom = new UnloadObjectsMsg();
+            for (AbstractWorldObject obj : toRemove) {
+                if (obj.getObjectType().equals(GameObjectType.Building))
+                    InterestManager.HandleSpecialUnload((Building) obj, origin);
+                if (obj != null && !obj.equals(player))
+                    uom.addObject(obj);
+            }
+
+            Dispatch dispatch = Dispatch.borrow(player, uom);
+            DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.PRIMARY);
+        }
+
+        loadedStaticObjects.removeAll(toRemove);
+
+        // remove any object to load that are already loaded
+        toLoad.removeAll(loadedStaticObjects);
+
+        LoadStructureMsg lsm = new LoadStructureMsg();
+        LoadCharacterMsg lcm = null;
+        ArrayList<LoadCharacterMsg> lcmList = new ArrayList<>();
+
+        for (AbstractWorldObject awo : toLoad) {
+            if (awo.getObjectType().equals(GameObjectType.Building))
+                lsm.addObject((Building) awo);
+            else if (awo.getObjectType().equals(GameObjectType.Corpse)) {
+                Corpse corpse = (Corpse) awo;
+                lcm = new LoadCharacterMsg(corpse, PlayerCharacter.hideNonAscii());
+
+                Dispatch dispatch = Dispatch.borrow(player, lcm);
+                DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.PRIMARY);
+
+
+            } else if (awo.getObjectType().equals(GameObjectType.NPC)) {
+                NPC npc = (NPC) awo;
+                lcm = new LoadCharacterMsg(npc, PlayerCharacter.hideNonAscii());
+
+                lcmList.add(lcm);
+            }
+        }
+
+        if (lsm.getStructureList().size() > 0) {
+            Dispatch dispatch = Dispatch.borrow(player, lsm);
+            DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.PRIMARY);
+        }
+
+        for (LoadCharacterMsg lc : lcmList) {
+
+            Dispatch dispatch = Dispatch.borrow(player, lc);
+            DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.PRIMARY);
+        }
+
+        loadedStaticObjects.addAll(toLoad);
+    }
+
+    private void updateMobileList(PlayerCharacter player, ClientConnection origin) {
+
+        if (player == null)
+            return;
+
+        // Get list of players in range
+        // TODO for now use a generic getALL list, later tie into Quad Tree
+        HashSet<AbstractWorldObject> toLoad = WorldGrid.getObjectsInRangePartial(player.getLoc(), MBServerStatics.CHARACTER_LOAD_RANGE,
+                MBServerStatics.MASK_MOBILE);
+
+        HashSet<AbstractWorldObject> toRemove = new HashSet<>();
+
+        HashSet<AbstractWorldObject> toLoadToPlayer = new HashSet<>();
+
+        for (AbstractWorldObject loadedObject : toLoad) {
+
+            switch (loadedObject.getObjectType()) {
+                case PlayerCharacter:
+                    PlayerCharacter loadedPlayer = (PlayerCharacter) loadedObject;
+
+                    if (loadedPlayer.getObjectUUID() == player.getObjectUUID())
+                        continue;
+
+                    if (player.getSeeInvis() < loadedPlayer.getHidden())
+                        continue;
+
+                    if (loadedPlayer.safemodeInvis())
+                        continue;
+
+                    if (player.getLoadedObjects().contains(loadedPlayer))
+                        continue;
+
+                    if (!loadedPlayer.isInWorldGrid())
+                        continue;
+
+                    toLoadToPlayer.add(loadedPlayer);
+                    break;
+                //not playerCharacter, mobs,npcs and corpses cant be invis or safemode, just add normaly
+                default:
+                    if (player.getLoadedObjects().contains(loadedObject))
+                        continue;
+
+                    if (!loadedObject.isInWorldGrid())
+                        continue;
+
+                    toLoadToPlayer.add(loadedObject);
+                    break;
+            }
+        }
+
+        float unloadDistance = MBServerStatics.CHARACTER_LOAD_RANGE;
+        for (AbstractWorldObject playerLoadedObject : player.getLoadedObjects()) {
+
+            if (playerLoadedObject.getObjectType().equals(GameObjectType.PlayerCharacter)) {
+                PlayerCharacter loadedPlayer = (PlayerCharacter) playerLoadedObject;
+                if (player.getSeeInvis() < loadedPlayer.getHidden())
+                    toRemove.add(playerLoadedObject);
+                else if (loadedPlayer.safemodeInvis())
+                    toRemove.add(playerLoadedObject);
+            }
+
+            if (!playerLoadedObject.isInWorldGrid())
+                toRemove.add(playerLoadedObject);
+            else if (playerLoadedObject.getLoc().distanceSquared2D(player.getLoc()) > unloadDistance * unloadDistance)
+                toRemove.add(playerLoadedObject);
+
+        }
+
+        player.getLoadedObjects().addAll(toLoadToPlayer);
+        player.getLoadedObjects().removeAll(toRemove);
+
+        // get list of obects loaded to remove
+
+        // unload objects now out of range
+
+        if (toRemove.size() > 0) {
+
+            UnloadObjectsMsg uom = new UnloadObjectsMsg();
+
+            for (AbstractWorldObject obj : toRemove) {
+
+                try {
+                    if (obj != null)
+                        if (obj.equals(player)) // don't unload self
+                            continue;
+
+                    uom.addObject(obj);
+
+                    if (obj.getObjectType() == GameObjectType.Mob)
+                        ((Mob) obj).getPlayerAgroMap().remove(player.getObjectUUID());
+                } catch (Exception e) {
+                    Logger.error("UnloadCharacter", obj.getObjectUUID() + " " + e.getMessage());
+                }
+            }
+
+            if (!uom.getObjectList().isEmpty()) {
+                Dispatch dispatch = Dispatch.borrow(player, uom);
+                DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.PRIMARY);
+            }
+        }
+
+        LoadCharacterMsg lcm = null;
+        ArrayList<AbstractWorldObject> players = new ArrayList<>();
+        ArrayList<AbstractWorldObject> addToList = new ArrayList<>();
+
+        for (AbstractWorldObject awo : toLoadToPlayer) {
+            // dont load yourself
+            try {
+                if (awo.equals(player))
+                    continue;
+
+                if ((awo.getObjectTypeMask() & MBServerStatics.MASK_PLAYER) != 0) {
+
+                    // object to load is a player
+                    PlayerCharacter awopc = (PlayerCharacter) awo;
+
+                    // dont load if invis
+                    if (player.getSeeInvis() < awopc.getHidden())
+                        continue;
+
+                    lcm = new LoadCharacterMsg(awopc, PlayerCharacter.hideNonAscii());
+                    players.add(awo);
+
+                    // check if in a group with the person being loaded
+                    // and if so set updateGroup flag
+
+                    if (GroupManager.getGroup(player) != null
+                            && GroupManager.getGroup(player) == GroupManager.getGroup(awopc))
+
+                        // submit a job as for some reason the client needs a delay
+                        // with group updates
+                        // as it wont update if we do RefreshGroup directly after
+                        // sending the lcm below
+
+                        JobScheduler.getInstance().scheduleJob(new RefreshGroupJob(player, awopc), MBServerStatics.LOAD_OBJECT_DELAY);
+
+                } else if ((awo.getObjectTypeMask() & MBServerStatics.MASK_MOB) != 0) {
+                    Mob awonpc = (Mob) awo;
+
+                    if (!awonpc.isAlive() && (awonpc.isPet() || awonpc.isSiege() || awonpc.isNecroPet() || awonpc.isPlayerGuard()))
+                        continue;
+
+                    if (awonpc.getState().equals(STATE.Respawn) || awonpc.getState().equals(STATE.Disabled))
+                        continue;
+
+                    awonpc.getPlayerAgroMap().put(player.getObjectUUID(), false);
+                    MobileFSM.setAwake(awonpc, false);
+                    //				IVarController.setVariable(awonpc, "IntelligenceDisableDelay", (double) (System.currentTimeMillis() + 5000));
+                    //				awonpc.enableIntelligence();
+                    lcm = new LoadCharacterMsg(awonpc, PlayerCharacter.hideNonAscii());
+                } else if ((awo.getObjectTypeMask() & MBServerStatics.MASK_NPC) != 0) {
+                    NPC awonpc = (NPC) awo;
+                    lcm = new LoadCharacterMsg(awonpc, PlayerCharacter.hideNonAscii());
+                } else if ((awo.getObjectTypeMask() & MBServerStatics.MASK_PET) != 0) {
+                    Mob awonpc = (Mob) awo;
+
+                    if (!awonpc.isAlive())
+                        continue;
+
+                    awonpc.getPlayerAgroMap().put(player.getObjectUUID(), false);
+
+                    if (awonpc.isMob())
+                        MobileFSM.setAwake(awonpc, false);
+                    //				IVarController.setVariable(awonpc, "IntelligenceDisableDelay", (double) (System.currentTimeMillis() + 5000));
+                    //				awonpc.enableIntelligence();
+                    lcm = new LoadCharacterMsg(awonpc, PlayerCharacter.hideNonAscii());
+                }
+
+                addToList.add(awo);
+
+                if (lcm != null) {
+                    Dispatch dispatch = Dispatch.borrow(player, lcm);
+                    DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.PRIMARY);
+                }
+
+
+            } catch (Exception e) {
+                Logger.error(awo.getObjectUUID() + " " + e.getMessage());
+            }
+            //Delaying character loading to reduce bandwidth consumption
+        }
+
+        // send effects for all players being loaded
+        // do it on a timer otherwise we may get failures as te client needs
+        // time to process lcm
+        //Added effects to LoadCharacter Serialization.
+        //JobScheduler.getInstance().scheduleJob(new LoadEffectsJob(players, origin), MBServerStatics.LOAD_OBJECT_DELAY);
+    }
+
+    // Forces the loading of static objects (corpses and buildings).
+    // Needed to override threshold limits on loading statics
+
+    public static void forceLoad(AbstractWorldObject awo) {
+
+        AbstractNetMsg msg = null;
+        LoadStructureMsg lsm;
+        LoadCharacterMsg lcm;
+        NPC npc;
+        Corpse corpse;
+        HashSet<AbstractWorldObject> toUpdate;
+
+        switch (awo.getObjectType()) {
+            case Building:
+                lsm = new LoadStructureMsg();
+                lsm.addObject((Building) awo);
+                msg = lsm;
+                break;
+            case Corpse:
+                corpse = (Corpse) awo;
+                lcm = new LoadCharacterMsg(corpse, false);
+                msg = lcm;
+                break;
+            case NPC:
+                npc = (NPC) awo;
+                lcm = new LoadCharacterMsg(npc, false);
+                msg = lcm;
+                break;
+            default:
+                return;
+        }
+
+        toUpdate = WorldGrid.getObjectsInRangePartial(awo.getLoc(), MBServerStatics.CHARACTER_LOAD_RANGE, MBServerStatics.MASK_PLAYER);
+
+        boolean send;
+
+        for (AbstractWorldObject tar : toUpdate) {
+            PlayerCharacter player = (PlayerCharacter) tar;
+            HashSet<AbstractWorldObject> loadedStaticObjects = player.getLoadedStaticObjects();
+            send = false;
+
+            if (!loadedStaticObjects.contains(awo)) {
+                loadedStaticObjects.add(awo);
+                send = true;
+            }
+
+            if (send) {
+
+                Dispatch dispatch = Dispatch.borrow(player, msg);
+                DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.PRIMARY);
+            }
+        }
+    }
+
+    public static void HandleSpecialUnload(Building building, ClientConnection origin) {
+
+        if (Regions.FurnitureRegionMap.get(building.getObjectUUID()) == null)
+            return;
+
+        Regions buildingRegion = Regions.FurnitureRegionMap.get(building.getObjectUUID());
+
+        if (!buildingRegion.isOutside())
+            return;
+
+        MoveToPointMsg moveMsg = new MoveToPointMsg(building);
+
+        if (origin != null)
+            origin.sendMsg(moveMsg);
+    }
+
+    public synchronized void HandleLoadForEnterWorld(PlayerCharacter player) {
+
+        if (player == null)
+            return;
+
+        ClientConnection origin = player.getClientConnection();
+
+        if (origin == null)
+            return;
+
+        //Update static list
+        try {
+            updateStaticList(player, origin);
+        } catch (Exception e) {
+            Logger.error("InterestManager.updateAllStaticPlayers: " + player.getObjectUUID(), e);
+        }
+
+        //Update mobile list
+        try {
+            updateMobileList(player, origin);
+        } catch (Exception e) {
+            Logger.error("InterestManager.updateAllMobilePlayers: " + player.getObjectUUID(), e);
+        }
+    }
+
+    public synchronized void HandleLoadForTeleport(PlayerCharacter player) {
+
+        if (player == null)
+            return;
+
+        ClientConnection origin = player.getClientConnection();
+
+        if (origin == null)
+            return;
+
+        //Update static list
+        try {
+            updateStaticList(player, origin);
+        } catch (Exception e) {
+            Logger.error("InterestManager.updateAllStaticPlayers: " + player.getObjectUUID(), e);
+        }
+
+        //Update mobile list
+        try {
+            updateMobileList(player, origin);
+        } catch (Exception e) {
+            Logger.error("InterestManager.updateAllMobilePlayers: " + player.getObjectUUID(), e);
+        }
+    }
+
+    public static void reloadCharacter(AbstractCharacter absChar) {
+
+        UnloadObjectsMsg uom = new UnloadObjectsMsg();
+        uom.addObject(absChar);
+        LoadCharacterMsg lcm = new LoadCharacterMsg(absChar, false);
+
+        HashSet<AbstractWorldObject> toSend = WorldGrid.getObjectsInRangePartial(absChar.getLoc(), MBServerStatics.CHARACTER_LOAD_RANGE,
+                MBServerStatics.MASK_PLAYER);
+
+        PlayerCharacter pc = null;
+
+        if (absChar.getObjectType().equals(GameObjectType.PlayerCharacter))
+            pc = (PlayerCharacter) absChar;
+
+        for (AbstractWorldObject awo : toSend) {
+
+            PlayerCharacter pcc = (PlayerCharacter) awo;
+
+            if (pcc == null)
+                continue;
+
+            ClientConnection cc = SessionManager.getClientConnection(pcc);
+
+            if (cc == null)
+                continue;
+
+            if (pcc.getObjectUUID() == absChar.getObjectUUID())
+                continue;
+
+            else {
+                if (pc != null)
+                    if (pcc.getSeeInvis() < pc.getHidden())
+                        continue;
+
+                if (!cc.sendMsg(uom)) {
+                    String classType = uom.getClass().getSimpleName();
+                    Logger.error("Failed to send message ");
+                }
+
+                if (!cc.sendMsg(lcm)) {
+                    String classType = lcm.getClass().getSimpleName();
+                    Logger.error("Failed to send message");
+                }
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/engine/InterestManagement/RealmMap.java b/src/engine/InterestManagement/RealmMap.java
new file mode 100644
index 00000000..b5604526
--- /dev/null
+++ b/src/engine/InterestManagement/RealmMap.java
@@ -0,0 +1,102 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+package engine.InterestManagement;
+
+/* This class is the main interface for Magicbane's
+*  Interest management facilities.
+*/
+
+import engine.Enum;
+import engine.math.Vector3fImmutable;
+import engine.net.Dispatch;
+import engine.net.DispatchMessage;
+import engine.net.client.msg.TerritoryChangeMessage;
+import engine.objects.City;
+import engine.objects.PlayerCharacter;
+import engine.objects.Realm;
+import engine.server.MBServerStatics;
+import engine.util.MapLoader;
+import org.pmw.tinylog.Logger;
+
+import static engine.objects.Realm.getRealm;
+
+public class RealmMap {
+
+    // Spatial hashmap.  Used for detecting which Realm
+    // a player is currently in..
+    
+    public static int[][] _realmImageMap;
+
+
+    public static int getRealmIDAtLocation(Vector3fImmutable pos) {
+
+        int xBuckets = (int) ((pos.getX() / MBServerStatics.MAX_WORLD_WIDTH) * MBServerStatics.SPATIAL_HASH_BUCKETSX);
+        int yBuckets = (int) ((pos.getZ() / MBServerStatics.MAX_WORLD_HEIGHT) * MBServerStatics.SPATIAL_HASH_BUCKETSY);
+
+        if (yBuckets < 0 || yBuckets >= MBServerStatics.SPATIAL_HASH_BUCKETSY
+                || xBuckets < 0 || xBuckets >= MBServerStatics.SPATIAL_HASH_BUCKETSX) {
+            Logger.error("WorldServerRealm.getRealmFromPosition",
+                    "Invalid range; Z: " + yBuckets + ", X: " + xBuckets);
+            return 255;
+        }
+
+        return RealmMap._realmImageMap[xBuckets][yBuckets];
+    }
+
+    public static Realm getRealmForCity(City city) {
+        Realm outRealm = null;
+        outRealm = city.getRealm();
+        return outRealm;
+    }
+
+    public static Realm getRealmAtLocation(Vector3fImmutable worldVector) {
+
+        return getRealm(RealmMap.getRealmIDAtLocation(worldVector));
+
+    }
+
+    public static void updateRealm(PlayerCharacter player){
+
+        int realmID = RealmMap.getRealmIDAtLocation(player.getLoc());
+
+        if (realmID != player.getLastRealmID()){
+            player.setLastRealmID(realmID);
+            Realm realm = Realm.getRealm(realmID);
+            if (realm != null){
+                if (realm.isRuled()){
+                    City city = realm.getRulingCity();
+                    if (city != null){
+                        TerritoryChangeMessage tcm = new TerritoryChangeMessage((PlayerCharacter)realm.getRulingCity().getOwner(),realm);
+                        Dispatch dispatch = Dispatch.borrow(player, tcm);
+                        DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.PRIMARY);
+                    }else{
+                        TerritoryChangeMessage tcm = new TerritoryChangeMessage(null,realm);
+                        Dispatch dispatch = Dispatch.borrow(player, tcm);
+                        DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.PRIMARY);
+                    }
+
+                }else{
+                    TerritoryChangeMessage tcm = new TerritoryChangeMessage(null,realm);
+                    Dispatch dispatch = Dispatch.borrow(player, tcm);
+                    DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.PRIMARY);
+                }
+            }
+
+        }
+
+
+    }
+
+    public static void loadRealmImageMap() {
+
+        RealmMap._realmImageMap = MapLoader.loadMap();
+
+    }
+
+}
diff --git a/src/engine/InterestManagement/WorldGrid.java b/src/engine/InterestManagement/WorldGrid.java
new file mode 100644
index 00000000..af932325
--- /dev/null
+++ b/src/engine/InterestManagement/WorldGrid.java
@@ -0,0 +1,312 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+package engine.InterestManagement;
+
+import engine.Enum.GridObjectType;
+import engine.math.FastMath;
+import engine.math.Vector3f;
+import engine.math.Vector3fImmutable;
+import engine.net.DispatchMessage;
+import engine.net.client.ClientConnection;
+import engine.net.client.msg.LoadCharacterMsg;
+import engine.net.client.msg.LoadStructureMsg;
+import engine.net.client.msg.UnloadObjectsMsg;
+import engine.objects.*;
+import engine.server.MBServerStatics;
+
+import java.util.HashSet;
+import java.util.concurrent.ConcurrentHashMap;
+
+
+public class WorldGrid {
+	
+	public static ConcurrentHashMap<Integer,AbstractWorldObject>[][] DynamicGridMap;
+	public static ConcurrentHashMap<Integer,AbstractWorldObject>[][] StaticGridMap;
+	private static float dynamicBucketScale = 0.00390625f; // 256 bucket size, 1/256
+	private static float staticBucketScale = 0.00390625f;
+	public static void startLoadJob() {
+
+		Thread loadJobThread;
+		
+		
+		loadJobThread = new Thread(InterestManager.INTERESTMANAGER);
+		loadJobThread.setName("InterestManager");
+		loadJobThread.start();
+	}
+
+	public static boolean moveWorldObject(AbstractWorldObject awo, Vector3fImmutable location) {
+		awo.setLoc(location);
+		return true;
+	}
+
+	public static HashSet<AbstractWorldObject> getInRange(Vector3f loc, double r) {
+		HashSet<AbstractWorldObject> outbound = new HashSet<>();
+		return outbound;
+	}
+
+	public static HashSet<AbstractWorldObject> getObjectsInRangePartial(Vector3fImmutable loc, double r, int mask) {
+		HashSet<AbstractWorldObject> outbound = new HashSet<>();
+		float scale;
+		
+		if ((mask & MBServerStatics.MASK_STATIC) != 0)
+			scale = WorldGrid.staticBucketScale;
+		else
+			scale = WorldGrid.dynamicBucketScale;
+		int gridX = (int) Math.abs(loc.x * scale);
+		int gridZ = (int)Math.abs(loc.z * scale);
+			int bucketSize = (int) (r *scale) + 1;
+			//start at top left most corner to scan.
+			int startingX = gridX - bucketSize;
+			int startingZ = gridZ + bucketSize;
+			
+			
+			
+			int limitX = Math.abs((int) (MBServerStatics.MAX_WORLD_WIDTH *scale));
+			int limitZ = Math.abs((int) (MBServerStatics.MAX_WORLD_HEIGHT *scale)); //LimitZ is negative, remember to flip sign.
+			
+			if (startingX < 0)
+				startingX = 0;
+			
+			if (startingZ < 0)
+				startingZ = 0;
+			
+			if (startingX > limitX)
+				startingX = limitX;
+			
+			if (startingZ > limitZ)
+				startingZ = limitZ;
+			
+			int endX = startingX + (bucketSize * 2);
+			int endZ = startingZ - (bucketSize * 2);
+			
+			if (endX < 0)
+				endX = 0;
+			
+			if (endZ < 0)
+				endZ = 0;
+			
+			if (endX > limitX)
+				endX = limitX;
+			
+			if (endZ > limitZ)
+				endZ = limitZ;
+			
+			int auditMob = 0;
+			for (int x = startingX;x<=endX;x++){
+ 				for (int z = startingZ;z >= endZ;z--){
+ 					
+ 					ConcurrentHashMap<Integer,AbstractWorldObject> gridMap;
+ 					
+ 					if ((MBServerStatics.MASK_STATIC & mask) != 0)
+ 						gridMap = WorldGrid.StaticGridMap[x][z];
+ 					else
+ 						gridMap = WorldGrid.DynamicGridMap[x][z];
+					for (AbstractWorldObject gridObject: gridMap.values()){
+						if ((gridObject.getObjectTypeMask() & mask) == 0)
+							continue;
+						if (gridObject.getLoc().distanceSquared2D(loc) <= FastMath.sqr(r))
+							outbound.add(gridObject);
+					}
+				}
+			}
+		return outbound;
+	}
+
+	public static HashSet<AbstractWorldObject> getObjectsInRangePartialNecroPets(Vector3fImmutable loc, double r) {
+		HashSet<AbstractWorldObject> outbound = new HashSet<>();
+		return outbound;
+	}
+
+	public static HashSet<AbstractWorldObject> getObjectsInRangeContains(Vector3fImmutable loc, double r, int mask) {
+		HashSet<AbstractWorldObject> outbound = getObjectsInRangePartial(loc,r,mask);
+		return outbound;
+	}
+
+	public static HashSet<AbstractWorldObject> getObjectsInRangePartial(AbstractWorldObject awo, double range, int mask) {
+		return getObjectsInRangePartial(awo.getLoc(), range, mask);
+	}
+
+	
+	public static void InitializeGridObjects(){
+		
+		int dynamicWidth = (int) Math.abs(MBServerStatics.MAX_WORLD_WIDTH *WorldGrid.dynamicBucketScale);
+		int dynamicHeight = (int) Math.abs(MBServerStatics.MAX_WORLD_HEIGHT*WorldGrid.dynamicBucketScale);
+		
+		int staticWidth = (int) Math.abs(MBServerStatics.MAX_WORLD_WIDTH *WorldGrid.staticBucketScale);
+		int staticHeight = (int) Math.abs(MBServerStatics.MAX_WORLD_HEIGHT*WorldGrid.staticBucketScale);
+		WorldGrid.DynamicGridMap = new ConcurrentHashMap[dynamicWidth+ 1][dynamicHeight + 1];
+		WorldGrid.StaticGridMap = new ConcurrentHashMap[staticWidth + 1][staticHeight + 1];
+		//create new hash maps for each bucket
+		for (int x = 0; x<= staticWidth; x++)
+			for (int y = 0; y<= staticHeight; y++){
+				WorldGrid.StaticGridMap[x][y] = new ConcurrentHashMap<Integer,AbstractWorldObject>();
+			}
+		
+		for (int x = 0; x<= dynamicWidth; x++)
+			for (int y = 0; y<= dynamicHeight; y++){
+				WorldGrid.DynamicGridMap[x][y] = new ConcurrentHashMap<Integer,AbstractWorldObject>();
+			}
+				
+	}
+	
+	public static void RemoveWorldObject(AbstractWorldObject gridObject){
+		
+		if (gridObject == null)
+			return;
+		AbstractWorldObject.RemoveFromWorldGrid(gridObject);
+	}
+	
+	public static boolean addObject(AbstractWorldObject gridObject, float x, float z){
+		
+		if (gridObject == null)
+			return false;
+		
+		if (x > MBServerStatics.MAX_WORLD_WIDTH)
+			return false;
+		
+		if (z < MBServerStatics.MAX_WORLD_HEIGHT)
+			return false;
+		
+		if (x < 0)
+			return false;
+		if (z > 0)
+			return false;
+		
+		int gridX;
+		int gridZ;
+		
+		if (gridObject.getGridObjectType().equals(GridObjectType.STATIC)){
+			 gridX = Math.abs((int) (x *WorldGrid.staticBucketScale));
+			 gridZ = Math.abs((int) (z*WorldGrid.staticBucketScale));
+		}else{
+			 gridX = Math.abs((int) (x *WorldGrid.dynamicBucketScale));
+			 gridZ = Math.abs((int) (z*WorldGrid.dynamicBucketScale));
+		}
+		
+		
+		WorldGrid.RemoveWorldObject(gridObject);
+		
+		return AbstractWorldObject.AddToWorldGrid(gridObject, gridX, gridZ);
+		
+		
+	}
+
+    public static void unloadObject(AbstractWorldObject awo) {
+
+        UnloadObjectsMsg uom = new UnloadObjectsMsg();
+        uom.addObject(awo);
+        DispatchMessage.sendToAllInRange(awo, uom);
+    }
+
+	public static void loadObject(AbstractWorldObject awo) {
+
+		LoadStructureMsg lsm;
+		LoadCharacterMsg lcm;
+
+		switch (awo.getObjectType()) {
+		case Building:
+			lsm = new LoadStructureMsg();
+			lsm.addObject((Building)awo);
+			DispatchMessage.sendToAllInRange(awo, lsm);
+			break;
+		case NPC:
+			lcm = new LoadCharacterMsg((NPC) awo, false);
+			DispatchMessage.sendToAllInRange(awo, lcm);
+			break;
+		case Mob:
+			lcm = new LoadCharacterMsg((Mob) awo, false);
+			DispatchMessage.sendToAllInRange(awo, lcm);
+			break;
+		default:
+			// *** Refactor: Log error?
+			break;
+		}
+	}
+
+	public static void loadObject(AbstractWorldObject awo, ClientConnection origin) {
+
+		LoadStructureMsg lsm;
+		LoadCharacterMsg lcm;
+
+		switch (awo.getObjectType()) {
+
+		case Building:
+			lsm = new LoadStructureMsg();
+			lsm.addObject((Building)awo);
+			DispatchMessage.sendToAllInRange(awo, lsm);
+			break;
+		case NPC:
+			lcm = new LoadCharacterMsg((NPC) awo, false);
+			DispatchMessage.sendToAllInRange(awo, lcm);
+			break;
+		case Mob:
+			lcm = new LoadCharacterMsg((Mob) awo, false);
+			DispatchMessage.sendToAllInRange(awo, lcm);
+			break;
+		case PlayerCharacter:
+			lcm = new LoadCharacterMsg((PlayerCharacter) awo, false);
+			DispatchMessage.sendToAllInRange(awo, lcm);
+			break;
+		default:
+			// *** Refactor: Log error?
+			break;
+		}
+	}
+
+	public static void unloadObject(AbstractWorldObject awo,
+									ClientConnection origin) {
+		UnloadObjectsMsg uom = new UnloadObjectsMsg();
+		uom.addObject(awo);
+		DispatchMessage.sendToAllInRange(awo, uom);
+	}
+
+	public static void addObject(AbstractWorldObject awo, PlayerCharacter pc) {
+		if (pc == null || awo == null)
+			return;
+		ClientConnection origin = pc.getClientConnection();
+		if (origin == null)
+			return;
+		loadObject(awo, origin);
+	}
+
+	public static void removeObject(AbstractWorldObject awo, PlayerCharacter pc) {
+		if (pc == null || awo == null)
+			return;
+		ClientConnection origin = pc.getClientConnection();
+		if (origin == null)
+			return;
+		unloadObject(awo, origin);
+	}
+
+	public static void updateObject(AbstractWorldObject awo, PlayerCharacter pc) {
+		if (pc == null || awo == null)
+			return;
+		ClientConnection origin = pc.getClientConnection();
+		if (origin == null)
+			return;
+		unloadObject(awo, origin);
+		loadObject(awo, origin);
+	}
+
+	public static void updateObject(AbstractWorldObject awo) {
+		if (awo == null)
+			return;
+		unloadObject(awo);
+		loadObject(awo);
+	}
+
+	/*
+	 *
+	 */
+	public static void removeObject(AbstractWorldObject awo) {
+		if (awo == null)
+			return;
+		unloadObject(awo);
+	}
+}
diff --git a/src/engine/ai/MobileFSM.java b/src/engine/ai/MobileFSM.java
new file mode 100644
index 00000000..4861fa4e
--- /dev/null
+++ b/src/engine/ai/MobileFSM.java
@@ -0,0 +1,1769 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.ai;
+
+
+import engine.Enum;
+import engine.Enum.DispatchChannel;
+import engine.Enum.GameObjectType;
+import engine.InterestManagement.WorldGrid;
+import engine.ai.utilities.CombatUtilities;
+import engine.ai.utilities.MovementUtilities;
+import engine.gameManager.BuildingManager;
+import engine.gameManager.CombatManager;
+import engine.gameManager.MovementManager;
+import engine.gameManager.PowersManager;
+import engine.math.Vector3fImmutable;
+import engine.net.DispatchMessage;
+import engine.net.client.msg.PerformActionMsg;
+import engine.net.client.msg.PowerProjectileMsg;
+import engine.net.client.msg.UpdateStateMsg;
+import engine.objects.*;
+import engine.powers.PowersBase;
+import engine.server.MBServerStatics;
+import org.pmw.tinylog.Logger;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map.Entry;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ThreadLocalRandom;
+
+import static engine.math.FastMath.sqr;
+
+public class MobileFSM {
+
+
+    public enum STATE {
+        Disabled,
+        Respawn,
+        Idle,
+        Awake,
+        Aggro,
+        Patrol,
+        Help,
+        Attack,
+        Home,
+        Dead,
+        Recalling,
+        Retaliate
+    }
+
+    public static void run(Mob mob) {
+        if (mob == null) {
+            return;
+        }
+
+
+        STATE state = mob.getState();
+        switch (state) {
+            case Idle:
+                if (mob.isAlive())
+                    mob.updateLocation();
+
+                if (mob.isPlayerGuard()) {
+                    guardAwake(mob);
+                    break;
+                }
+
+                idle(mob);
+                break;
+            case Awake:
+
+                if (mob.isAlive())
+                    mob.updateLocation();
+
+                if (mob.isPlayerGuard())
+                    guardAwake(mob);
+                else if (mob.isSiege() == false) {
+                    if (mob.isPet())
+                        petAwake(mob);
+                    else if (mob.isGuard())
+                        awakeNPCguard(mob);
+                    else
+                        awake(mob);
+                }
+
+                break;
+            case Aggro:
+
+
+                if (mob.isAlive())
+                    mob.updateLocation();
+
+                if (mob.isPlayerGuard())
+                    guardAggro(mob, mob.getAggroTargetID());
+                else
+                    aggro(mob, mob.getAggroTargetID());
+                break;
+            case Patrol:
+
+                if (mob.isAlive())
+                    mob.updateLocation();
+
+                if (mob.isPlayerGuard())
+                    guardPatrol(mob);
+                else
+                    patrol(mob);
+                break;
+            case Attack:
+                if (mob.isAlive())
+                    mob.updateLocation();
+
+
+                if (!mob.isCombat()) {
+                    mob.setCombat(true);
+                    UpdateStateMsg rwss = new UpdateStateMsg();
+                    rwss.setPlayer(mob);
+                    DispatchMessage.sendToAllInRange(mob, rwss);
+                }
+
+                if (mob.isPlayerGuard())
+                    guardAttack(mob);
+                else if (mob.isPet() || mob.isSiege())
+                    petAttack(mob);
+                else if (mob.isGuard())
+                    guardAttackMob(mob);
+                else
+                    mobAttack(mob);
+                break;
+            case Home:
+                if (mob.isPlayerGuard())
+                    guardHome(mob, mob.isWalkingHome());
+                else
+                    home(mob, mob.isWalkingHome());
+                break;
+            case Dead:
+                dead(mob);
+                break;
+            case Respawn:
+                respawn(mob);
+                break;
+            case Recalling:
+                recalling(mob);
+                break;
+            case Retaliate:
+                retaliate(mob);
+                break;
+        }
+    }
+
+    public static boolean setAwake(Mob aiAgent, boolean force) {
+        if (force) {
+            aiAgent.setState(STATE.Awake);
+            return true;
+        }
+        if (aiAgent.getState() == STATE.Idle) {
+            aiAgent.setState(STATE.Awake);
+            return true;
+        }
+        return false;
+    }
+
+    public static boolean setAggro(Mob aiAgent, int targetID) {
+        if (aiAgent.getState() != STATE.Dead) {
+            aiAgent.setNoAggro(false);
+            aiAgent.setAggroTargetID(targetID);
+            aiAgent.setState(STATE.Aggro);
+            return true;
+        }
+        return false;
+    }
+
+    public static Mob getMobile(int mobileID) {
+        return Mob.getFromCache(mobileID);
+    }
+
+    private static void idle(Mob mob) {
+
+        if (mob.getLoc().distanceSquared2D(mob.getBindLoc()) > sqr(2000)) {
+
+            mob.setWalkingHome(false);
+            mob.setState(STATE.Home);
+        }
+    }
+
+
+    private static void awake(Mob aiAgent) {
+        if (!aiAgent.isAlive()) {
+            aiAgent.setState(STATE.Dead);
+            return;
+        }
+
+        if (aiAgent.getLoc().distanceSquared2D(aiAgent.getBindLoc()) > sqr(2000)) {
+            aiAgent.setWalkingHome(false);
+            aiAgent.setState(STATE.Home);
+            return;
+        }
+        //Don't attempt to aggro if No aggro is on and aiAgent is not home yet.
+        if (aiAgent.isNoAggro() && aiAgent.isMoving()) {
+            return;
+        }
+
+        //Mob stopped Moving let's turn aggro back on.
+        if (aiAgent.isNoAggro()) {
+            aiAgent.setNoAggro(false);
+        }
+        //no players currently have this mob loaded. return to IDLE.
+        if (aiAgent.getPlayerAgroMap().isEmpty()) {
+            aiAgent.setState(STATE.Idle);
+            return;
+        }
+
+
+        //currently npc guards wont patrol or aggro
+        if (aiAgent.isGuard()) {
+            return;
+        }
+
+        //Get the Map for Players that loaded this mob.
+
+        ConcurrentHashMap<Integer, Boolean> loadedPlayers = aiAgent.getPlayerAgroMap();
+
+
+        if (!Enum.MobFlagType.AGGRESSIVE.elementOf(aiAgent.getMobBase().getFlags()) && aiAgent.getCombatTarget() == null) {
+            //attempt to patrol even if aiAgent isn't aggresive;
+
+            int patrolRandom = ThreadLocalRandom.current().nextInt(1000);
+            if (patrolRandom <= MBServerStatics.AI_PATROL_DIVISOR) {
+                aiAgent.setState(STATE.Patrol);
+            }
+            return;
+        }
+        //aiAgent finished moving home, set aggro on.
+
+        for (Entry playerEntry : loadedPlayers.entrySet()) {
+            int playerID = (int) playerEntry.getKey();
+            PlayerCharacter loadedPlayer = PlayerCharacter.getFromCache(playerID);
+
+            //Player is null, let's remove them from the list.
+            if (loadedPlayer == null) {
+                //     Logger.error("MobileFSM", "Player with UID " + playerID + " returned null in mob.getPlayerAgroMap()");
+                loadedPlayers.remove(playerID);
+                continue;
+            }
+            //Player is Dead, Mob no longer needs to attempt to aggro. Remove them from aggro map.
+            if (!loadedPlayer.isAlive()) {
+                loadedPlayers.remove(playerID);
+                continue;
+            }
+            //Can't see target, skip aggro.
+            if (!aiAgent.canSee(loadedPlayer)) {
+                continue;
+            }
+
+            // No aggro for this race type
+            if (loadedPlayer.getRace().getRaceType().getAggroType().elementOf(aiAgent.getMobBase().getNoAggro()))
+                continue;
+
+
+            if (MovementUtilities.inRangeToAggro(aiAgent, loadedPlayer)) {
+                aiAgent.setAggroTargetID(playerID);
+                aiAgent.setState(STATE.Aggro);
+                return;
+            }
+
+
+        }
+
+        int patrolRandom = ThreadLocalRandom.current().nextInt(1000);
+        if (patrolRandom <= MBServerStatics.AI_PATROL_DIVISOR) {
+            aiAgent.setState(STATE.Patrol);
+        }
+
+    }
+
+    private static void guardAttackMob(Mob aiAgent) {
+        if (!aiAgent.isAlive()) {
+            aiAgent.setState(STATE.Dead);
+            return;
+        }
+
+        AbstractGameObject target = aiAgent.getCombatTarget();
+        if (target == null) {
+            aiAgent.setState(STATE.Awake);
+            return;
+        }
+
+        if (target.getObjectType().equals(GameObjectType.Mob) == false) {
+            aiAgent.setState(STATE.Awake);
+            return;
+        }
+
+        if (target.equals(aiAgent)) {
+            aiAgent.setState(STATE.Awake);
+            return;
+        }
+
+        Mob mob = (Mob) target;
+
+        if (!mob.isAlive() || mob.getState() == STATE.Dead) {
+            aiAgent.setCombatTarget(null);
+            aiAgent.setState(STATE.Awake);
+            return;
+        }
+
+        if (CombatUtilities.inRangeToAttack(aiAgent, mob)) {
+            //not time to attack yet.
+            if (System.currentTimeMillis() < aiAgent.getLastAttackTime()) {
+                return;
+            }
+
+            if (!CombatUtilities.RunAIRandom())
+                return;
+
+            if (aiAgent.getRange() >= 30 && aiAgent.isMoving())
+                return;
+            //no weapons, defualt mob attack speed 3 seconds.
+            ItemBase mainHand = aiAgent.getWeaponItemBase(true);
+            ItemBase offHand = aiAgent.getWeaponItemBase(false);
+            if (mainHand == null && offHand == null) {
+                CombatUtilities.combatCycle(aiAgent, mob, true, null);
+                int delay = 3000;
+                if (aiAgent.isSiege())
+                    delay = 11000;
+                aiAgent.setLastAttackTime(System.currentTimeMillis() + delay);
+
+            } else
+                //TODO set offhand attack time.
+                if (aiAgent.getWeaponItemBase(true) != null) {
+                    int attackDelay = 3000;
+                    if (aiAgent.isSiege())
+                        attackDelay = 11000;
+                    CombatUtilities.combatCycle(aiAgent, mob, true, aiAgent.getWeaponItemBase(true));
+                    aiAgent.setLastAttackTime(System.currentTimeMillis() + attackDelay);
+                } else if (aiAgent.getWeaponItemBase(false) != null) {
+                    int attackDelay = (int) (aiAgent.getSpeedHandTwo() * 100);
+                    if (aiAgent.isSiege())
+                        attackDelay = 3000;
+                    CombatUtilities.combatCycle(aiAgent, mob, false, aiAgent.getWeaponItemBase(false));
+                    aiAgent.setLastAttackTime(System.currentTimeMillis() + attackDelay);
+                }
+            return;
+
+        }
+        if (!MovementUtilities.updateMovementToCharacter(aiAgent, mob))
+            return;
+
+        if (!MovementUtilities.canMove(aiAgent))
+            return;
+
+
+        if (CombatUtilities.inRangeToAttack2D(aiAgent, mob))
+            return;
+
+
+        aiAgent.destination = MovementUtilities.GetDestinationToCharacter(aiAgent, mob);
+
+        MovementUtilities.moveToLocation(aiAgent, aiAgent.destination, aiAgent.getRange());
+    }
+
+    private static void awakeNPCguard(Mob aiAgent) {
+        if (!aiAgent.isAlive()) {
+            aiAgent.setState(STATE.Dead);
+            return;
+        }
+
+        // Player guards are bound to their city zone
+        // and recall when leaving it.
+
+        if (aiAgent.getLoc().distanceSquared2D(aiAgent.getBindLoc()) > sqr(2000)) {
+            aiAgent.setWalkingHome(false);
+            aiAgent.setState(STATE.Home);
+            return;
+        }
+
+        //Don't attempt to aggro if No aggro is on and aiAgent is not home yet.
+        //no players currently have this mob loaded. return to IDLE.
+        //currently npc guards wont patrol or aggro
+        //Get the Map for Players that loaded this mob.
+
+        HashSet<AbstractWorldObject> awoList = WorldGrid.getObjectsInRangePartial(aiAgent, 100, MBServerStatics.MASK_MOB);
+
+        for (AbstractWorldObject awoMob : awoList) {
+
+            //dont scan self.
+            if (aiAgent.equals(awoMob))
+                continue;
+
+            Mob mob = (Mob) awoMob;
+            //dont attack other guards
+            if (mob.isGuard())
+                continue;
+            if (aiAgent.getLoc().distanceSquared2D(mob.getLoc()) > sqr(50))
+                continue;
+            aiAgent.setCombatTarget(mob);
+            aiAgent.setState(STATE.Attack);
+        }
+    }
+
+    private static void petAwake(Mob aiAgent) {
+
+        if (!aiAgent.isAlive()) {
+            aiAgent.setState(STATE.Dead);
+            return;
+        }
+
+        PlayerCharacter petOwner = aiAgent.getOwner();
+
+        if (petOwner == null)
+            return;
+
+        //lets make mobs ai less twitchy, Don't call another movement until mob reaches it's destination.
+        if (aiAgent.isMoving())
+            return;
+
+        if (!MovementUtilities.canMove(aiAgent))
+            return;
+
+        if (petOwner.getLoc().distanceSquared2D(aiAgent.getLoc()) > MBServerStatics.AI_RECALL_RANGE * MBServerStatics.AI_RECALL_RANGE) {
+            aiAgent.teleport(petOwner.getLoc());
+            return;
+        }
+
+        if (petOwner.getLoc().distanceSquared2D(aiAgent.getLoc()) > 30 * 30) {
+            if (aiAgent.isMoving())
+                return;
+
+            if (!MovementUtilities.canMove(aiAgent))
+                return;
+            if (aiAgent.getLoc().distanceSquared2D(petOwner.getLoc()) < aiAgent.getRange() * aiAgent.getRange())
+                return;
+
+            MovementUtilities.moveToLocation(aiAgent, petOwner.getLoc(), aiAgent.getRange());
+        }
+    }
+
+    private static void aggro(Mob aiAgent, int targetID) {
+
+        if (!aiAgent.isAlive()) {
+            aiAgent.setState(STATE.Dead);
+            return;
+        }
+
+        if (aiAgent.getLoc().distanceSquared2D(aiAgent.getBindLoc()) > sqr(2000)) {
+            aiAgent.setWalkingHome(false);
+            aiAgent.setState(STATE.Home);
+            return;
+        }
+
+        if (!aiAgent.isCombat()) {
+            aiAgent.setCombat(true);
+            UpdateStateMsg rwss = new UpdateStateMsg();
+            rwss.setPlayer(aiAgent);
+            DispatchMessage.sendToAllInRange(aiAgent, rwss);
+        }
+
+        //a player got in aggro range. Move to player until in range of attack.
+        PlayerCharacter aggroTarget = PlayerCharacter.getFromCache(targetID);
+
+        if (aggroTarget == null) {
+            // Logger.error("MobileFSM.aggro", "aggro target with UUID " + targetID + " returned null");
+            aiAgent.getPlayerAgroMap().remove(targetID);
+            aiAgent.setAggroTargetID(0);
+            aiAgent.setState(STATE.Patrol);
+            return;
+        }
+        if (!aiAgent.canSee(aggroTarget)) {
+            aiAgent.setCombatTarget(null);
+            targetID = 0;
+            aiAgent.setState(STATE.Patrol);
+            return;
+        }
+
+        if (!aggroTarget.isActive()) {
+            aiAgent.setCombatTarget(null);
+            targetID = 0;
+            aiAgent.setState(STATE.Patrol);
+            return;
+        }
+
+        if (CombatUtilities.inRangeToAttack(aiAgent, aggroTarget)) {
+            aiAgent.setState(STATE.Attack);
+            attack(aiAgent, targetID);
+            return;
+        }
+
+        if (!MovementUtilities.inRangeDropAggro(aiAgent, aggroTarget)) {
+            aiAgent.setAggroTargetID(0);
+            aiAgent.setCombatTarget(null);
+            MovementUtilities.moveToLocation(aiAgent, aiAgent.getTrueBindLoc(), 0);
+            aiAgent.setState(STATE.Awake);
+            return;
+        }
+
+        if (!MovementUtilities.inRangeOfBindLocation(aiAgent)) {
+            aiAgent.setCombatTarget(null);
+            aiAgent.setAggroTargetID(0);
+            aiAgent.setState(STATE.Home);
+            return;
+        }
+
+        //use this so mobs dont continue to try to move if they are underneath a flying target. only use 2D range check.
+        if (CombatUtilities.inRangeToAttack2D(aiAgent, aggroTarget))
+            return;
+
+        if (!MovementUtilities.updateMovementToCharacter(aiAgent, aggroTarget))
+            return;
+
+        if (!MovementUtilities.canMove(aiAgent))
+            return;
+
+        if (aiAgent.getLoc().distanceSquared2D(aggroTarget.getLoc()) < aiAgent.getRange() * aiAgent.getRange())
+            return;
+
+        aiAgent.destination = MovementUtilities.GetDestinationToCharacter(aiAgent, aggroTarget);
+        MovementUtilities.moveToLocation(aiAgent, aiAgent.destination, aiAgent.getRange());
+
+    }
+
+    private static void petAttack(Mob aiAgent) {
+
+        if (!aiAgent.isAlive()) {
+            aiAgent.setState(STATE.Dead);
+            return;
+        }
+
+        AbstractGameObject target = aiAgent.getCombatTarget();
+
+        if (target == null) {
+            aiAgent.setState(STATE.Awake);
+            return;
+        }
+
+        switch (target.getObjectType()) {
+
+            case PlayerCharacter:
+
+                PlayerCharacter player = (PlayerCharacter) target;
+
+                if (!player.isActive()) {
+                    aiAgent.setCombatTarget(null);
+                    aiAgent.setState(STATE.Awake);
+                    return;
+                }
+
+                if (player.inSafeZone()) {
+                    aiAgent.setCombatTarget(null);
+                    aiAgent.setState(STATE.Awake);
+                    return;
+                }
+
+                handlePlayerAttackForPet(aiAgent, player);
+
+                break;
+            case Building:
+                Building building = (Building) target;
+                petHandleBuildingAttack(aiAgent, building);
+                break;
+            case Mob:
+                Mob mob = (Mob) target;
+                handleMobAttackForPet(aiAgent, mob);
+                break;
+        }
+    }
+
+    private static void mobAttack(Mob aiAgent) {
+
+        if (!aiAgent.isAlive()) {
+            aiAgent.setState(STATE.Dead);
+            return;
+        }
+
+        if (aiAgent.getLoc().distanceSquared2D(aiAgent.getBindLoc()) > sqr(2000)) {
+
+            aiAgent.setWalkingHome(false);
+            aiAgent.setState(STATE.Home);
+            return;
+        }
+
+        AbstractGameObject target = aiAgent.getCombatTarget();
+
+        if (target == null) {
+            aiAgent.setState(STATE.Patrol);
+            return;
+        }
+
+        switch (target.getObjectType()) {
+
+            case PlayerCharacter:
+
+                PlayerCharacter player = (PlayerCharacter) target;
+
+                if (!player.isActive()) {
+                    aiAgent.setCombatTarget(null);
+                    aiAgent.setState(STATE.Patrol);
+                    return;
+                }
+
+                if (aiAgent.isNecroPet() && player.inSafeZone()) {
+                    aiAgent.setCombatTarget(null);
+                    aiAgent.setState(STATE.Idle);
+                    return;
+                }
+
+                handlePlayerAttackForMob(aiAgent, player);
+                break;
+            case Building:
+                Building building = (Building) target;
+                petHandleBuildingAttack(aiAgent, building);
+                break;
+            case Mob:
+                Mob mob = (Mob) target;
+                handleMobAttackForMob(aiAgent, mob);
+        }
+    }
+
+    private static void petHandleBuildingAttack(Mob aiAgent, Building building) {
+
+        int buildingHitBox = (int) CombatManager.calcHitBox(building);
+
+        if (building.getRank() == -1) {
+            aiAgent.setCombatTarget(null);
+            aiAgent.setState(STATE.Awake);
+            return;
+        }
+
+        if (!building.isVulnerable()) {
+            aiAgent.setCombatTarget(null);
+            aiAgent.setState(STATE.Awake);
+            return;
+        }
+
+        if (BuildingManager.getBuildingFromCache(building.getObjectUUID()) == null) {
+            aiAgent.setCombatTarget(null);
+            aiAgent.setState(STATE.Awake);
+            return;
+        }
+
+        if (building.getParentZone() != null && building.getParentZone().isPlayerCity()) {
+
+            for (Mob mob : building.getParentZone().zoneMobSet) {
+
+                if (!mob.isPlayerGuard())
+                    continue;
+
+                if (mob.getCombatTarget() != null)
+                    continue;
+
+                if (mob.getGuild() != null && building.getGuild() != null)
+                    if (!Guild.sameGuild(mob.getGuild().getNation(), building.getGuild().getNation()))
+                        continue;
+
+                mob.setCombatTarget(aiAgent);
+                mob.setState(STATE.Attack);
+            }
+        }
+
+        if (CombatUtilities.inRangeToAttack(aiAgent, building)) {
+            //not time to attack yet.
+
+            if (!CombatUtilities.RunAIRandom())
+                return;
+
+            if (System.currentTimeMillis() < aiAgent.getLastAttackTime())
+                return;
+
+            if (aiAgent.getRange() >= 30 && aiAgent.isMoving())
+                return;
+
+            //reset attack animation
+            if (aiAgent.isSiege())
+                MovementManager.sendRWSSMsg(aiAgent);
+
+            //			Fire siege balls
+            //			 TODO: Fix animations not following stone
+
+            //no weapons, defualt mob attack speed 3 seconds.
+            ItemBase mainHand = aiAgent.getWeaponItemBase(true);
+            ItemBase offHand = aiAgent.getWeaponItemBase(false);
+
+            if (mainHand == null && offHand == null) {
+
+                CombatUtilities.combatCycle(aiAgent, building, true, null);
+                int delay = 3000;
+
+                if (aiAgent.isSiege())
+                    delay = 15000;
+
+                aiAgent.setLastAttackTime(System.currentTimeMillis() + delay);
+            } else
+                //TODO set offhand attack time.
+                if (aiAgent.getWeaponItemBase(true) != null) {
+
+                    int attackDelay = 3000;
+
+                    if (aiAgent.isSiege())
+                        attackDelay = 15000;
+
+                    CombatUtilities.combatCycle(aiAgent, building, true, aiAgent.getWeaponItemBase(true));
+                    aiAgent.setLastAttackTime(System.currentTimeMillis() + attackDelay);
+
+                } else if (aiAgent.getWeaponItemBase(false) != null) {
+
+                    int attackDelay = 3000;
+
+                    if (aiAgent.isSiege())
+                        attackDelay = 15000;
+
+                    CombatUtilities.combatCycle(aiAgent, building, false, aiAgent.getWeaponItemBase(false));
+                    aiAgent.setLastAttackTime(System.currentTimeMillis() + attackDelay);
+                }
+
+            if (aiAgent.isSiege()) {
+                PowerProjectileMsg ppm = new PowerProjectileMsg(aiAgent, building);
+                ppm.setRange(50);
+                DispatchMessage.dispatchMsgToInterestArea(aiAgent, ppm, DispatchChannel.SECONDARY, MBServerStatics.CHARACTER_LOAD_RANGE, false, false);
+            }
+            return;
+        }
+
+        //Outside of attack Range, Move to players predicted loc.
+
+        if (!aiAgent.isMoving())
+            if (MovementUtilities.canMove(aiAgent))
+                MovementUtilities.moveToLocation(aiAgent, building.getLoc(), aiAgent.getRange() + buildingHitBox);
+    }
+
+    private static void handlePlayerAttackForPet(Mob aiAgent, PlayerCharacter player) {
+
+        if (aiAgent.getMobBase().getSeeInvis() < player.getHidden()) {
+            aiAgent.setCombatTarget(null);
+            aiAgent.setState(STATE.Awake);
+            return;
+        }
+
+        if (!player.isAlive()) {
+            aiAgent.setCombatTarget(null);
+            aiAgent.setState(STATE.Awake);
+            return;
+        }
+
+        if (CombatUtilities.inRangeToAttack(aiAgent, player)) {
+            //not time to attack yet.
+            if (System.currentTimeMillis() < aiAgent.getLastAttackTime())
+                return;
+
+            if (!CombatUtilities.RunAIRandom())
+                return;
+
+            if (aiAgent.getRange() >= 30 && aiAgent.isMoving())
+                return;
+            // add timer for last attack.
+            //player.setTimeStamp("LastCombatPlayer", System.currentTimeMillis());
+            //no weapons, defualt mob attack speed 3 seconds.
+            ItemBase mainHand = aiAgent.getWeaponItemBase(true);
+            ItemBase offHand = aiAgent.getWeaponItemBase(false);
+
+            if (mainHand == null && offHand == null) {
+
+                CombatUtilities.combatCycle(aiAgent, player, true, null);
+
+                int delay = 3000;
+
+                if (aiAgent.isSiege())
+                    delay = 11000;
+
+                aiAgent.setLastAttackTime(System.currentTimeMillis() + delay);
+            }
+            //TODO set offhand attack time.
+
+            if (aiAgent.getWeaponItemBase(true) != null) {
+
+                int attackDelay = 3000;
+
+                if (aiAgent.isSiege())
+                    attackDelay = 11000;
+
+                CombatUtilities.combatCycle(aiAgent, player, true, aiAgent.getWeaponItemBase(true));
+                aiAgent.setLastAttackTime(System.currentTimeMillis() + attackDelay);
+
+            } else if (aiAgent.getWeaponItemBase(false) != null) {
+
+                int attackDelay = (int) (aiAgent.getSpeedHandTwo() * 100);
+
+                if (aiAgent.isSiege())
+                    attackDelay = 3000;
+
+                CombatUtilities.combatCycle(aiAgent, player, false, aiAgent.getWeaponItemBase(false));
+                aiAgent.setLastAttackTime(System.currentTimeMillis() + attackDelay);
+            }
+            return;
+        }
+
+        if (!MovementUtilities.updateMovementToCharacter(aiAgent, player))
+            return;
+
+        //out of range to attack move
+        if (!MovementUtilities.canMove(aiAgent))
+            return;
+
+        aiAgent.destination = MovementUtilities.GetDestinationToCharacter(aiAgent, player);
+        MovementUtilities.moveToLocation(aiAgent, aiAgent.destination, aiAgent.getRange());
+    }
+
+    private static void handlePlayerAttackForMob(Mob aiAgent, PlayerCharacter player) {
+
+        if (aiAgent.getMobBase().getSeeInvis() < player.getHidden()) {
+            aiAgent.setCombatTarget(null);
+            aiAgent.setState(STATE.Awake);
+            return;
+        }
+
+        if (!player.isAlive()) {
+            aiAgent.setCombatTarget(null);
+            aiAgent.setState(STATE.Awake);
+            return;
+        }
+
+        if (aiAgent.getLastMobPowerToken() != 0) {
+
+            PowersBase mobPower = PowersManager.getPowerByToken(aiAgent.getLastMobPowerToken());
+
+            if (System.currentTimeMillis() > aiAgent.getTimeStamp("FInishCast")) {
+                PerformActionMsg msg = PowersManager.createPowerMsg(mobPower, 40, aiAgent, player);
+                msg.setUnknown04(2);
+                PowersManager.finishUseMobPower(msg, aiAgent, 0, 0);
+                aiAgent.setLastMobPowerToken(0);
+                aiAgent.setIsCasting(false);
+            }
+            return;
+        }
+
+        if (System.currentTimeMillis() > aiAgent.getTimeStamp("CallForHelp")) {
+            CombatUtilities.CallForHelp(aiAgent);
+            aiAgent.getTimestamps().put("CallForHelp", System.currentTimeMillis() + 60000);
+        }
+
+        HashMap<Integer, Integer> staticPowers = aiAgent.getMobBase().getStaticPowers();
+
+        if (staticPowers != null && !staticPowers.isEmpty()) {
+            int chance = ThreadLocalRandom.current().nextInt(300);
+
+            if (chance <= 1) {
+
+                int randomPower = ThreadLocalRandom.current().nextInt(staticPowers.size());
+                int powerToken = (int) staticPowers.keySet().toArray()[randomPower];
+                PowersBase pb = PowersManager.getPowerByToken(powerToken);
+
+                if (pb == null)
+                    return;
+
+                if (System.currentTimeMillis() > aiAgent.getTimeStamp(pb.getIDString())) {
+
+                    PowersManager.useMobPower(aiAgent, player, pb, staticPowers.get(powerToken));
+
+                    int cooldown = pb.getRecycleTime(staticPowers.get(powerToken));
+                    aiAgent.getTimestamps().put(pb.getIDString(), System.currentTimeMillis() + cooldown + (pb.getToken() == 429023263 ? 10000 : 120000));
+                    return;
+                }
+            }
+        }
+
+        if (!MovementUtilities.inRangeOfBindLocation(aiAgent)) {
+            aiAgent.setCombatTarget(null);
+            aiAgent.setAggroTargetID(0);
+            aiAgent.setWalkingHome(false);
+            aiAgent.setState(STATE.Home);
+            return;
+        }
+
+        if (!MovementUtilities.inRangeDropAggro(aiAgent, player)) {
+            aiAgent.setAggroTargetID(0);
+            aiAgent.setCombatTarget(null);
+            MovementUtilities.moveToLocation(aiAgent, aiAgent.getTrueBindLoc(), 0);
+            aiAgent.setState(STATE.Awake);
+            return;
+        }
+
+        if (CombatUtilities.inRangeToAttack(aiAgent, player)) {
+
+            //no weapons, defualt mob attack speed 3 seconds.
+
+            if (System.currentTimeMillis() < aiAgent.getLastAttackTime())
+                return;
+
+            if (!CombatUtilities.RunAIRandom())
+                return;
+
+            // ranged mobs cant attack while running. skip until they finally stop.
+            if (aiAgent.getRange() >= 30 && aiAgent.isMoving())
+                return;
+
+            // add timer for last attack.
+            //	player.setTimeStamp("LastCombatPlayer", System.currentTimeMillis());
+            ItemBase mainHand = aiAgent.getWeaponItemBase(true);
+            ItemBase offHand = aiAgent.getWeaponItemBase(false);
+
+            if (mainHand == null && offHand == null) {
+
+                CombatUtilities.combatCycle(aiAgent, player, true, null);
+                int delay = 3000;
+
+                if (aiAgent.isSiege())
+                    delay = 11000;
+
+                aiAgent.setLastAttackTime(System.currentTimeMillis() + delay);
+
+            } else
+                //TODO set offhand attack time.
+                if (aiAgent.getWeaponItemBase(true) != null) {
+
+                    int delay = 3000;
+
+                    if (aiAgent.isSiege())
+                        delay = 11000;
+
+                    CombatUtilities.combatCycle(aiAgent, player, true, aiAgent.getWeaponItemBase(true));
+                    aiAgent.setLastAttackTime(System.currentTimeMillis() + delay);
+                } else if (aiAgent.getWeaponItemBase(false) != null) {
+
+                    int attackDelay = 3000;
+
+                    if (aiAgent.isSiege())
+                        attackDelay = 11000;
+
+                    CombatUtilities.combatCycle(aiAgent, player, false, aiAgent.getWeaponItemBase(false));
+                    aiAgent.setLastAttackTime(System.currentTimeMillis() + attackDelay);
+                }
+            return;
+        }
+
+        if (!MovementUtilities.updateMovementToCharacter(aiAgent, player))
+            return;
+
+        if (!MovementUtilities.canMove(aiAgent))
+            return;
+
+        //this stops mobs from attempting to move while they are underneath a player.
+        if (CombatUtilities.inRangeToAttack2D(aiAgent, player))
+            return;
+
+        aiAgent.destination = MovementUtilities.GetDestinationToCharacter(aiAgent, player);
+        MovementUtilities.moveToLocation(aiAgent, aiAgent.destination, aiAgent.getRange());
+
+    }
+
+    private static void handleMobAttackForPet(Mob aiAgent, Mob mob) {
+
+        if (!mob.isAlive()) {
+            aiAgent.setCombatTarget(null);
+            aiAgent.setState(STATE.Awake);
+            return;
+        }
+
+        if (CombatUtilities.inRangeToAttack(aiAgent, mob)) {
+            //not time to attack yet.
+            if (System.currentTimeMillis() < aiAgent.getLastAttackTime())
+                return;
+
+            if (!CombatUtilities.RunAIRandom())
+                return;
+
+            if (aiAgent.getRange() >= 30 && aiAgent.isMoving())
+                return;
+
+            //no weapons, defualt mob attack speed 3 seconds.
+            ItemBase mainHand = aiAgent.getWeaponItemBase(true);
+            ItemBase offHand = aiAgent.getWeaponItemBase(false);
+
+            if (mainHand == null && offHand == null) {
+
+                CombatUtilities.combatCycle(aiAgent, mob, true, null);
+
+                int delay = 3000;
+
+                if (aiAgent.isSiege())
+                    delay = 11000;
+
+                aiAgent.setLastAttackTime(System.currentTimeMillis() + delay);
+
+            } else
+                //TODO set offhand attack time.
+                if (aiAgent.getWeaponItemBase(true) != null) {
+
+                    int attackDelay = 3000;
+
+                    if (aiAgent.isSiege())
+                        attackDelay = 11000;
+
+                    CombatUtilities.combatCycle(aiAgent, mob, true, aiAgent.getWeaponItemBase(true));
+                    aiAgent.setLastAttackTime(System.currentTimeMillis() + attackDelay);
+
+                } else if (aiAgent.getWeaponItemBase(false) != null) {
+
+                    int attackDelay = (int) (aiAgent.getSpeedHandTwo() * 100);
+
+                    if (aiAgent.isSiege())
+                        attackDelay = 3000;
+
+                    CombatUtilities.combatCycle(aiAgent, mob, false, aiAgent.getWeaponItemBase(false));
+                    aiAgent.setLastAttackTime(System.currentTimeMillis() + attackDelay);
+                }
+            return;
+        }
+
+        if (!MovementUtilities.updateMovementToCharacter(aiAgent, mob))
+            return;
+
+        if (!MovementUtilities.canMove(aiAgent))
+            return;
+
+        if (CombatUtilities.inRangeToAttack2D(aiAgent, mob))
+            return;
+
+        aiAgent.destination = MovementUtilities.GetDestinationToCharacter(aiAgent, mob);
+        MovementUtilities.moveToLocation(aiAgent, aiAgent.destination, aiAgent.getRange());
+    }
+
+    private static void handleMobAttackForMob(Mob aiAgent, Mob mob) {
+
+
+        if (!mob.isAlive()) {
+            aiAgent.setCombatTarget(null);
+            aiAgent.setState(STATE.Awake);
+            return;
+        }
+
+        if (CombatUtilities.inRangeToAttack(aiAgent, mob)) {
+            //not time to attack yet.
+            if (System.currentTimeMillis() < aiAgent.getLastAttackTime()) {
+                return;
+            }
+
+            if (!CombatUtilities.RunAIRandom())
+                return;
+
+            if (aiAgent.getRange() >= 30 && aiAgent.isMoving())
+                return;
+            //no weapons, defualt mob attack speed 3 seconds.
+            ItemBase mainHand = aiAgent.getWeaponItemBase(true);
+            ItemBase offHand = aiAgent.getWeaponItemBase(false);
+
+            if (mainHand == null && offHand == null) {
+
+                CombatUtilities.combatCycle(aiAgent, mob, true, null);
+                int delay = 3000;
+
+                if (aiAgent.isSiege())
+                    delay = 11000;
+
+                aiAgent.setLastAttackTime(System.currentTimeMillis() + delay);
+            } else
+                //TODO set offhand attack time.
+                if (aiAgent.getWeaponItemBase(true) != null) {
+
+                    int attackDelay = 3000;
+
+                    if (aiAgent.isSiege())
+                        attackDelay = 11000;
+
+                    CombatUtilities.combatCycle(aiAgent, mob, true, aiAgent.getWeaponItemBase(true));
+                    aiAgent.setLastAttackTime(System.currentTimeMillis() + attackDelay);
+
+                } else if (aiAgent.getWeaponItemBase(false) != null) {
+
+                    int attackDelay = 3000;
+
+                    if (aiAgent.isSiege())
+                        attackDelay = 11000;
+
+                    CombatUtilities.combatCycle(aiAgent, mob, false, aiAgent.getWeaponItemBase(false));
+                    aiAgent.setLastAttackTime(System.currentTimeMillis() + attackDelay);
+                }
+            return;
+        }
+
+        //use this so mobs dont continue to try to move if they are underneath a flying target. only use 2D range check.
+        if (CombatUtilities.inRangeToAttack2D(aiAgent, mob))
+            return;
+
+        if (!MovementUtilities.updateMovementToCharacter(aiAgent, mob))
+            return;
+
+        //out of range to attack move
+        if (!MovementUtilities.canMove(aiAgent))
+            return;
+
+        aiAgent.destination = MovementUtilities.GetDestinationToCharacter(aiAgent, mob);
+        MovementUtilities.moveToLocation(aiAgent, aiAgent.destination, aiAgent.getRange());
+    }
+
+    private static void attack(Mob aiAgent, int targetID) {
+
+        //in range to attack, start attacking now!
+        if (!aiAgent.isAlive()) {
+            aiAgent.setState(STATE.Dead);
+            return;
+        }
+
+        PlayerCharacter aggroTarget = PlayerCharacter.getFromCache(targetID);
+
+        if (aggroTarget == null) {
+            //  Logger.error("MobileFSM.aggro", "aggro target with UUID " + targetID + " returned null");
+            aiAgent.getPlayerAgroMap().remove(targetID);
+            aiAgent.setAggroTargetID(0);
+            aiAgent.setState(STATE.Patrol);
+            return;
+        }
+
+        if (aiAgent.getMobBase().getSeeInvis() < aggroTarget.getHidden()) {
+            aiAgent.setAggroTargetID(0);
+            aiAgent.setCombatTarget(null);
+            aiAgent.setState(STATE.Patrol);
+            return;
+        }
+
+        if (!aggroTarget.isAlive()) {
+            aiAgent.setAggroTargetID(0);
+            aiAgent.setCombatTarget(null);
+            aiAgent.setState(STATE.Patrol);
+            return;
+        }
+
+        HashMap<Integer, Integer> staticPowers = aiAgent.getMobBase().getStaticPowers();
+
+        if (staticPowers != null && !staticPowers.isEmpty()) {
+
+            int chance = ThreadLocalRandom.current().nextInt(100);
+
+            if (chance <= MBServerStatics.AI_POWER_DIVISOR) {
+
+                ArrayList<Integer> powerList = new ArrayList<>();
+
+                for (Integer key : staticPowers.keySet()) {
+                    powerList.add(key);
+                }
+
+                int randomPower = ThreadLocalRandom.current().nextInt(powerList.size());
+                int powerToken = powerList.get(randomPower);
+
+                PowersBase pb = PowersManager.getPowerByToken(powerToken);
+
+                if (pb != null)
+                    PowersManager.useMobPower(aiAgent, aggroTarget, pb, staticPowers.get(powerToken));
+
+                return;
+            }
+        }
+
+        if (!MovementUtilities.inRangeOfBindLocation(aiAgent)) {
+            aiAgent.setCombatTarget(null);
+            aiAgent.setAggroTargetID(0);
+            aiAgent.setWalkingHome(false);
+            aiAgent.setState(STATE.Home);
+            return;
+        }
+
+        if (!MovementUtilities.inRangeDropAggro(aiAgent, aggroTarget)) {
+            aiAgent.setAggroTargetID(0);
+            aiAgent.setCombatTarget(null);
+            MovementUtilities.moveToLocation(aiAgent, aiAgent.getTrueBindLoc(), 0);
+            aiAgent.setState(STATE.Awake);
+            return;
+        }
+
+
+        if (CombatUtilities.inRangeToAttack(aiAgent, aggroTarget)) {
+
+            if (aiAgent.getCombatTarget() == null)
+                aiAgent.setCombatTarget(aggroTarget);
+
+            if (!CombatUtilities.RunAIRandom())
+                return;
+
+            //not time to attack yet.
+            if (System.currentTimeMillis() < aiAgent.getLastAttackTime())
+                return;
+
+            if (aiAgent.getRange() >= 30 && aiAgent.isMoving())
+                return;
+
+            //no weapons, defualt mob attack speed 3 seconds.
+            ItemBase mainHand = aiAgent.getWeaponItemBase(true);
+            ItemBase offHand = aiAgent.getWeaponItemBase(false);
+
+            if (mainHand == null && offHand == null) {
+                CombatUtilities.combatCycle(aiAgent, aggroTarget, true, null);
+                aiAgent.setLastAttackTime(System.currentTimeMillis() + 3000);
+            } else
+                //TODO set offhand attack time.
+                if (aiAgent.getWeaponItemBase(true) != null) {
+
+                    int attackDelay = 3000;
+
+                    CombatUtilities.combatCycle(aiAgent, aggroTarget, true, aiAgent.getWeaponItemBase(true));
+                    aiAgent.setLastAttackTime(System.currentTimeMillis() + attackDelay);
+                } else if (aiAgent.getWeaponItemBase(false) != null) {
+
+                    int attackDelay = 3000;
+
+                    CombatUtilities.combatCycle(aiAgent, aggroTarget, false, aiAgent.getWeaponItemBase(false));
+                    aiAgent.setLastAttackTime(System.currentTimeMillis() + attackDelay);
+                }
+            return;
+        }
+
+        //use this so mobs dont continue to try to move if they are underneath a flying target. only use 2D range check.
+        if (CombatUtilities.inRangeToAttack2D(aiAgent, aggroTarget))
+            return;
+
+        if (!MovementUtilities.canMove(aiAgent))
+            return;
+
+        if (!MovementUtilities.updateMovementToCharacter(aiAgent, aggroTarget))
+            return;
+
+        aiAgent.destination = MovementUtilities.GetDestinationToCharacter(aiAgent, aggroTarget);
+        MovementUtilities.moveToLocation(aiAgent, aiAgent.destination, aiAgent.getRange());
+    }
+
+    private static void home(Mob aiAgent, boolean walk) {
+
+        //recall home.
+        MovementManager.translocate(aiAgent, aiAgent.getBindLoc(), null);
+        aiAgent.setAggroTargetID(0);
+        aiAgent.setCombatTarget(null);
+        aiAgent.setState(STATE.Awake);
+    }
+
+    private static void recall(Mob aiAgent) {
+        //recall home.
+        PowersBase recall = PowersManager.getPowerByToken(-1994153779);
+        PowersManager.useMobPower(aiAgent, aiAgent, recall, 40);
+        aiAgent.setState(MobileFSM.STATE.Recalling);
+    }
+
+    private static void recalling(Mob aiAgent) {
+        //recall home.
+        if (aiAgent.getLoc() == aiAgent.getBindLoc())
+            aiAgent.setState(STATE.Awake);
+
+        if (aiAgent.getLoc().distanceSquared2D(aiAgent.getBindLoc()) > sqr(2000)) {
+
+            aiAgent.setWalkingHome(false);
+            aiAgent.setState(STATE.Home);
+        }
+    }
+
+    private static void patrol(Mob aiAgent) {
+
+        MobBase mobbase = aiAgent.getMobBase();
+
+        if (mobbase != null && (Enum.MobFlagType.SENTINEL.elementOf(mobbase.getFlags()) || !Enum.MobFlagType.CANROAM.elementOf(mobbase.getFlags()))) {
+            aiAgent.setState(STATE.Awake);
+            return;
+        }
+
+        if (MovementUtilities.canMove(aiAgent) && !aiAgent.isMoving()) {
+
+            float patrolRadius = aiAgent.getSpawnRadius();
+
+            if (patrolRadius > 256)
+                patrolRadius = 256;
+
+            if (patrolRadius < 60)
+                patrolRadius = 60;
+
+            MovementUtilities.aiMove(aiAgent, Vector3fImmutable.getRandomPointInCircle(aiAgent.getBindLoc(), patrolRadius), true);
+        }
+        aiAgent.setState(STATE.Awake);
+    }
+
+    public static void goHome(Mob aiAgent, boolean walk) {
+
+        if (aiAgent.getState() != STATE.Dead) {
+            aiAgent.setWalkingHome(walk);
+            aiAgent.setAggroTargetID(0);
+            aiAgent.setState(STATE.Home);
+        }
+    }
+
+    private static void dead(Mob aiAgent) {
+        //Despawn Timer with Loot currently in inventory.
+        if (aiAgent.getCharItemManager().getInventoryCount() > 0) {
+            if (System.currentTimeMillis() > aiAgent.getDeathTime() + MBServerStatics.DESPAWN_TIMER_WITH_LOOT) {
+                aiAgent.despawn();
+                //update time of death after mob despawns so respawn time happens after mob despawns.
+                aiAgent.setDeathTime(System.currentTimeMillis());
+                aiAgent.setState(STATE.Respawn);
+            }
+
+            //No items in inventory.
+        } else {
+            //Mob's Loot has been looted.
+            if (aiAgent.isHasLoot()) {
+                if (System.currentTimeMillis() > aiAgent.getDeathTime() + MBServerStatics.DESPAWN_TIMER_ONCE_LOOTED) {
+                    aiAgent.despawn();
+                    //update time of death after mob despawns so respawn time happens after mob despawns.
+                    aiAgent.setDeathTime(System.currentTimeMillis());
+                    aiAgent.setState(STATE.Respawn);
+                }
+                //Mob never had Loot.
+            } else {
+                if (System.currentTimeMillis() > aiAgent.getDeathTime() + MBServerStatics.DESPAWN_TIMER) {
+                    aiAgent.despawn();
+                    //update time of death after mob despawns so respawn time happens after mob despawns.
+                    aiAgent.setDeathTime(System.currentTimeMillis());
+                    aiAgent.setState(STATE.Respawn);
+                }
+            }
+        }
+    }
+
+    private static void guardAwake(Mob aiAgent) {
+
+        if (!aiAgent.isAlive()) {
+            aiAgent.setState(STATE.Dead);
+            return;
+        }
+
+        if (aiAgent.getLoc().distanceSquared2D(aiAgent.getBindLoc()) > sqr(2000)) {
+
+            aiAgent.setWalkingHome(false);
+            aiAgent.setState(STATE.Home);
+            return;
+        }
+
+        //Don't attempt to aggro if No aggro is on and aiAgent is not home yet.
+
+        //Mob stopped Moving let's turn aggro back on.
+        if (aiAgent.isNoAggro())
+            aiAgent.setNoAggro(false);
+
+        // do nothing if no players are around.
+        if (aiAgent.getPlayerAgroMap().isEmpty())
+            return;
+
+        //Get the Map for Players that loaded this mob.
+
+        ConcurrentHashMap<Integer, Boolean> loadedPlayers = aiAgent.getPlayerAgroMap();
+
+        //no players currently have this mob loaded. return to IDLE.
+        //aiAgent finished moving home, set aggro on.
+
+        for (Entry playerEntry : loadedPlayers.entrySet()) {
+
+            int playerID = (int) playerEntry.getKey();
+
+            PlayerCharacter loadedPlayer = PlayerCharacter.getFromCache(playerID);
+
+            //Player is null, let's remove them from the list.
+            if (loadedPlayer == null) {
+                //     Logger.error("MobileFSM", "Player with UID " + playerID + " returned null in mob.getPlayerAgroMap()");
+                loadedPlayers.remove(playerID);
+                continue;
+            }
+
+            //Player is Dead, Mob no longer needs to attempt to aggro. Remove them from aggro map.
+            if (!loadedPlayer.isAlive()) {
+                loadedPlayers.remove(playerID);
+                continue;
+            }
+
+            //Can't see target, skip aggro.
+            if (!aiAgent.canSee(loadedPlayer)) {
+                continue;
+            }
+
+            //Guard aggro check
+
+            boolean aggro = false;
+            Zone cityZone = aiAgent.getParentZone();
+
+            if (cityZone != null) {
+                City city = City.GetCityFromCache(cityZone.getPlayerCityUUID());
+                if (city != null) {
+
+                    Building tol = city.getTOL();
+
+                    if (tol != null) {
+                        if (tol.reverseKOS) {
+
+                            aggro = true;
+
+                            for (Condemned condemned : tol.getCondemned().values()) {
+                                switch (condemned.getFriendType()) {
+                                    case Condemned.NATION:
+                                        if (loadedPlayer.getGuild() != null && loadedPlayer.getGuild().getNation() != null)
+                                            if (loadedPlayer.getGuild().getNation().getObjectUUID() == condemned.getGuildUID())
+                                                if (condemned.isActive())
+                                                    aggro = false;
+                                        break;
+                                    case Condemned.GUILD:
+                                        if (loadedPlayer.getGuild() != null)
+                                            if (loadedPlayer.getGuild().getObjectUUID() == condemned.getGuildUID())
+                                                if (condemned.isActive())
+                                                    aggro = false;
+                                        break;
+                                    case Condemned.INDIVIDUAL:
+                                        if (loadedPlayer.getObjectUUID() == condemned.getPlayerUID())
+                                            if (condemned.isActive())
+                                                aggro = false;
+                                        break;
+                                }
+                            }
+                        } else {
+                            aggro = false;
+
+                            for (Condemned condemned : tol.getCondemned().values()) {
+                                switch (condemned.getFriendType()) {
+                                    case Condemned.NATION:
+                                        if (loadedPlayer.getGuild() != null && loadedPlayer.getGuild().getNation() != null)
+                                            if (loadedPlayer.getGuild().getNation().getObjectUUID() == condemned.getGuildUID())
+                                                if (condemned.isActive())
+                                                    aggro = true;
+                                        break;
+                                    case Condemned.GUILD:
+                                        if (loadedPlayer.getGuild() != null)
+                                            if (loadedPlayer.getGuild().getObjectUUID() == condemned.getGuildUID())
+                                                if (condemned.isActive())
+                                                    aggro = true;
+                                        break;
+                                    case Condemned.INDIVIDUAL:
+                                        if (loadedPlayer.getObjectUUID() == condemned.getPlayerUID())
+                                            if (condemned.isActive())
+                                                aggro = true;
+                                        break;
+                                }
+                            }
+                        }
+                    }
+                }
+
+                if (loadedPlayer.getGuild() != null && loadedPlayer.getGuild().getNation() != null && city.getGuild() != null)
+                    if (Guild.sameGuild(loadedPlayer.getGuild().getNation(), city.getGuild().getNation()))
+                        aggro = false;
+
+            }
+
+            //lets make sure we dont aggro players in the nation.
+
+            if (aggro) {
+                if (CombatUtilities.inRangeToAttack(aiAgent, loadedPlayer)) {
+                    aiAgent.setAggroTargetID(playerID);
+                    aiAgent.setState(STATE.Aggro);
+                    return;
+                }
+
+                if (MovementUtilities.inRangeToAggro(aiAgent, loadedPlayer)) {
+                    aiAgent.setAggroTargetID(playerID);
+                    aiAgent.setState(STATE.Aggro);
+                    return;
+                }
+            }
+        }
+
+        //attempt to patrol even if aiAgent isn't aggresive;
+        if (aiAgent.isMoving() == false)
+            aiAgent.setState(STATE.Patrol);
+    }
+
+    private static void guardAggro(Mob aiAgent, int targetID) {
+
+        if (!aiAgent.isAlive()) {
+            aiAgent.setState(STATE.Dead);
+            return;
+        }
+
+        if (!aiAgent.isCombat()) {
+            aiAgent.setCombat(true);
+            UpdateStateMsg rwss = new UpdateStateMsg();
+            rwss.setPlayer(aiAgent);
+            DispatchMessage.sendToAllInRange(aiAgent, rwss);
+        }
+
+        //a player got in aggro range. Move to player until in range of attack.
+        PlayerCharacter aggroTarget = PlayerCharacter.getFromCache(targetID);
+
+        if (aggroTarget == null) {
+            aiAgent.setState(STATE.Patrol);
+            return;
+        }
+
+        if (!aiAgent.canSee(aggroTarget)) {
+            aiAgent.setCombatTarget(null);
+            targetID = 0;
+            aiAgent.setState(STATE.Patrol);
+            return;
+        }
+
+        if (!aggroTarget.isActive()) {
+            aiAgent.setCombatTarget(null);
+            targetID = 0;
+            aiAgent.setState(STATE.Patrol);
+            return;
+        }
+
+        if (System.currentTimeMillis() > aiAgent.getTimeStamp("CallForHelp")) {
+            CombatUtilities.CallForHelp(aiAgent);
+            aiAgent.getTimestamps().put("CallForHelp", System.currentTimeMillis() + 60000);
+        }
+
+
+        if (CombatUtilities.inRangeToAttack(aiAgent, aggroTarget)) {
+            aiAgent.setCombatTarget(aggroTarget);
+            aiAgent.setState(STATE.Attack);
+            guardAttack(aiAgent);
+            return;
+        }
+
+        //use this so mobs dont continue to try to move if they are underneath a flying target. only use 2D range check.
+        if (CombatUtilities.inRangeToAttack2D(aiAgent, aggroTarget))
+            return;
+
+
+        if (!MovementUtilities.canMove(aiAgent))
+            return;
+
+        if (!MovementUtilities.inRangeDropAggro(aiAgent, aggroTarget)) {
+            aiAgent.setAggroTargetID(0);
+            aiAgent.setCombatTarget(null);
+            MovementUtilities.moveToLocation(aiAgent, aiAgent.getTrueBindLoc(), 0);
+            aiAgent.setState(STATE.Awake);
+            return;
+        }
+
+        if (!MovementUtilities.inRangeOfBindLocation(aiAgent)) {
+            aiAgent.setCombatTarget(null);
+            aiAgent.setAggroTargetID(0);
+            aiAgent.setWalkingHome(false);
+            aiAgent.setState(STATE.Home);
+            return;
+        }
+
+        if (!MovementUtilities.updateMovementToCharacter(aiAgent, aggroTarget))
+            return;
+
+        //Outside of attack Range, Move to players predicted loc.
+
+        if (aiAgent.getLoc().distanceSquared2D(aggroTarget.getLoc()) < aiAgent.getRange() * aiAgent.getRange())
+            return;
+        aiAgent.destination = MovementUtilities.GetDestinationToCharacter(aiAgent, aggroTarget);
+        MovementUtilities.moveToLocation(aiAgent, aiAgent.destination, aiAgent.getRange());
+
+    }
+
+    private static void guardPatrol(Mob aiAgent) {
+
+        if (aiAgent.getPlayerAgroMap().isEmpty()) {
+            aiAgent.setState(STATE.Awake);
+            return;
+        }
+
+        if (aiAgent.isCombat() && aiAgent.getCombatTarget() == null) {
+            aiAgent.setCombat(false);
+            UpdateStateMsg rwss = new UpdateStateMsg();
+            rwss.setPlayer(aiAgent);
+            DispatchMessage.sendToAllInRange(aiAgent, rwss);
+        }
+
+        if (aiAgent.getNpcOwner() == null) {
+
+            if (!aiAgent.isWalk() || (aiAgent.isCombat() && aiAgent.getCombatTarget() == null)) {
+                aiAgent.setWalkMode(true);
+                aiAgent.setCombat(false);
+                UpdateStateMsg rwss = new UpdateStateMsg();
+                rwss.setPlayer(aiAgent);
+                DispatchMessage.sendToAllInRange(aiAgent, rwss);
+            }
+
+            if (aiAgent.isMoving()) {
+                aiAgent.setState(STATE.Awake);
+                return;
+            }
+
+            Building barrack = aiAgent.getBuilding();
+
+            if (barrack == null) {
+                aiAgent.setState(STATE.Awake);
+                return;
+            }
+
+            int patrolRandom = ThreadLocalRandom.current().nextInt(1000);
+
+            if (patrolRandom <= 10) {
+                int buildingHitBox = (int) CombatManager.calcHitBox(barrack);
+                if (MovementUtilities.canMove(aiAgent)) {
+                    MovementUtilities.aiMove(aiAgent, MovementUtilities.randomPatrolLocation(aiAgent, aiAgent.getBindLoc(), buildingHitBox * 2), true);
+                }
+            }
+
+            aiAgent.setState(STATE.Awake);
+            return;
+
+        }
+
+        if (!aiAgent.isWalk() || (aiAgent.isCombat() && aiAgent.getCombatTarget() == null)) {
+            aiAgent.setWalkMode(true);
+            aiAgent.setCombat(false);
+            UpdateStateMsg rwss = new UpdateStateMsg();
+            rwss.setPlayer(aiAgent);
+            DispatchMessage.sendToAllInRange(aiAgent, rwss);
+
+        }
+
+        Building barrack = ((Mob) aiAgent.getNpcOwner()).getBuilding();
+
+        if (barrack == null) {
+            aiAgent.setState(STATE.Awake);
+            return;
+        }
+
+        if (barrack.getPatrolPoints() == null) {
+            aiAgent.setState(STATE.Awake);
+            return;
+        }
+
+        if (barrack.getPatrolPoints().isEmpty()) {
+            aiAgent.setState(STATE.Awake);
+            return;
+        }
+
+        if (aiAgent.isMoving()) {
+            aiAgent.setState(STATE.Awake);
+            return;
+        }
+
+        int patrolRandom = ThreadLocalRandom.current().nextInt(1000);
+
+        if (patrolRandom <= 10) {
+            if (aiAgent.getPatrolPointIndex() < barrack.getPatrolPoints().size()) {
+                Vector3fImmutable patrolLoc = barrack.getPatrolPoints().get(aiAgent.getPatrolPointIndex());
+                aiAgent.setPatrolPointIndex(aiAgent.getPatrolPointIndex() + 1);
+                if (aiAgent.getPatrolPointIndex() == barrack.getPatrolPoints().size())
+                    aiAgent.setPatrolPointIndex(0);
+
+                if (patrolLoc != null) {
+                    if (MovementUtilities.canMove(aiAgent)) {
+                        MovementUtilities.aiMove(aiAgent, patrolLoc, true);
+                        aiAgent.setState(STATE.Awake);
+                    }
+                }
+            }
+        }
+        aiAgent.setState(STATE.Awake);
+    }
+
+    private static void guardAttack(Mob aiAgent) {
+
+        if (!aiAgent.isAlive()) {
+            aiAgent.setState(STATE.Dead);
+            return;
+        }
+
+        AbstractGameObject target = aiAgent.getCombatTarget();
+
+        if (target == null) {
+            aiAgent.setState(STATE.Patrol);
+            return;
+        }
+
+        switch (target.getObjectType()) {
+            case PlayerCharacter:
+
+                PlayerCharacter player = (PlayerCharacter) target;
+
+                if (!player.isActive()) {
+                    aiAgent.setCombatTarget(null);
+                    aiAgent.setState(STATE.Patrol);
+                    return;
+                }
+
+                if (aiAgent.isNecroPet() && player.inSafeZone()) {
+                    aiAgent.setCombatTarget(null);
+                    aiAgent.setState(STATE.Idle);
+                    return;
+                }
+
+                handlePlayerAttackForMob(aiAgent, player);
+                break;
+            case Building:
+                Logger.info("PLAYER GUARD ATTEMPTING TO ATTACK BUILDING IN " + aiAgent.getParentZone().getName());
+                aiAgent.setState(STATE.Awake);
+                break;
+            case Mob:
+                Mob mob = (Mob) target;
+                handleMobAttackForMob(aiAgent, mob);
+        }
+    }
+
+    private static void guardHome(Mob aiAgent, boolean walk) {
+
+        //recall home.
+        PowersBase recall = PowersManager.getPowerByToken(-1994153779);
+        PowersManager.useMobPower(aiAgent, aiAgent, recall, 40);
+
+        aiAgent.setAggroTargetID(0);
+        aiAgent.setCombatTarget(null);
+        aiAgent.setState(STATE.Awake);
+    }
+
+    private static void guardRespawn(Mob aiAgent) {
+
+        if (!aiAgent.canRespawn())
+            return;
+
+        if (aiAgent.isPlayerGuard() && aiAgent.getNpcOwner() != null && !aiAgent.getNpcOwner().isAlive())
+            return;
+
+        long spawnTime = aiAgent.getSpawnTime();
+
+        if (System.currentTimeMillis() > aiAgent.getDeathTime() + spawnTime) {
+            aiAgent.respawn();
+            aiAgent.setState(STATE.Idle);
+        }
+    }
+
+    private static void respawn(Mob aiAgent) {
+
+        if (!aiAgent.canRespawn())
+            return;
+
+        long spawnTime = aiAgent.getSpawnTime();
+
+        if (aiAgent.isPlayerGuard() && aiAgent.getNpcOwner() != null && !aiAgent.getNpcOwner().isAlive())
+            return;
+
+        if (System.currentTimeMillis() > aiAgent.getDeathTime() + spawnTime) {
+            aiAgent.respawn();
+            aiAgent.setState(STATE.Idle);
+        }
+    }
+
+    private static void retaliate(Mob aiAgent) {
+
+        if (aiAgent.getCombatTarget() == null)
+            aiAgent.setState(STATE.Awake);
+
+        //out of range to attack move
+        if (!MovementUtilities.canMove(aiAgent)) {
+            aiAgent.setState(STATE.Attack);
+            return;
+        }
+
+        aiAgent.setState(STATE.Attack);
+
+        //lets make mobs ai less twitchy, Don't call another movement until mob reaches it's destination.
+        if (aiAgent.isMoving())
+            return;
+
+        MovementUtilities.moveToLocation(aiAgent, aiAgent.getCombatTarget().getLoc(), aiAgent.getRange());
+    }
+
+    private static void moveToWorldObjectRegion(Mob mob, AbstractWorldObject regionObject) {
+
+        if (regionObject.getRegion() == null)
+            return;
+
+        MovementManager.translocate(mob, regionObject.getLoc(), null);
+    }
+}
diff --git a/src/engine/ai/MobileFSMManager.java b/src/engine/ai/MobileFSMManager.java
new file mode 100644
index 00000000..a1f8ce20
--- /dev/null
+++ b/src/engine/ai/MobileFSMManager.java
@@ -0,0 +1,104 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.ai;
+
+import engine.gameManager.ZoneManager;
+import engine.objects.Mob;
+import engine.objects.Zone;
+import engine.server.MBServerStatics;
+import engine.util.ThreadUtils;
+import org.pmw.tinylog.Logger;
+
+import java.util.HashSet;
+
+
+public class MobileFSMManager {
+
+	private static final MobileFSMManager INSTANCE = new MobileFSMManager();
+
+	private volatile boolean alive;
+	private long timeOfKill = -1;
+
+	private MobileFSMManager() {
+
+		Runnable worker = new Runnable() {
+			@Override
+			public void run() {
+				execution();
+			}
+		};
+
+		alive = true;
+
+		Thread t = new Thread(worker, "MobileFSMManager");
+		t.start();
+	}
+
+	public static MobileFSMManager getInstance() {
+		return INSTANCE;
+	}
+
+	/**
+	 * Stops the MobileFSMManager
+	 */
+	public void shutdown() {
+		if (alive) {
+			alive = false;
+			timeOfKill = System.currentTimeMillis();
+		}
+	}
+
+
+	public long getTimeOfKill() {
+		return this.timeOfKill;
+	}
+
+	public boolean isAlive() {
+		return this.alive;
+	}
+
+
+	private void execution() {
+
+		//Load zone threshold once.
+
+		long mobPulse = System.currentTimeMillis() + MBServerStatics.AI_PULSE_MOB_THRESHOLD;
+
+		while (alive) {
+
+			ThreadUtils.sleep(1);
+
+			if (System.currentTimeMillis() > mobPulse) {
+				
+				HashSet<Integer> auditMobs = new HashSet<Integer>();
+
+				for (Zone zone : ZoneManager.getAllZones()) {
+
+					for (Mob mob : zone.zoneMobSet) {
+						
+						if (auditMobs.contains(mob.getObjectUUID()))
+							continue;
+						auditMobs.add(mob.getObjectUUID());
+						try {
+							if (mob != null)
+								MobileFSM.run(mob);
+						} catch (Exception e) {
+							Logger.error(e);
+							e.printStackTrace();
+						}
+					}
+				}
+
+				mobPulse = System.currentTimeMillis() + MBServerStatics.AI_PULSE_MOB_THRESHOLD;
+			}
+		}
+	}
+
+}
diff --git a/src/engine/ai/utilities/CombatUtilities.java b/src/engine/ai/utilities/CombatUtilities.java
new file mode 100644
index 00000000..60b538b9
--- /dev/null
+++ b/src/engine/ai/utilities/CombatUtilities.java
@@ -0,0 +1,413 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+
+package engine.ai.utilities;
+
+import engine.Enum;
+import engine.Enum.*;
+import engine.ai.MobileFSM.STATE;
+import engine.gameManager.ChatManager;
+import engine.gameManager.CombatManager;
+import engine.math.Vector3fImmutable;
+import engine.net.DispatchMessage;
+import engine.net.client.msg.TargetedActionMsg;
+import engine.objects.*;
+import engine.server.MBServerStatics;
+import org.pmw.tinylog.Logger;
+
+import java.util.Set;
+import java.util.concurrent.ThreadLocalRandom;
+
+import static engine.math.FastMath.sqr;
+
+public class CombatUtilities {
+
+	public static boolean inRangeToAttack(Mob agent,AbstractWorldObject target){
+
+		if (Float.isNaN(agent.getLoc().x))
+			return false;
+
+		try{
+			Vector3fImmutable sl = agent.getLoc();
+			Vector3fImmutable tl = target.getLoc();
+			
+			//add Hitbox's to range.
+			float range = agent.getRange();
+			range += CombatManager.calcHitBox(target) + CombatManager.calcHitBox(agent);
+			//if (target instanceof AbstractCharacter)
+			//				if (((AbstractCharacter)target).isMoving())
+			//					range+= 5;
+
+			return !(sl.distanceSquared(tl) > sqr(range));
+		}catch(Exception e){
+			Logger.error( e.toString());
+			return false;
+		}
+
+	}
+	
+	public static boolean inRangeToAttack2D(Mob agent,AbstractWorldObject target){
+
+		if (Float.isNaN(agent.getLoc().x))
+			return false;
+
+		try{
+			Vector3fImmutable sl = agent.getLoc();
+			Vector3fImmutable tl = target.getLoc();
+			
+			//add Hitbox's to range.
+			float range = agent.getRange();
+			range += CombatManager.calcHitBox(target) + CombatManager.calcHitBox(agent);
+			//if (target instanceof AbstractCharacter)
+			//				if (((AbstractCharacter)target).isMoving())
+			//					range+= 5;
+
+			return !(sl.distanceSquared2D(tl) > sqr(range));
+		}catch(Exception e){
+			Logger.error( e.toString());
+			return false;
+		}
+
+	}
+
+	public static void swingIsBlock(Mob agent,AbstractWorldObject target, int animation) {
+
+		if (!target.isAlive())
+			return;
+
+		TargetedActionMsg msg = new TargetedActionMsg(agent,animation, target, MBServerStatics.COMBAT_SEND_BLOCK);
+
+		if (target.getObjectType() == GameObjectType.PlayerCharacter)
+			DispatchMessage.dispatchMsgToInterestArea(target, msg, DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, true,false);
+		else
+			DispatchMessage.sendToAllInRange(agent,msg);
+
+	}
+
+	public static void swingIsParry(Mob agent,AbstractWorldObject target, int animation) {
+
+		if (!target.isAlive())
+			return;
+
+		TargetedActionMsg msg = new TargetedActionMsg(agent,animation, target,  MBServerStatics.COMBAT_SEND_PARRY);
+
+		if (target.getObjectType() == GameObjectType.PlayerCharacter)
+			DispatchMessage.dispatchMsgToInterestArea(target, msg, DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, true,false);
+		else
+			DispatchMessage.sendToAllInRange(agent,msg);
+
+	}
+
+	public static void swingIsDodge(Mob agent,AbstractWorldObject target, int animation) {
+
+		if (!target.isAlive())
+			return;
+
+		TargetedActionMsg msg = new TargetedActionMsg(agent,animation, target, MBServerStatics.COMBAT_SEND_DODGE);
+
+		if (target.getObjectType() == GameObjectType.PlayerCharacter)
+			DispatchMessage.dispatchMsgToInterestArea(target, msg, DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, true,false);
+		else
+			DispatchMessage.sendToAllInRange(agent,msg);
+	}
+
+	public static void swingIsDamage(Mob agent,AbstractWorldObject target, float damage, int animation){
+		float trueDamage = 0;
+
+		if (!target.isAlive())
+			return;
+
+		if (AbstractWorldObject.IsAbstractCharacter(target))
+			trueDamage = ((AbstractCharacter) target).modifyHealth(-damage, agent, false);
+		else if (target.getObjectType() == GameObjectType.Building)
+			trueDamage = ((Building) target).modifyHealth(-damage, agent);
+
+		//Don't send 0 damage kay thanx.
+
+		if (trueDamage == 0)
+			return;
+
+		TargetedActionMsg msg = new TargetedActionMsg(agent,target, damage, animation);
+
+		if (target.getObjectType() == GameObjectType.PlayerCharacter)
+			DispatchMessage.dispatchMsgToInterestArea(target, msg, DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, true,false);
+		else
+			DispatchMessage.sendToAllInRange(agent,msg);
+
+		//check damage shields
+		if(AbstractWorldObject.IsAbstractCharacter(target) && target.isAlive() && target.getObjectType() != GameObjectType.Mob)
+			CombatManager.handleDamageShields(agent,(AbstractCharacter)target, damage);
+	}
+
+	public static boolean canSwing(Mob agent) {
+		return (agent.isAlive() && !agent.getBonuses().getBool(ModType.Stunned, SourceType.None));
+	}
+
+	public static void swingIsMiss(Mob agent,AbstractWorldObject target, int animation) {
+
+		TargetedActionMsg msg = new TargetedActionMsg(agent,target, 0f, animation);
+
+		if (target.getObjectType() == GameObjectType.PlayerCharacter)
+			DispatchMessage.dispatchMsgToInterestArea(target, msg, DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, true,false);
+		else
+			DispatchMessage.sendToAllInRange(agent,msg);
+
+	}
+
+	public static boolean triggerDefense(Mob agent, AbstractWorldObject target) {
+		int defenseScore = 0;
+		int attackScore = agent.getAtrHandOne();
+		switch (target.getObjectType()) {
+		case PlayerCharacter:
+			defenseScore = ((AbstractCharacter) target).getDefenseRating();
+			break;
+		case Mob:
+
+			Mob mob = (Mob)target;
+			if (mob.isSiege())
+				defenseScore = attackScore;
+			break;
+		case Building:
+			return false;
+		}
+
+
+
+		int hitChance;
+		if (attackScore > defenseScore || defenseScore == 0)
+			hitChance = 94;
+		else if (attackScore == defenseScore && target.getObjectType() == GameObjectType.Mob)
+			hitChance = 10;
+		else {
+			float dif = attackScore / defenseScore;
+			if (dif <= 0.8f)
+				hitChance = 4;
+			else
+				hitChance = ((int)(450 * (dif - 0.8f)) + 4);
+			if (target.getObjectType() == GameObjectType.Building)
+				hitChance = 100;
+		}
+		return ThreadLocalRandom.current().nextInt(100) > hitChance;
+	}
+
+	public static boolean triggerBlock(Mob agent,AbstractWorldObject ac) {
+		return triggerPassive(agent,ac, "Block");
+	}
+
+	public static boolean triggerParry(Mob agent,AbstractWorldObject ac) {
+		return triggerPassive(agent,ac, "Parry");
+	}
+
+	public static boolean triggerDodge(Mob agent,AbstractWorldObject ac) {
+		return triggerPassive(agent,ac, "Dodge");
+	}
+
+	public static boolean triggerPassive(Mob agent,AbstractWorldObject ac, String type) {
+		float chance = 0;
+		if (AbstractWorldObject.IsAbstractCharacter(ac))
+			chance = ((AbstractCharacter)ac).getPassiveChance(type, agent.getLevel(), true);
+
+		if (chance > 75f)
+			chance = 75f;
+		if (agent.isSiege() && AbstractWorldObject.IsAbstractCharacter(ac))
+			chance = 100;
+
+		return ThreadLocalRandom.current().nextInt(100) < chance;
+	}
+
+
+	public static void combatCycle(Mob agent,AbstractWorldObject target, boolean mainHand, ItemBase wb) {
+
+		if (!agent.isAlive() || !target.isAlive()) return;
+
+		if (target.getObjectType() == GameObjectType.PlayerCharacter)
+			if (!((PlayerCharacter)target).isActive())
+				return;
+
+		int anim = 75;
+		float speed = 30f;
+		if (mainHand)
+			speed = agent.getSpeedHandOne();
+		else
+			speed = agent.getSpeedHandTwo();
+
+		DamageType dt = DamageType.Crush;
+		if (agent.isSiege())
+			dt = DamageType.Siege;
+		if (wb != null) {
+			anim = CombatManager.getSwingAnimation(wb, null,mainHand);
+			dt = wb.getDamageType();
+		} else if (!mainHand)
+			return;
+		Resists res = null;
+		PlayerBonuses bonus = null;
+		switch(target.getObjectType()){
+		case Building:
+			res = ((Building)target).getResists();
+			break;
+		case PlayerCharacter:
+			res = ((PlayerCharacter)target).getResists();
+			bonus = ((PlayerCharacter)target).getBonuses();
+			break;
+		case Mob:
+			Mob mob = (Mob)target;
+			res = mob.getResists();
+			bonus = ((Mob)target).getBonuses();
+			break;
+		}
+
+		//must not be immune to all or immune to attack
+
+		if (bonus != null && !bonus.getBool(ModType.NoMod, SourceType.ImmuneToAttack))
+			if (res != null &&(res.immuneToAll() || res.immuneToAttacks() || res.immuneTo(dt)))
+				return;
+
+		int passiveAnim =  CombatManager.getSwingAnimation(wb, null,mainHand);
+		if(canSwing(agent)) {
+			if(triggerDefense(agent,target))
+				swingIsMiss(agent,target, passiveAnim);
+			else if(triggerDodge(agent,target))
+				swingIsDodge(agent,target, passiveAnim);
+			else if(triggerParry(agent,target))
+				swingIsParry(agent,target, passiveAnim);
+			else if(triggerBlock(agent,target))
+				swingIsBlock(agent,target, passiveAnim);
+			else
+				swingIsDamage(agent,target, determineDamage(agent,target, mainHand, speed, dt), anim);
+
+			if (agent.getWeaponPower() != null)
+				agent.getWeaponPower().attack(target, MBServerStatics.ONE_MINUTE);
+		}
+		
+		if (target.getObjectType().equals(GameObjectType.PlayerCharacter)){
+			PlayerCharacter player = (PlayerCharacter)target;
+			if (player.getDebug(64)){
+				ChatManager.chatSayInfo(player, "Debug Combat: Mob UUID " + agent.getObjectUUID() + " || Building ID  = " + agent.getBuildingID() + " || Floor = " + agent.getInFloorID() + " || Level = " + agent.getInBuilding() );//combat debug
+			}
+		}
+
+		//SIEGE MONSTERS DO NOT ATTACK GUARDSs
+		if (target.getObjectType() == GameObjectType.Mob)
+			if (((Mob)target).isSiege())
+				return;
+
+		//handle the retaliate
+
+		if (AbstractWorldObject.IsAbstractCharacter(target))
+			CombatManager.handleRetaliate((AbstractCharacter)target, agent);
+
+		if (target.getObjectType() == GameObjectType.Mob){
+			Mob targetMob = (Mob)target;
+			if (targetMob.isSiege())
+				return;
+
+			if (System.currentTimeMillis() < targetMob.getTimeStamp("CallForHelp"))
+				return;
+			CallForHelp(targetMob);
+			targetMob.getTimestamps().put("CallForHelp", System.currentTimeMillis() + 60000);
+		}
+
+
+	}
+
+	public static void CallForHelp(Mob aiAgent) {
+
+		Set<Mob> zoneMobs = aiAgent.getParentZone().zoneMobSet;
+
+
+		AbstractWorldObject target = aiAgent.getCombatTarget();
+		if (target == null) {
+			return;
+		}
+
+		int count = 0;
+		for (Mob mob: zoneMobs){
+			if (!mob.isAlive())
+				continue;
+			if (mob.isSiege() || mob.isPet() || !Enum.MobFlagType.AGGRESSIVE.elementOf(mob.getMobBase().getFlags()))
+				continue;
+			if (count == 5)
+				continue;
+
+
+			if (mob.getCombatTarget() != null)
+				continue;
+
+			if (!aiAgent.isPlayerGuard() && mob.isPlayerGuard())
+				continue;
+
+			if (aiAgent.isPlayerGuard() && !mob.isPlayerGuard() )
+				continue;
+
+			if (target.getObjectType() == GameObjectType.PlayerCharacter){
+
+				if (!MovementUtilities.inRangeToAggro(mob, (PlayerCharacter)target))
+					continue;
+				count++;
+
+			}else{
+
+				if (count == 5)
+					continue;
+
+				if (aiAgent.getLoc().distanceSquared2D(target.getLoc()) > sqr(aiAgent.getAggroRange()))
+					continue;
+
+				count++;
+
+			}
+
+
+
+
+
+
+			if (mob.getState() == STATE.Awake || mob.getState() == STATE.Patrol){
+				mob.setCombatTarget(target);
+				mob.setState(STATE.Attack);
+			}
+		}
+
+	}
+
+	public static float determineDamage(Mob agent,AbstractWorldObject target, boolean mainHand, float speed, DamageType dt) {
+
+		float min = (mainHand) ? agent.getMinDamageHandOne() : agent.getMinDamageHandTwo();
+		float max = (mainHand) ? agent.getMaxDamageHandOne() : agent.getMaxDamageHandTwo();;
+
+		float range = max - min;
+		float damage = min + ((ThreadLocalRandom.current().nextFloat()*range)+(ThreadLocalRandom.current().nextFloat()*range))/2;
+
+		if (AbstractWorldObject.IsAbstractCharacter(target))
+			if (((AbstractCharacter)target).isSit())
+				damage *= 2.5f; //increase damage if sitting
+
+		if (AbstractWorldObject.IsAbstractCharacter(target))
+			return ((AbstractCharacter)target).getResists().getResistedDamage(agent,(AbstractCharacter)target, dt, damage, 0);
+
+		if (target.getObjectType() == GameObjectType.Building){
+			Building building = (Building)target;
+			Resists resists = building.getResists();
+			return damage * (1 - (resists.getResist(dt, 0) / 100));
+		}
+
+		return damage;
+
+	}
+	
+	public static boolean RunAIRandom(){
+		int random = ThreadLocalRandom.current().nextInt(4);
+		
+		if (random == 0)
+			return true;
+		
+		return false;
+	}
+}
diff --git a/src/engine/ai/utilities/MovementUtilities.java b/src/engine/ai/utilities/MovementUtilities.java
new file mode 100644
index 00000000..28a89689
--- /dev/null
+++ b/src/engine/ai/utilities/MovementUtilities.java
@@ -0,0 +1,303 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+
+package engine.ai.utilities;
+
+import engine.Enum;
+import engine.Enum.GameObjectType;
+import engine.Enum.ModType;
+import engine.Enum.SourceType;
+import engine.exception.MsgSendException;
+import engine.gameManager.MovementManager;
+import engine.math.Vector3fImmutable;
+import engine.net.client.msg.MoveToPointMsg;
+import engine.objects.*;
+import engine.server.MBServerStatics;
+import org.pmw.tinylog.Logger;
+
+import java.util.concurrent.ThreadLocalRandom;
+
+import static engine.math.FastMath.sqr;
+import static engine.math.FastMath.sqrt;
+
+public class MovementUtilities {
+
+
+	public static boolean inRangeOfBindLocation(Mob agent){
+		
+		
+		
+		if (agent.isPlayerGuard()){
+			
+			Mob guardCaptain = null;
+			if (agent.getContract() != null)
+				guardCaptain = agent;
+			else
+		guardCaptain = (Mob) agent.getNpcOwner();
+			
+			if (guardCaptain != null){
+				Building barracks = guardCaptain.getBuilding();
+				
+				if (barracks != null){
+					City city = barracks.getCity();
+					
+					if (city != null){
+						Building tol = city.getTOL();
+						
+						//Guards recall distance = 814.
+						if (tol != null){
+							if (agent.getLoc().distanceSquared2D(tol.getLoc()) > sqr(Enum.CityBoundsType.SIEGE.extents)) {
+					                return false;
+					            }
+						}
+						
+					}
+				}
+			}
+			
+			return true;
+		}
+
+		Vector3fImmutable sl = new Vector3fImmutable(agent.getLoc().getX(), 0, agent.getLoc().getZ());
+		Vector3fImmutable tl = new Vector3fImmutable(agent.getTrueBindLoc().x,0,agent.getTrueBindLoc().z);
+
+		float distanceSquaredToTarget = sl.distanceSquared2D(tl); //distance to center of target
+		float zoneRange = 250;
+
+		if (agent.getParentZone() != null){
+			if (agent.getParentZone().getBounds() != null)
+				zoneRange = agent.getParentZone().getBounds().getHalfExtents().x * 2;
+		}
+
+		if (zoneRange > 300)
+			zoneRange = 300;
+		
+		if (agent.getSpawnRadius() > zoneRange)
+			zoneRange = agent.getSpawnRadius();
+		
+
+		return distanceSquaredToTarget < sqr(MBServerStatics.AI_DROP_AGGRO_RANGE + zoneRange);
+
+	}
+
+	public static boolean inRangeToAggro(Mob agent,PlayerCharacter target){
+
+		Vector3fImmutable sl = agent.getLoc();
+		Vector3fImmutable tl =target.getLoc();
+
+		float distanceSquaredToTarget = sl.distanceSquared2D(tl) - sqr(agent.calcHitBox() + target.calcHitBox()); //distance to center of target
+		float range = MBServerStatics.AI_BASE_AGGRO_RANGE;
+
+		if (agent.isPlayerGuard())
+			range = 150;
+
+		return distanceSquaredToTarget < sqr(range);
+
+	}
+
+	public static boolean inRangeDropAggro(Mob agent,PlayerCharacter target){
+
+		Vector3fImmutable sl = agent.getLoc();
+		Vector3fImmutable tl = target.getLoc();
+
+		float distanceSquaredToTarget = sl.distanceSquared2D(tl) - sqr(agent.calcHitBox() + target.calcHitBox()); //distance to center of target
+
+		float range = agent.getRange() + 150;
+
+		if (range > 200)
+			range = 200;
+
+
+		return distanceSquaredToTarget < sqr(range);
+
+	}
+
+	public static Vector3fImmutable GetMoveLocation(Mob aiAgent, AbstractCharacter aggroTarget){
+
+		// Player isnt moving and neither is mob.  Just return
+		// the mobile's current location.  Ain't goin nowhere!
+		// *** Refactor: Check to ensure methods calling us
+		// all don't sent move messages when not moving.
+
+		if ((aggroTarget.isMoving() == false))
+			return aggroTarget.getLoc();
+
+		if (aggroTarget.getEndLoc().x != 0){
+
+			float aggroTargetDistanceSquared = aggroTarget.getLoc().distanceSquared2D(aggroTarget.getEndLoc());
+			float aiAgentDistanceSquared = aiAgent.getLoc().distanceSquared2D(aggroTarget.getEndLoc());
+
+			if (aiAgentDistanceSquared >= aggroTargetDistanceSquared)
+				return aggroTarget.getEndLoc();
+			else{
+				float distanceToMove = sqrt(aggroTargetDistanceSquared + aiAgentDistanceSquared) *.5f;
+
+				return aggroTarget.getFaceDir().scaleAdd(distanceToMove, aggroTarget.getLoc());
+
+			}
+		}
+
+		// One of us is moving so let's calculate our destination loc for this
+		// simulation frame.  We will simply project our position onto the
+		// character's movement vector and return the closest point.
+
+		return aiAgent.getLoc().ClosestPointOnLine(aggroTarget.getLoc(), aggroTarget.getEndLoc());
+	}
+
+	public static void moveToLocation(Mob agent,Vector3fImmutable newLocation, float offset){
+		try {
+			
+			//don't move farther than 30 units from player.
+			if (offset > 30)
+				offset = 30;
+			Vector3fImmutable newLoc = Vector3fImmutable.getRandomPointInCircle(newLocation, offset);
+
+
+			agent.setFaceDir(newLoc.subtract2D(agent.getLoc()).normalize());
+		
+			aiMove(agent,newLoc,false);
+		} catch (Exception e) {
+			Logger.error( e.toString());
+		}
+	}
+
+
+
+	public static boolean canMove(Mob agent) {
+		if (agent.getMobBase() != null && Enum.MobFlagType.SENTINEL.elementOf(agent.getMobBase().getFlags()))
+			return false;
+
+		return (agent.isAlive() && !agent.getBonuses().getBool(ModType.Stunned,SourceType.None) && !agent.getBonuses().getBool(ModType.CannotMove, SourceType.None));
+	}
+
+	public static Vector3fImmutable randomPatrolLocation(Mob agent,Vector3fImmutable center, float radius){
+
+		//Determing where I want to move.
+		return new Vector3fImmutable((center.x - radius) + ((ThreadLocalRandom.current().nextFloat()+.1f*2)*radius),
+				center.y,
+				(center.z - radius) + ((ThreadLocalRandom.current().nextFloat()+.1f *2)*radius));
+	}
+	public static Long estimateMovementTime(Mob agent) {
+		if(agent.getEndLoc().x == 0 && agent.getEndLoc().y == 0)
+			return 0L;
+
+		return (long) ((agent.getLoc().distance2D(agent.getEndLoc())*1000)/agent.getSpeed());
+	}
+
+	public static void aiMove(Mob agent,Vector3fImmutable vect, boolean isWalking) {
+
+		//update our walk/run state.
+		if (isWalking && !agent.isWalk()){
+			agent.setWalkMode(true);
+			MovementManager.sendRWSSMsg(agent);
+		}else if(!isWalking && agent.isWalk()){
+			agent.setWalkMode(false);
+			MovementManager.sendRWSSMsg(agent);
+		}
+
+		MoveToPointMsg msg = new MoveToPointMsg();
+
+
+//		Regions currentRegion = Mob.InsideBuildingRegion(agent);
+//
+//		if (currentRegion != null){
+//
+//
+//			if (currentRegion.isGroundLevel()){
+//				agent.setInBuilding(0);
+//				agent.setInFloorID(-1);
+//			}else{
+//				agent.setInBuilding(currentRegion.getLevel());
+//				agent.setInFloorID(currentRegion.getRoom());
+//			}
+//		}else{
+//			agent.setInBuilding(-1);
+//			agent.setInFloorID(-1);
+//			agent.setInBuildingID(0);
+//		}
+//		agent.setLastRegion(currentRegion);
+
+
+
+		Vector3fImmutable startLoc = null;
+		Vector3fImmutable endLoc = null;
+
+//		if (agent.getLastRegion() != null){
+//			Building inBuilding = Building.getBuildingFromCache(agent.getInBuildingID());
+//			if (inBuilding != null){
+//				startLoc = ZoneManager.convertWorldToLocal(inBuilding, agent.getLoc());
+//				endLoc = ZoneManager.convertWorldToLocal(inBuilding, vect);
+//			}
+//		}else{
+//			agent.setBuildingID(0);
+//			agent.setInBuildingID(0);
+//			startLoc = agent.getLoc();
+//			endLoc = vect;
+//		}
+		
+		startLoc = agent.getLoc();
+		endLoc = vect;
+
+		msg.setSourceType(GameObjectType.Mob.ordinal());
+		msg.setSourceID(agent.getObjectUUID());
+		msg.setStartCoord(startLoc);
+		msg.setEndCoord(endLoc);
+		msg.setUnknown01(-1);
+		msg.setInBuilding(-1);
+		msg.setTargetType(0);
+		msg.setTargetID(0);
+
+
+		try {
+			MovementManager.movement(msg, agent);
+		} catch (MsgSendException e) {
+			// TODO Figure out how we want to handle the msg send exception
+			e.printStackTrace();
+		}
+	}
+	
+	public static Vector3fImmutable GetDestinationToCharacter(Mob aiAgent, AbstractCharacter character){
+		
+		if (!character.isMoving())
+			return character.getLoc();
+			
+		
+		float agentDistanceEndLoc = aiAgent.getLoc().distanceSquared2D(character.getEndLoc());
+		float characterDistanceEndLoc = character.getLoc().distanceSquared2D(character.getEndLoc());
+		
+		if (agentDistanceEndLoc > characterDistanceEndLoc)
+			return character.getEndLoc();
+		
+		return character.getLoc();
+	}
+	
+	public static boolean updateMovementToCharacter(Mob aiAgent, AbstractCharacter aggroTarget){
+		
+		if (aiAgent.destination.equals(Vector3fImmutable.ZERO))
+			return true;
+		
+		if (!aiAgent.isMoving())
+			return true;
+		
+		
+		
+		
+		if (aggroTarget.isMoving()){
+		if (!aiAgent.destination.equals(aggroTarget.getEndLoc()) && !aiAgent.destination.equals(aggroTarget.getLoc()))
+			return true;
+		}else{
+			if (aiAgent.destination.equals(aggroTarget.getLoc()))
+				return false;
+		}
+		
+		return false;
+	}
+
+}
diff --git a/src/engine/ai/utilities/PowerUtilities.java b/src/engine/ai/utilities/PowerUtilities.java
new file mode 100644
index 00000000..12c80cb7
--- /dev/null
+++ b/src/engine/ai/utilities/PowerUtilities.java
@@ -0,0 +1,15 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+
+package engine.ai.utilities;
+
+public class PowerUtilities {
+
+}
diff --git a/src/engine/core/ControlledRunnable.java b/src/engine/core/ControlledRunnable.java
new file mode 100644
index 00000000..d9d8bf4a
--- /dev/null
+++ b/src/engine/core/ControlledRunnable.java
@@ -0,0 +1,174 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.core;
+
+import org.pmw.tinylog.Logger;
+
+import java.util.ArrayList;
+
+/**
+ * 
+ */
+public abstract class ControlledRunnable implements Runnable {
+	protected boolean runCmd = false;
+	protected boolean runStatus = false;
+	private Thread thisThread;
+	private final String threadName;
+
+	public ControlledRunnable(String threadName) {
+		super();
+		this.threadName = threadName;
+		ControlledRunnable.runnables.add(this);
+	}
+
+	/*
+	 * Main loop
+	 */
+
+	/**
+	 * This is the method called when ControlledRunnable.thisThread.start() is
+	 * called.
+	 */
+	@Override
+	public void run() {
+		if (this._preRun() == false) {
+			return;
+		}
+
+		this.runStatus = true;
+
+		if (this._Run() == false) {
+			return;
+		}
+
+		if (this._postRun() == false) {
+			return;
+		}
+
+		this.runStatus = false;
+	}
+
+	/**
+	 * _preRun() is called prior to the call to _Run(), but after _startup()
+	 * 
+	 * @return
+	 */
+	protected abstract boolean _preRun();
+
+	/**
+	 * _Run() is called after _startup() and contains should contain the main
+	 * loop.
+	 * 
+	 * @return
+	 */
+	protected abstract boolean _Run();
+
+	/**
+	 * _postRun() is called after _Run() exits, not necessarily before
+	 * _shutdown()
+	 * 
+	 * @return
+	 */
+	protected abstract boolean _postRun();
+
+	/*
+	 * Control
+	 */
+
+	/**
+	 * startup() initializes the internal thread, sets the runCMD to true, and
+	 * calls _startup() prior to starting of the internal Thread.
+	 */
+	public void startup() {
+
+		this.thisThread = new Thread(this, this.threadName);
+		this.runCmd = true;
+		this._startup();
+		this.thisThread.start();
+	}
+
+	/**
+	 * This method is called just before ControlledRunnable.thisThread.start()
+	 * is called.
+	 */
+	protected abstract void _startup();
+
+	/**
+	 * This method is called to request a shutdown of the runnable.
+	 */
+	public void shutdown() {
+		this.runCmd = false;
+		this._shutdown();
+	}
+
+	/**
+	 * This method is called just after ControlledRunnable.runCmd is set to
+	 * False.
+	 */
+	protected abstract void _shutdown();
+
+	/*
+	 * Getters n setters
+	 */
+	public boolean getRunCmd() {
+		return runCmd;
+	}
+
+	public boolean getRunStatus() {
+		return runStatus;
+	}
+
+	/*
+	 * Blockers
+	 */
+	public void blockTillRunStatus(boolean status) {
+		while (this.runStatus != status) {
+			try {
+				System.out.println("BLOCKING");
+				Thread.sleep(25L);
+			} catch (InterruptedException e) {
+				Logger.debug( e.getMessage());
+
+				break;
+			}
+		}
+	}
+
+	/**
+	 * @return the thisThread
+	 */
+	protected Thread getThisThread() {
+		return thisThread;
+	}
+
+	/**
+	 * @return the threadName
+	 */
+	public String getThreadName() {
+		return threadName;
+	}
+
+	/*
+	 * Instance monitoring and tools
+	 */
+
+	// Runnable tracking
+	private static final ArrayList<ControlledRunnable> runnables = new ArrayList<>();
+
+	public static void shutdownAllRunnables() {
+		for (ControlledRunnable cr : ControlledRunnable.runnables) {
+			//Use Direct logging since JobManager is a runnable.
+            Logger.info("ControlledRunnable",
+					"Sending Shutdown cmd to: " + cr.threadName);
+			cr.shutdown();
+		}
+	}
+
+}
diff --git a/src/engine/db/archive/BaneRecord.java b/src/engine/db/archive/BaneRecord.java
new file mode 100644
index 00000000..51209f06
--- /dev/null
+++ b/src/engine/db/archive/BaneRecord.java
@@ -0,0 +1,383 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.db.archive;
+
+import engine.Enum;
+import engine.objects.Bane;
+import engine.objects.City;
+import engine.workthreads.WarehousePushThread;
+import org.joda.time.DateTime;
+import org.pmw.tinylog.Logger;
+
+import java.sql.*;
+import java.time.LocalDateTime;
+import java.util.concurrent.LinkedBlockingQueue;
+
+import static engine.Enum.RecordEventType;
+
+public class BaneRecord extends DataRecord {
+
+	private static final LinkedBlockingQueue<BaneRecord> recordPool = new LinkedBlockingQueue<>();
+	private RecordEventType eventType;
+	private String cityHash;
+	private String cityName;
+	private String cityGuildHash;
+	private String cityNationHash;
+	private String baneDropperHash;
+	private String baneGuildHash;
+	private String baneNationHash;
+	private DateTime baneLiveTime;
+	private DateTime baneDropTime;
+
+	private BaneRecord(Bane bane) {
+		this.recordType = Enum.DataRecordType.BANE;
+		this.eventType = RecordEventType.PENDING;
+	}
+
+	public static BaneRecord borrow(Bane bane, RecordEventType eventType) {
+		BaneRecord baneRecord;
+
+		baneRecord = recordPool.poll();
+
+		if (baneRecord == null) {
+			baneRecord = new BaneRecord(bane);
+			baneRecord.eventType = eventType;
+		}
+		else {
+			baneRecord.recordType = Enum.DataRecordType.BANE;
+			baneRecord.eventType = eventType;
+
+		}
+
+		baneRecord.cityHash = bane.getCity().getHash();
+		baneRecord.cityName = bane.getCity().getCityName();
+		baneRecord.cityGuildHash = bane.getCity().getGuild().getHash();
+		baneRecord.cityNationHash = bane.getCity().getGuild().getNation().getHash();
+
+
+		if (bane.getOwner() == null) {
+			baneRecord.baneDropperHash = "ERRANT";
+			baneRecord.baneGuildHash = "ERRANT";
+			baneRecord.baneNationHash = "ERRANT";
+		}
+		else {
+			baneRecord.baneDropperHash = DataWarehouse.hasher.encrypt(bane.getOwner().getObjectUUID());  // getPlayerCharacter didn't check hash first?  OMFG
+
+
+			baneRecord.baneGuildHash = bane.getOwner().getGuild().getHash();
+			baneRecord.baneNationHash = bane.getOwner().getGuild().getNation().getHash();
+
+
+			baneRecord.baneLiveTime = bane.getLiveDate();
+			baneRecord.baneDropTime = bane.getPlacementDate();
+		}
+
+
+		return baneRecord;
+	}
+
+	public static PreparedStatement buildBanePushStatement(Connection connection, ResultSet rs) throws SQLException {
+
+		PreparedStatement outStatement = null;
+		String queryString = "INSERT INTO `warehouse_banehistory` (`event_number`, `city_id`, `city_name`, `char_id`, `offGuild_id`, `offNat_id`, `defGuild_id`, `defNat_id`, `dropDatetime`, `liveDateTime`, `resolution`) VALUES(?,?,?,?,?,?,?,?,?,?,?)";
+		java.util.Date sqlDateTime;
+
+		outStatement = connection.prepareStatement(queryString);
+
+		// Bind record data
+
+		outStatement.setInt(1, rs.getInt("event_number"));
+		outStatement.setString(2, rs.getString("city_id"));
+		outStatement.setString(3, rs.getString("city_name"));
+		outStatement.setString(4, rs.getString("char_id"));
+		outStatement.setString(5, rs.getString("offGuild_id"));
+		outStatement.setString(6, rs.getString("offNat_id"));
+		outStatement.setString(7, rs.getString("defGuild_id"));
+		outStatement.setString(8, rs.getString("defNat_id"));
+
+		sqlDateTime = rs.getTimestamp("dropDatetime");
+
+		if (sqlDateTime == null)
+			outStatement.setNull(9, Types.DATE);
+		else
+			outStatement.setTimestamp(9, rs.getTimestamp("dropDatetime"));
+
+		sqlDateTime = rs.getTimestamp("dropDatetime");
+
+		if (sqlDateTime == null)
+			outStatement.setNull(10, Types.DATE);
+		else
+			outStatement.setTimestamp(10, rs.getTimestamp("liveDateTime"));
+
+		outStatement.setString(11, rs.getString("resolution"));
+
+		return outStatement;
+	}
+
+	public static PreparedStatement buildBaneQueryStatement(Connection connection) throws SQLException {
+
+		PreparedStatement outStatement = null;
+		String queryString = "SELECT * FROM `warehouse_banehistory` WHERE `event_number` > ?";
+		outStatement = connection.prepareStatement(queryString);
+		outStatement.setInt(1, WarehousePushThread.baneIndex);
+		return outStatement;
+	}
+
+	public static DateTime getLastBaneDateTime(City city) {
+
+		DateTime outDateTime = null;
+
+		try (Connection connection = DataWarehouse.connectionPool.getConnection();
+				PreparedStatement statement = buildDateTimeQueryStatement(connection, city);
+				ResultSet rs = statement.executeQuery()) {
+
+			while (rs.next()) {
+
+				outDateTime = new DateTime(rs.getTimestamp("endDatetime"));
+
+			}
+
+		} catch (SQLException e) {
+			Logger.error( e.toString());
+		}
+
+		return outDateTime;
+	}
+
+
+	private static PreparedStatement buildDateTimeQueryStatement (Connection connection, City city) throws SQLException {
+		PreparedStatement outStatement;
+		String queryString = "SELECT `endDatetime` FROM `warehouse_banehistory` WHERE `city_id` = ? ORDER BY `endDatetime` DESC LIMIT 1";
+		outStatement = connection.prepareStatement(queryString);
+		outStatement.setString(1, city.getHash());
+		return outStatement;
+
+	}
+
+	public static void updateLiveDate(Bane bane, DateTime dateTime) {
+
+		if (bane == null)
+			return;
+
+		try (Connection connection = DataWarehouse.connectionPool.getConnection();
+				PreparedStatement statement = buildUpdateLiveDateStatement(connection, bane, dateTime)) {
+
+			statement.execute();
+
+		} catch (SQLException e) {
+			Logger.error( e.toString());
+		}
+	}
+
+	private static PreparedStatement buildUpdateLiveDateStatement(Connection connection, Bane bane, DateTime dateTime) throws SQLException {
+
+		PreparedStatement outStatement = null;
+		String queryString = "UPDATE `warehouse_banehistory` SET `liveDatetime` = ?, `dirty` = 1 WHERE `city_id` = ? AND `resolution` = 'PENDING'";
+
+		outStatement = connection.prepareStatement(queryString);
+		outStatement.setTimestamp(1, new java.sql.Timestamp(dateTime.getMillis()));
+		outStatement.setString(2, bane.getCity().getHash());
+
+		return outStatement;
+	}
+
+	private static PreparedStatement buildUpdateResolutionStatement(Connection connection, Bane bane, RecordEventType eventType) throws SQLException {
+
+		PreparedStatement outStatement = null;
+		String queryString = "UPDATE `warehouse_banehistory` SET `endDatetime` = ?, `resolution` = ?, `dirty` = 1 WHERE `city_id` = ? AND `resolution` = 'PENDING'";
+
+		outStatement = connection.prepareStatement(queryString);
+		outStatement.setTimestamp(1, Timestamp.valueOf(LocalDateTime.now()));
+		outStatement.setString(2, eventType.name());
+		outStatement.setString(3, bane.getCity().getHash());
+
+		return outStatement;
+	}
+
+	public static void updateResolution(Bane bane, RecordEventType eventType) {
+
+		try (Connection connection = DataWarehouse.connectionPool.getConnection();
+				PreparedStatement statement = buildUpdateResolutionStatement(connection, bane, eventType)) {
+
+			statement.execute();
+
+		} catch (SQLException e) {
+			Logger.error(e.toString());
+		}
+	}
+
+	public static String getBaneHistoryString() {
+
+		String outString;
+		String queryString;
+		String dividerString;
+		String newLine = System.getProperty("line.separator");
+		outString = "[LUA_BANES() DATA WAREHOUSE]" + newLine;
+		dividerString = "--------------------------------" + newLine;
+		queryString = "CALL `baneHistory`()";
+
+		try (Connection connection = DataWarehouse.connectionPool.getConnection();
+				PreparedStatement statement = connection.prepareCall(queryString);
+				ResultSet rs = statement.executeQuery()) {
+
+			while (rs.next()) {
+
+				outString += "Magicbane unresolved banes: " + rs.getInt("PENDING") + '/' + rs.getInt("TOTAL") + newLine;
+				outString += dividerString;
+				outString += "Bane Resolution History" + newLine;
+				outString += dividerString;
+
+				outString += "Destruction: " + rs.getInt("DESTROY") + newLine;
+				outString += "Capture: " + rs.getInt("CAPTURE") + newLine;
+				outString += "Defended: " + rs.getInt("DEFEND") + newLine;
+			}
+
+		} catch (SQLException e) {
+			e.printStackTrace();
+		}
+		return outString;
+	}
+
+	public static void updateDirtyRecords() {
+
+		String queryString = "SELECT * FROM `warehouse_banehistory` where `dirty` = 1";
+
+		// Reset character delta
+
+		WarehousePushThread.baneDelta = 0;
+
+		try (Connection localConnection = DataWarehouse.connectionPool.getConnection();
+				PreparedStatement statement = localConnection.prepareStatement(queryString, ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE); // Make this an updatable result set as we'll reset the dirty flag as we go along
+				ResultSet rs = statement.executeQuery()) {
+
+			while (rs.next()) {
+
+				// Only update the index and dirty flag
+				// if the remote database update succeeded
+
+				if (updateDirtyRecord(rs) == true)
+					WarehousePushThread.baneDelta++;
+				else
+					continue;
+
+				// Reset the dirty flag in the local database
+
+				rs.updateInt("dirty", 0);
+				rs.updateRow();
+			}
+
+		} catch (SQLException e) {
+			Logger.error( e.toString());
+		}
+	}
+
+	private static boolean updateDirtyRecord(ResultSet rs) {
+
+		try (Connection remoteConnection = DataWarehouse.remoteConnectionPool.getConnection();
+				PreparedStatement statement = buildUpdateDirtyStatement(remoteConnection, rs)) {
+
+			statement.execute();
+			return true;
+		} catch (SQLException e) {
+			Logger.error( e.toString());
+			return false;
+		}
+	}
+
+	private static PreparedStatement buildUpdateDirtyStatement(Connection connection, ResultSet rs) throws SQLException {
+
+		PreparedStatement outStatement;
+		String queryString = "UPDATE `warehouse_banehistory` SET `liveDateTime` = ?, `endDateTime` = ?, `resolution` = ? WHERE `event_number` = ?";
+		java.util.Date sqlDateTime;
+
+		outStatement = connection.prepareStatement(queryString);
+
+		// Bind record data
+
+		sqlDateTime = rs.getTimestamp("liveDateTime");
+
+		if (sqlDateTime == null)
+			outStatement.setNull(1, Types.DATE);
+		else
+			outStatement.setTimestamp(1, rs.getTimestamp("liveDateTime"));
+
+		sqlDateTime = rs.getTimestamp("endDateTime");
+
+		if (sqlDateTime == null)
+			outStatement.setNull(2, Types.DATE);
+		else
+			outStatement.setTimestamp(2, rs.getTimestamp("endDateTime"));
+
+		outStatement.setString(3, rs.getString("resolution"));
+		outStatement.setInt(4, rs.getInt("event_number"));
+
+		return outStatement;
+	}
+
+	void reset() {
+		this.cityHash = null;
+		this.cityGuildHash = null;
+		this.cityNationHash = null;
+		this.baneDropperHash = null;
+		this.baneGuildHash = null;
+		this.baneNationHash = null;
+		this.baneLiveTime = null;
+	}
+
+	public void release() {
+		this.reset();
+		recordPool.add(this);
+	}
+
+	public void write() {
+
+		try (Connection connection = DataWarehouse.connectionPool.getConnection();
+				PreparedStatement statement = buildBaneInsertStatement(connection)) {
+
+			statement.execute();
+
+		} catch (SQLException e) {
+			Logger.error( e.toString());
+		}
+
+	}
+
+	private PreparedStatement buildBaneInsertStatement(Connection connection) throws SQLException {
+
+		PreparedStatement outStatement = null;
+		String queryString = "INSERT INTO `warehouse_banehistory` (`city_id`, `city_name`, `char_id`, `offGuild_id`, `offNat_id`, `defGuild_id`, `defNat_id`, `dropDatetime`, `liveDateTime`, `resolution`) VALUES(?,?,?,?,?,?,?,?,?,?)";
+
+		outStatement = connection.prepareStatement(queryString);
+
+		outStatement.setString(1, this.cityHash);
+		outStatement.setString(2, this.cityName);
+		outStatement.setString(3, this.baneDropperHash);
+		outStatement.setString(4, this.baneGuildHash);
+		outStatement.setString(5, this.baneNationHash);
+		outStatement.setString(6, this.cityGuildHash);
+		outStatement.setString(7, this.cityNationHash);
+
+		if (this.baneDropTime == null)
+			outStatement.setNull(8, java.sql.Types.DATE);
+		else
+			outStatement.setTimestamp(8, new java.sql.Timestamp(this.baneDropTime.getMillis()));
+
+		if (this.baneLiveTime == null)
+			outStatement.setNull(9, java.sql.Types.DATE);
+		else
+			outStatement.setTimestamp(9, new java.sql.Timestamp(this.baneLiveTime.getMillis()));
+
+		outStatement.setString(10, this.eventType.name());
+
+
+		return outStatement;
+	}
+} // END CLASS
+
diff --git a/src/engine/db/archive/CharacterRecord.java b/src/engine/db/archive/CharacterRecord.java
new file mode 100644
index 00000000..b1c6e3b5
--- /dev/null
+++ b/src/engine/db/archive/CharacterRecord.java
@@ -0,0 +1,284 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.db.archive;
+
+import engine.Enum;
+import engine.objects.Guild;
+import engine.objects.PlayerCharacter;
+import engine.workthreads.WarehousePushThread;
+import org.pmw.tinylog.Logger;
+
+import java.sql.*;
+import java.time.LocalDateTime;
+import java.util.concurrent.LinkedBlockingQueue;
+
+/*
+ * This class warehouses character creation events.  It also tracks
+ * updates to summary kills/death data and their promotion class.
+ */
+public class CharacterRecord extends DataRecord {
+
+    // Local object pool for class
+
+    private static final LinkedBlockingQueue<CharacterRecord> recordPool = new LinkedBlockingQueue<>();
+
+    private PlayerCharacter player;
+
+    private CharacterRecord(PlayerCharacter player) {
+        this.recordType = Enum.DataRecordType.CHARACTER;
+        this.player = player;
+    }
+
+    public static CharacterRecord borrow(PlayerCharacter player) {
+        CharacterRecord characterRecord;
+
+        characterRecord = recordPool.poll();
+
+        if (characterRecord == null) {
+            characterRecord = new CharacterRecord(player);
+        }
+        else {
+            characterRecord.recordType = Enum.DataRecordType.CHARACTER;
+            characterRecord.player = player;
+
+        }
+
+        return characterRecord;
+    }
+
+    private static PreparedStatement buildCharacterInsertStatement(Connection connection, PlayerCharacter player) throws SQLException {
+
+        PreparedStatement outStatement = null;
+        String queryString = "INSERT INTO `warehouse_characterhistory` (`char_id`, `char_fname`, `char_lname`, `baseClass`, `race`, `promoteClass`, `startingGuild`, `datetime`) VALUES(?,?,?,?,?,?,?,?)";
+        Guild charGuild;
+
+        outStatement = connection.prepareStatement(queryString);
+
+        charGuild = player.getGuild();
+
+        // Bind character data
+
+        outStatement.setString(1, DataWarehouse.hasher.encrypt(player.getObjectUUID()));
+        outStatement.setString(2, player.getFirstName());
+        outStatement.setString(3, player.getLastName());
+        outStatement.setInt(4, player.getBaseClassID());
+        outStatement.setInt(5, player.getRaceID());
+        outStatement.setInt(6, player.getPromotionClassID());
+        outStatement.setString(7, DataWarehouse.hasher.encrypt(charGuild.getObjectUUID()));
+        outStatement.setTimestamp(8, Timestamp.valueOf(LocalDateTime.now()));
+
+        return outStatement;
+    }
+
+    public static PreparedStatement buildCharacterPushStatement(Connection connection, ResultSet rs) throws SQLException {
+
+        PreparedStatement outStatement = null;
+        String queryString = "INSERT INTO `warehouse_characterhistory` (`event_number`, `char_id`, `char_fname`, `char_lname`, `baseClass`, `race`, `promoteClass`, `startingGuild`, `datetime`) VALUES(?,?,?,?,?,?,?,?,?)";
+
+        outStatement = connection.prepareStatement(queryString);
+
+        // Bind record data
+
+        outStatement.setInt(1, rs.getInt("event_number"));
+        outStatement.setString(2, rs.getString("char_id"));
+        outStatement.setString(3, rs.getString("char_fname"));
+        outStatement.setString(4, rs.getString("char_lname"));
+        outStatement.setInt(5, rs.getInt("baseClass"));
+        outStatement.setInt(6, rs.getInt("race"));
+        outStatement.setInt(7, rs.getInt("promoteClass"));
+        outStatement.setString(8, rs.getString("startingGuild"));
+        outStatement.setTimestamp(9, rs.getTimestamp("datetime"));
+        return outStatement;
+    }
+
+    public static PreparedStatement buildCharacterQueryStatement(Connection connection) throws SQLException {
+
+        PreparedStatement outStatement = null;
+        String queryString = "SELECT * FROM `warehouse_characterhistory` WHERE `event_number` > ?";
+        outStatement = connection.prepareStatement(queryString);
+        outStatement.setInt(1, WarehousePushThread.charIndex);
+        return outStatement;
+    }
+
+    public static void advanceKillCounter(PlayerCharacter player) {
+
+        try (Connection connection = DataWarehouse.connectionPool.getConnection();
+             PreparedStatement statement = buildKillCounterStatement(connection, player)) {
+
+            statement.execute();
+
+        } catch (SQLException e) {
+            Logger.error( e.toString());
+        }
+
+    }
+
+    private static PreparedStatement buildKillCounterStatement(Connection connection, PlayerCharacter player) throws SQLException {
+
+        PreparedStatement outStatement = null;
+        String queryString = "UPDATE `warehouse_characterhistory` SET `kills` = `kills` +1, `dirty` = 1 WHERE `char_id` = ?";
+
+        if (player == null)
+            return outStatement;
+
+        outStatement = connection.prepareStatement(queryString);
+        outStatement.setString(1, player.getHash());
+
+        return outStatement;
+    }
+
+    public static void advanceDeathCounter(PlayerCharacter player) {
+
+        try (Connection connection = DataWarehouse.connectionPool.getConnection();
+             PreparedStatement statement = buildDeathCounterStatement(connection, player)) {
+
+            statement.execute();
+
+        } catch (SQLException e) {
+            Logger.error( e.toString());
+        }
+
+    }
+
+    private static PreparedStatement buildDeathCounterStatement(Connection connection, PlayerCharacter player) throws SQLException {
+
+        PreparedStatement outStatement = null;
+        String queryString = "UPDATE `warehouse_characterhistory` SET `deaths` = `deaths` +1, `dirty` = 1 WHERE `char_id` = ?";
+
+        if (player == null)
+            return outStatement;
+
+        outStatement = connection.prepareStatement(queryString);
+        outStatement.setString(1, player.getHash());
+
+        return outStatement;
+    }
+
+    public static void updatePromotionClass(PlayerCharacter player) {
+
+        try (Connection connection = DataWarehouse.connectionPool.getConnection();
+             PreparedStatement statement = buildUpdatePromotionStatement(connection, player)) {
+
+            statement.execute();
+
+        } catch (SQLException e) {
+            Logger.error( e.toString());
+        }
+
+    }
+
+    private static PreparedStatement buildUpdatePromotionStatement(Connection connection, PlayerCharacter player) throws SQLException {
+
+        PreparedStatement outStatement = null;
+        String queryString = "UPDATE `warehouse_characterhistory` SET `promoteClass` = ?, `dirty` = 1 WHERE `char_id` = ?";
+
+        if (player == null)
+            return outStatement;
+
+        outStatement = connection.prepareStatement(queryString, ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE);
+        outStatement.setInt(1, player.getPromotionClassID());
+        outStatement.setString(2, player.getHash());
+
+        return outStatement;
+    }
+
+    public static void updateDirtyRecords() {
+
+        String queryString = "SELECT * FROM `warehouse_characterhistory` where `dirty` = 1";
+
+        // Reset character delta
+
+        WarehousePushThread.charDelta = 0;
+
+        try (Connection localConnection = DataWarehouse.connectionPool.getConnection();
+             PreparedStatement statement = localConnection.prepareStatement(queryString, ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE); // Make this an updatable result set as we'll reset the dirty flag as we go along
+             ResultSet rs = statement.executeQuery()) {
+
+            while (rs.next()) {
+
+                // Only update the index and dirty flag
+                // if the remote database update succeeded
+
+                if (updateDirtyRecord(rs) == true)
+                    WarehousePushThread.charDelta++;
+                else
+                    continue;
+
+                // Reset the dirty flag in the local database
+
+                rs.updateInt("dirty", 0);
+                rs.updateRow();
+            }
+
+        } catch (SQLException e) {
+            Logger.error(e.toString());
+        }
+    }
+
+    private static boolean updateDirtyRecord(ResultSet rs) {
+
+        try (Connection remoteConnection = DataWarehouse.remoteConnectionPool.getConnection();
+             PreparedStatement statement = buildUpdateDirtyStatement(remoteConnection, rs)) {
+
+            statement.execute();
+            return true;
+        } catch (SQLException e) {
+            Logger.error(e.toString());
+            return false;
+        }
+    }
+
+    private static PreparedStatement buildUpdateDirtyStatement(Connection connection, ResultSet rs) throws SQLException {
+
+        PreparedStatement outStatement;
+        String queryString = "UPDATE `warehouse_characterhistory` SET `promoteClass` = ?, `kills` = ?, `deaths` = ? WHERE `char_id` = ?";
+
+        outStatement = connection.prepareStatement(queryString);
+
+        // Bind record data
+
+        outStatement.setInt(1, rs.getInt("promoteClass"));
+        outStatement.setInt(2, rs.getInt("kills"));
+        outStatement.setInt(3, rs.getInt("deaths"));
+        outStatement.setString(4, rs.getString("char_id"));
+
+        return outStatement;
+    }
+
+    void reset() {
+        this.player = null;
+    }
+
+    public void release() {
+        this.reset();
+        recordPool.add(this);
+    }
+
+    public void write() {
+
+        try (Connection connection = DataWarehouse.connectionPool.getConnection();
+             PreparedStatement statement = buildCharacterInsertStatement(connection, this.player)) {
+
+            statement.execute();
+
+        } catch (SQLException e) {
+            Logger.error( "Error writing character record " + e.toString());
+
+        }
+    }
+
+}
+
+
+
+
+
+
+
diff --git a/src/engine/db/archive/CityRecord.java b/src/engine/db/archive/CityRecord.java
new file mode 100644
index 00000000..50b123e0
--- /dev/null
+++ b/src/engine/db/archive/CityRecord.java
@@ -0,0 +1,161 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.db.archive;
+
+import engine.Enum;
+import engine.objects.City;
+import engine.workthreads.WarehousePushThread;
+
+import java.sql.*;
+import java.util.concurrent.LinkedBlockingQueue;
+
+public class CityRecord extends DataRecord {
+
+	private static final LinkedBlockingQueue<CityRecord> recordPool = new LinkedBlockingQueue<>();
+	private Enum.RecordEventType eventType;
+	private City city;
+	private String cityHash;
+	private String cityGuildHash;
+	private String cityName;
+	private String cityMotto;
+	private float locX;
+	private float locY;
+	private String zoneHash;
+	private java.time.LocalDateTime establishedDatetime;
+
+	private CityRecord(City city) {
+		this.recordType = Enum.DataRecordType.CITY;
+		this.city = city;
+		this.eventType = Enum.RecordEventType.CREATE;
+
+	}
+
+	public static CityRecord borrow(City city, Enum.RecordEventType eventType) {
+		CityRecord cityRecord;
+
+		cityRecord = recordPool.poll();
+
+		if (cityRecord == null) {
+			cityRecord = new CityRecord(city);
+			cityRecord.eventType = eventType;
+		}
+		else {
+			cityRecord.recordType = Enum.DataRecordType.CITY;
+			cityRecord.eventType = eventType;
+			cityRecord.city = city;
+
+		}
+
+		if (cityRecord.city.getHash() == null)
+			cityRecord.city.setHash(DataWarehouse.hasher.encrypt(cityRecord.city.getObjectUUID()));
+
+		cityRecord.cityHash = cityRecord.city.getHash();
+
+
+		cityRecord.cityName = cityRecord.city.getCityName();
+		cityRecord.cityMotto = cityRecord.city.getMotto();
+
+		cityRecord.cityGuildHash = cityRecord.city.getGuild().getHash();
+
+		cityRecord.locX = cityRecord.city.getTOL().getLoc().x;
+		cityRecord.locY = -cityRecord.city.getTOL().getLoc().z; // flip sign on 'y' coordinate
+
+		cityRecord.zoneHash = cityRecord.city.getParent().getHash();
+
+		if (cityRecord.eventType.equals(Enum.RecordEventType.CREATE))
+            cityRecord.establishedDatetime =  cityRecord.city.established;
+		else
+			cityRecord.establishedDatetime = java.time.LocalDateTime.now();
+
+		return cityRecord;
+	}
+
+	public static PreparedStatement buildCityPushStatement(Connection connection, ResultSet rs) throws SQLException {
+
+		PreparedStatement outStatement = null;
+		String queryString = "INSERT INTO `warehouse_cityhistory` (`event_number`, `city_id`, `city_name`, `city_motto`, `guild_id`, `loc_x`, `loc_y`, `zone_id`, `eventType`, `datetime`) VALUES(?,?,?,?,?,?,?,?,?,?)";
+		outStatement = connection.prepareStatement(queryString);
+
+		// Bind record data
+
+		outStatement.setInt(1, rs.getInt("event_number"));
+		outStatement.setString(2, rs.getString("city_id"));
+		outStatement.setString(3, rs.getString("city_name"));
+		outStatement.setString(4, rs.getString("city_motto"));
+		outStatement.setString(5, rs.getString("guild_id"));
+
+		outStatement.setFloat(6, rs.getFloat("loc_x"));
+		outStatement.setFloat(7, rs.getFloat("loc_y"));
+		outStatement.setString(8, rs.getString("zone_id"));
+		outStatement.setString(9, rs.getString("eventType"));
+		outStatement.setTimestamp(10, rs.getTimestamp("datetime"));
+
+		return outStatement;
+	}
+
+	public static PreparedStatement buildCityQueryStatement(Connection connection) throws SQLException {
+
+		PreparedStatement outStatement = null;
+		String queryString = "SELECT * FROM `warehouse_cityhistory` WHERE `event_number` > ?";
+		outStatement = connection.prepareStatement(queryString);
+		outStatement.setInt(1, WarehousePushThread.cityIndex);
+		return outStatement;
+	}
+
+	void reset() {
+		this.city = null;
+		this.cityHash = null;
+		this.cityGuildHash = null;
+		this.cityMotto = null;
+		this.zoneHash = null;
+		this.establishedDatetime = null;
+
+	}
+
+	public void release() {
+		this.reset();
+		recordPool.add(this);
+	}
+
+	public void write() {
+
+		try (Connection connection = DataWarehouse.connectionPool.getConnection();
+				PreparedStatement statement = this.buildCityInsertStatement(connection)) {
+
+			statement.execute();
+
+		} catch (SQLException e) {
+			e.printStackTrace();
+		}
+	}
+
+	private PreparedStatement buildCityInsertStatement(Connection connection) throws SQLException {
+
+		PreparedStatement outStatement = null;
+		String queryString = "INSERT INTO `warehouse_cityhistory` (`city_id`, `city_name`, `city_motto`, `guild_id`, `loc_x`, `loc_y`, `zone_id`, `eventType`, `datetime`) VALUES(?,?,?,?,?,?,?,?,?)";
+
+		outStatement = connection.prepareStatement(queryString);
+
+		// Bind character data
+
+		outStatement.setString(1, this.cityHash);
+		outStatement.setString(2, this.cityName);
+		outStatement.setString(3, this.cityMotto);
+		outStatement.setString(4, this.cityGuildHash);
+
+		outStatement.setFloat(5, this.locX);
+		outStatement.setFloat(6, this.locY);
+		outStatement.setString(7, this.zoneHash);
+		outStatement.setString(8, this.eventType.name());
+		outStatement.setTimestamp(9,  Timestamp.valueOf(this.establishedDatetime));
+
+		return outStatement;
+	}
+}
diff --git a/src/engine/db/archive/DataRecord.java b/src/engine/db/archive/DataRecord.java
new file mode 100644
index 00000000..a86dd3a3
--- /dev/null
+++ b/src/engine/db/archive/DataRecord.java
@@ -0,0 +1,23 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+
+package engine.db.archive;
+
+import engine.Enum;
+
+class DataRecord {
+
+    public Enum.DataRecordType recordType;
+
+    DataRecord() {
+
+    }
+
+}
diff --git a/src/engine/db/archive/DataWarehouse.java b/src/engine/db/archive/DataWarehouse.java
new file mode 100644
index 00000000..3947d72d
--- /dev/null
+++ b/src/engine/db/archive/DataWarehouse.java
@@ -0,0 +1,324 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.db.archive;
+
+import com.zaxxer.hikari.HikariConfig;
+import com.zaxxer.hikari.HikariDataSource;
+import engine.gameManager.ConfigManager;
+import engine.util.Hasher;
+import org.pmw.tinylog.Logger;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.concurrent.LinkedBlockingQueue;
+
+import static engine.Enum.DataRecordType;
+
+public class DataWarehouse implements Runnable {
+
+    public static final Hasher hasher = new Hasher("Cthulhu Owns Joo");
+    private static final LinkedBlockingQueue<DataRecord> recordQueue = new LinkedBlockingQueue<>();
+    public static HikariDataSource connectionPool = null;
+    public static HikariDataSource remoteConnectionPool = null;
+
+    public DataWarehouse() {
+
+        Logger.info("Configuring local Database Connection Pool...");
+
+        configureConnectionPool();
+
+        // If WarehousePush is disabled
+        // then early exit
+
+        if ( ConfigManager.MB_WORLD_WAREHOUSE_PUSH.getValue().equals("false")) {
+            Logger.info("Warehouse Remote Connection disabled along with push");
+            return;
+        }
+
+        Logger.info( "Configuring remote Database Connection Pool...");
+        configureRemoteConnectionPool();
+
+    }
+
+    public static void bootStrap() {
+        Thread warehousingThread;
+        warehousingThread = new Thread(new DataWarehouse());
+
+        warehousingThread.setName("DataWarehouse");
+        warehousingThread.setPriority(Thread.NORM_PRIORITY - 1);
+        warehousingThread.start();
+    }
+
+    public static void pushToWarehouse(DataRecord dataRecord) {
+
+        DataWarehouse.recordQueue.add(dataRecord);
+    }
+
+    public static void writeHash(DataRecordType recordType, int uuid) {
+
+        // Member variable declaration
+
+        Connection connection = null;
+        PreparedStatement statement = null;
+        String queryString;
+        String hashString;
+
+        try {
+            connection = DataWarehouse.connectionPool.getConnection();
+        } catch (SQLException e) {
+            e.printStackTrace();
+        }
+
+        if (connection == null) {
+            Logger.error("Null connection when writing zone hash.");
+            return;
+        }
+
+        // Build query string
+
+        switch (recordType) {
+            case CHARACTER:
+                queryString = "UPDATE `obj_character` SET hash = ? WHERE `UID` = ?";
+                break;
+            case GUILD:
+                queryString = "UPDATE `obj_guild` SET hash = ? WHERE `UID` = ?";
+                break;
+            case ZONE:
+                queryString = "UPDATE `obj_zone` SET hash = ? WHERE `UID` = ?";
+                break;
+            case CITY:
+                queryString = "UPDATE `obj_city` SET hash = ? WHERE `UID` = ?";
+                break;
+            case REALM:
+                queryString = "UPDATE `obj_realm` SET hash = ? WHERE `realmID` = ?";
+                break;
+            default:
+                queryString = null;
+                break;
+        }
+
+        hashString = hasher.encrypt(uuid);
+
+        // Write this record to the warehouse
+
+        try {
+
+            statement = connection.prepareStatement(queryString);
+
+            statement.setString(1, hashString);
+            statement.setLong(2, uuid);
+            statement.execute();
+        } catch (SQLException e) {
+            Logger.error("Error writing hash for uuid" + uuid + " of type " + recordType.name() + ' ' + e.toString());
+            e.printStackTrace();
+        } finally {
+            if (connection != null) {
+                try {
+                    connection.close();
+                } catch (SQLException e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+    }
+
+    public static boolean recordExists(DataRecordType recordType, int uuid) {
+
+        // Member variable declaration
+
+        Connection connection = null;
+        PreparedStatement statement = null;
+        String queryString;
+        ResultSet resultSet;
+
+        try {
+            connection = DataWarehouse.connectionPool.getConnection();
+        } catch (SQLException e) {
+            e.printStackTrace();
+        }
+
+        if (connection == null) {
+            Logger.error("Null connection during char record lookup");
+            return true;  // False positive here, so as not to try and write the record twice.
+            // will refactor out once we write hashes to object tables
+        }
+
+        // Build query string
+
+        switch (recordType) {
+            case CHARACTER:
+                queryString = "SELECT COUNT(*) from warehouse_characterhistory where char_id = ?";
+                break;
+            case GUILD:
+                queryString = "SELECT COUNT(*) from warehouse_guildhistory where guild_id = ?";
+                break;
+            case CITY:
+                queryString = "SELECT COUNT(*) from warehouse_cityhistory where city_id = ?";
+                break;
+            case REALM:
+                queryString = "SELECT COUNT(*) from warehouse_realmhistory where realm_id = ?";
+                break;
+            case BANE:
+                queryString = "SELECT COUNT(*) from warehouse_banehistory where city_id = ? AND `resolution` = 'PENDING'";
+                break;
+            case ZONE: // Does not really exist but enum acts as a proxy for hash lookup
+            case MINE: // Does not really exist but enum acts as a proxy for hash lookup
+            default:
+                queryString = null;
+                break;
+        }
+
+        try {
+            statement = connection.prepareStatement(queryString);
+            statement.setString(1, DataWarehouse.hasher.encrypt(uuid));
+            resultSet = statement.executeQuery();
+
+            while (resultSet.next()) {
+                return resultSet.getInt("COUNT(*)") > 0;
+            }
+
+        } catch (SQLException e) {
+            Logger.error("Error in record lookup for " + recordType.name() + " of uuid:" + uuid + e.toString());
+            e.printStackTrace();
+        } finally {
+            if (connection != null) {
+                try {
+                    connection.close();
+                } catch (SQLException e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+        return false;
+    }
+
+    public void run() {
+
+        // Working variable set
+
+        DataRecord dataRecord;
+        PvpRecord pvpRecord;
+        GuildRecord guildRecord;
+        CharacterRecord characterRecord;
+        CityRecord cityRecord;
+        BaneRecord baneRecord;
+        RealmRecord realmRecord;
+        MineRecord mineRecord;
+
+        Logger.info( "DataWarehouse is running.");
+
+        while (true) {
+
+            dataRecord = null;
+            pvpRecord = null;
+            guildRecord = null;
+            characterRecord = null;
+            cityRecord = null;
+            baneRecord = null;
+            realmRecord = null;
+            mineRecord = null;
+
+            try {
+                dataRecord = recordQueue.take();
+            } catch (InterruptedException e) {
+                e.printStackTrace();
+            }
+
+            // Write record to appropriate warehousing table
+
+            if (dataRecord != null) {
+
+                switch (dataRecord.recordType) {
+                    case PVP:
+                        pvpRecord = (PvpRecord) dataRecord;
+                        pvpRecord.write();
+                        pvpRecord.release();
+                        break;
+                    case CHARACTER:
+                        characterRecord = (CharacterRecord) dataRecord;
+                        characterRecord.write();
+                        characterRecord.release();
+                        break;
+                    case GUILD:
+                        guildRecord = (GuildRecord) dataRecord;
+                        guildRecord.write();
+                        guildRecord.release();
+                        break;
+                    case CITY:
+                        cityRecord = (CityRecord) dataRecord;
+                        cityRecord.write();
+                        cityRecord.release();
+                        break;
+                    case BANE:
+                        baneRecord = (BaneRecord) dataRecord;
+                        baneRecord.write();
+                        baneRecord.release();
+                        break;
+                    case REALM:
+                        realmRecord = (RealmRecord) dataRecord;
+                        realmRecord.write();
+                        realmRecord.release();
+                        break;
+                    case MINE:
+                        mineRecord = (MineRecord) dataRecord;
+                        mineRecord.write();
+                        mineRecord.release();
+                        break;
+                    default:
+                        Logger.error( "Unhandled record type");
+                        break;
+
+                } // end switch
+            }
+        }
+    }
+
+    private static void configureConnectionPool() {
+
+        HikariConfig config = new HikariConfig();
+
+        config.setMaximumPoolSize(10);
+
+        config.setJdbcUrl("jdbc:mysql://" +  ConfigManager.MB_DATABASE_ADDRESS.getValue() +
+                ":" +  ConfigManager.MB_DATABASE_PORT.getValue() + "/" +
+                ConfigManager.MB_DATABASE_NAME.getValue());
+        config.setUsername(ConfigManager.MB_DATABASE_USER.getValue());
+        config.setPassword( ConfigManager.MB_DATABASE_PASS.getValue());
+        config.addDataSourceProperty("characterEncoding", "utf8");
+        config.addDataSourceProperty("cachePrepStmts", "true");
+        config.addDataSourceProperty("prepStmtCacheSize", "250");
+        config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048");
+
+        connectionPool = new HikariDataSource(config); // setup the connection pool
+
+        Logger.info("Local warehouse database connection configured");
+    }
+
+    private static void configureRemoteConnectionPool() {
+
+        HikariConfig config = new HikariConfig();
+
+        config.setMaximumPoolSize(1); // Only the server talks to remote, so yeah.
+        config.setJdbcUrl(ConfigManager.MB_WAREHOUSE_ADDR.getValue());
+        config.setUsername(ConfigManager.MB_WAREHOUSE_USER.getValue());
+        config.setPassword(ConfigManager.MB_WAREHOUSE_PASS.getValue());
+        config.addDataSourceProperty("characterEncoding", "utf8");
+        config.addDataSourceProperty("cachePrepStmts", "true");
+        config.addDataSourceProperty("prepStmtCacheSize", "250");
+        config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048");
+
+        remoteConnectionPool = new HikariDataSource(config); // setup the connection pool
+
+        Logger.info("remote warehouse connection configured");
+    }
+
+}
diff --git a/src/engine/db/archive/GuildRecord.java b/src/engine/db/archive/GuildRecord.java
new file mode 100644
index 00000000..c68a3cb3
--- /dev/null
+++ b/src/engine/db/archive/GuildRecord.java
@@ -0,0 +1,215 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.db.archive;
+
+import engine.Enum;
+import engine.Enum.RecordEventType;
+import engine.objects.Guild;
+import engine.workthreads.WarehousePushThread;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.util.HashMap;
+import java.util.concurrent.LinkedBlockingQueue;
+
+public class GuildRecord extends DataRecord {
+
+	private static final LinkedBlockingQueue<GuildRecord> recordPool = new LinkedBlockingQueue<>();
+	private Enum.RecordEventType eventType;
+	private Guild guild;
+	public String guildHash;
+	private String guildName;
+	private String charterName;
+	private String GLHash;
+	private String guildMotto;
+	private int bgIcon;
+	private int bgColour1;
+	private int bgColour2;
+	private int fgIcon;
+	private int fgColour;
+	public int guildID;
+
+	private java.time.LocalDateTime  eventDatetime;
+	
+	public static HashMap<Integer, GuildRecord> GuildRecordCache = null;
+
+	private GuildRecord(Guild guild) {
+		this.recordType = Enum.DataRecordType.GUILD;
+		this.guild = guild;
+		this.eventType = Enum.RecordEventType.CREATE;
+	}
+	
+	
+
+	public GuildRecord(ResultSet rs) throws SQLException {
+		super();
+		this.eventType = RecordEventType.valueOf(rs.getString("eventType"));
+		this.guildHash = rs.getString("guild_id");
+		this.guildName = rs.getString("guild_name");
+		this.charterName = rs.getString("charter");
+		GLHash = rs.getString("guild_founder");
+		this.guildMotto = rs.getString("guild_motto");
+		this.bgIcon = rs.getInt("bgicon");
+		this.bgColour1 = rs.getInt("bgcoloura");
+		this.bgColour2 = rs.getInt("bgcolourb");
+		this.fgIcon = rs.getInt("fgicon");
+		this.fgColour = rs.getInt("fgcolour");
+
+		java.sql.Timestamp eventTimeStamp = rs.getTimestamp("upgradeDate");
+
+		if (eventTimeStamp != null)
+			this.eventDatetime = LocalDateTime.ofInstant(eventTimeStamp.toInstant(), ZoneId.systemDefault());
+	}
+
+
+
+	public static GuildRecord borrow(Guild guild, Enum.RecordEventType eventType) {
+		GuildRecord guildRecord;
+		//add
+		guildRecord = recordPool.poll();
+
+		if (guildRecord == null) {
+			guildRecord = new GuildRecord(guild);
+			guildRecord.eventType = eventType;
+		}
+		else {
+			guildRecord.guild = guild;
+			guildRecord.recordType = Enum.DataRecordType.GUILD;
+			guildRecord.eventType = eventType;
+
+		}
+
+		guildRecord.guildHash = guildRecord.guild.getHash();
+		guildRecord.guildID = guildRecord.guild.getObjectUUID();
+		guildRecord.guildName = guildRecord.guild.getName();
+		guildRecord.charterName = Enum.GuildType.getGuildTypeFromInt(guildRecord.guild.getCharter()).getCharterName();
+
+		guildRecord.GLHash = DataWarehouse.hasher.encrypt(guildRecord.guild.getGuildLeaderUUID());
+
+		guildRecord.guildMotto = guildRecord.guild.getMotto();
+		guildRecord.bgIcon = guildRecord.guild.getBgDesign();
+		guildRecord.bgColour1 = guildRecord.guild.getBgc1();
+		guildRecord.bgColour2 = guildRecord.guild.getBgc2();
+		guildRecord.fgIcon = guildRecord.guild.getSymbol();
+		guildRecord.fgColour = guildRecord.guild.getSc();
+
+		if (guild.getOwnedCity() != null)
+            guildRecord.eventDatetime =  guild.getOwnedCity().established;
+		else
+			guildRecord.eventDatetime = LocalDateTime.now();
+
+		return guildRecord;
+	}
+
+	public static PreparedStatement buildGuildPushStatement(Connection connection, ResultSet rs) throws SQLException {
+
+		PreparedStatement outStatement = null;
+		String queryString = "INSERT INTO `warehouse_guildhistory` (`event_number`, `guild_id`, `guild_name`, `guild_motto`, `guild_founder`, `charter`, `bgicon`, `bgcoloura`, `bgcolourb`, `fgicon`, `fgcolour`, `eventtype`, `datetime`) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?)";
+
+		outStatement = connection.prepareStatement(queryString);
+
+		// Bind record data
+
+		outStatement.setInt(1, rs.getInt("event_number"));
+		outStatement.setString(2, rs.getString("guild_id"));
+		outStatement.setString(3, rs.getString("guild_name"));
+		outStatement.setString(4, rs.getString("guild_motto"));
+		outStatement.setString(5, rs.getString("guild_founder"));
+		outStatement.setString(6, rs.getString("charter"));
+		outStatement.setInt(7, rs.getInt("bgicon"));
+		outStatement.setInt(8, rs.getInt("bgcoloura"));
+		outStatement.setInt(9, rs.getInt("bgcolourb"));
+		outStatement.setInt(10, rs.getInt("fgicon"));
+		outStatement.setInt(11, rs.getInt("fgcolour"));
+		outStatement.setString(12, rs.getString("eventtype"));
+		outStatement.setTimestamp(13, rs.getTimestamp("datetime"));
+
+		return outStatement;
+	}
+
+	public static PreparedStatement buildGuildQueryStatement(Connection connection) throws SQLException {
+
+		PreparedStatement outStatement = null;
+		String queryString = "SELECT * FROM `warehouse_guildhistory` WHERE `event_number` > ?";
+		outStatement = connection.prepareStatement(queryString);
+		outStatement.setInt(1, WarehousePushThread.guildIndex);
+		return outStatement;
+	}
+
+	void reset() {
+
+		this.guild = null;
+		this.guildHash = null;
+		this.GLHash = null;
+		this.guildMotto = null;
+		this.charterName = null;
+		this.eventDatetime = null;
+	}
+
+	public void release() {
+		this.reset();
+		recordPool.add(this);
+	}
+
+	public void write() {
+
+		try (Connection connection = DataWarehouse.connectionPool.getConnection();
+				PreparedStatement statement = this.buildGuildInsertStatement(connection)) {
+
+			statement.execute();
+
+		} catch (SQLException e) {
+			e.printStackTrace();
+		}
+
+	}
+
+	private PreparedStatement buildGuildInsertStatement(Connection connection) throws SQLException {
+
+		PreparedStatement outStatement = null;
+		String queryString = "INSERT INTO `warehouse_guildhistory` (`guild_id`, `guild_name`, `guild_motto`, `guild_founder`, `charter`, `bgicon`, `bgcoloura`, `bgcolourb`, `fgicon`, `fgcolour`, `eventtype`, `datetime`) VALUES(?,?,?,?,?,?,?,?,?,?,?,?)";
+
+		outStatement = connection.prepareStatement(queryString);
+
+		// Bind character data
+
+		outStatement.setString(1, this.guildHash);
+		outStatement.setString(2, this.guildName);
+		outStatement.setString(3, this.guildMotto);
+		outStatement.setString(4, this.GLHash);
+		outStatement.setString(5, this.charterName);
+
+		outStatement.setInt(6, this.bgIcon);
+		outStatement.setInt(7, this.bgColour1);
+		outStatement.setInt(8, this.bgColour2);
+		outStatement.setInt(9, this.fgIcon);
+		outStatement.setInt(10, this.fgColour);
+		outStatement.setString(11, this.eventType.name());
+		outStatement.setTimestamp(12, new java.sql.Timestamp(	this.eventDatetime.atZone(ZoneId.systemDefault())
+				.toInstant().toEpochMilli()));
+
+		return outStatement;
+	}
+	
+//	public static void InitializeGuildRecords(){
+//		GuildRecord.GuildRecordCache = DbManager.GuildQueries.GET_WAREHOUSE_GUILD_HISTORY();
+//	}
+}
+
+
+
+
+
+
+
diff --git a/src/engine/db/archive/MineRecord.java b/src/engine/db/archive/MineRecord.java
new file mode 100644
index 00000000..69621ba8
--- /dev/null
+++ b/src/engine/db/archive/MineRecord.java
@@ -0,0 +1,166 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.db.archive;
+
+import engine.Enum;
+import engine.objects.AbstractCharacter;
+import engine.objects.Mine;
+import engine.objects.PlayerCharacter;
+import engine.workthreads.WarehousePushThread;
+
+import java.sql.*;
+import java.time.LocalDateTime;
+import java.util.concurrent.LinkedBlockingQueue;
+
+public class MineRecord extends DataRecord {
+
+    private static final LinkedBlockingQueue<MineRecord> recordPool = new LinkedBlockingQueue<>();
+    private Enum.RecordEventType eventType;
+    private String zoneHash;
+    private String charHash;
+    private String mineGuildHash;
+    private String mineNationHash;
+    private String mineType;
+    private float locX;
+    private float locY;
+
+    private MineRecord() {
+        this.recordType = Enum.DataRecordType.MINE;
+        this.eventType = Enum.RecordEventType.CAPTURE;
+
+    }
+
+    public static MineRecord borrow(Mine mine, AbstractCharacter character, Enum.RecordEventType eventType) {
+
+        MineRecord mineRecord;
+        mineRecord = recordPool.poll();
+        PlayerCharacter player;
+
+        if (mineRecord == null) {
+            mineRecord = new MineRecord();
+            mineRecord.eventType = eventType;
+        }
+        else {
+            mineRecord.recordType = Enum.DataRecordType.MINE;
+            mineRecord.eventType = eventType;
+        }
+
+        mineRecord.zoneHash = mine.getParentZone().getHash();
+
+        if (character.getObjectType().equals(Enum.GameObjectType.PlayerCharacter)) {
+            player = (PlayerCharacter) character;
+            mineRecord.charHash = player.getHash();
+        }
+        else
+            mineRecord.charHash = character.getName();
+
+        DataWarehouse.hasher.encrypt(0);
+
+        if (mine.getOwningGuild() == null)
+            mineRecord.mineGuildHash = "ERRANT";
+        else
+            mineRecord.mineGuildHash = mine.getOwningGuild().getHash();
+
+        if (mine.getOwningGuild() == null)
+            mineRecord.mineNationHash = "ERRANT";
+        else
+            mineRecord.mineNationHash = mine.getOwningGuild().getNation().getHash();
+
+        mineRecord.locX = mine.getParentZone().getLoc().x;
+        mineRecord.locY = -mine.getParentZone().getLoc().z;
+
+        mineRecord.mineType = mine.getMineType().name;
+
+        return mineRecord;
+    }
+
+    public static PreparedStatement buildMinePushStatement(Connection connection, ResultSet rs) throws SQLException {
+
+        PreparedStatement outStatement = null;
+        String queryString = "INSERT INTO `warehouse_minehistory` (`event_number`, `zone_id`, `mine_type`, `char_id`, `mine_guildID`, `mine_nationID`, `loc_x`, `loc_y`, `eventType`, `datetime`) VALUES(?,?,?,?,?,?,?,?,?,?)";
+        outStatement = connection.prepareStatement(queryString);
+
+        // Bind record data
+
+        outStatement.setInt(1, rs.getInt("event_number"));
+        outStatement.setString(2, rs.getString("zone_id"));
+        outStatement.setString(3, rs.getString("char_id"));
+        outStatement.setString(4, rs.getString("mine_type"));
+        outStatement.setString(5, rs.getString("mine_guildID"));
+        outStatement.setString(6, rs.getString("mine_nationID"));
+
+        outStatement.setFloat(7, rs.getFloat("loc_x"));
+        outStatement.setFloat(8, rs.getFloat("loc_y"));
+        outStatement.setString(9, rs.getString("eventType"));
+        outStatement.setTimestamp(10, rs.getTimestamp("datetime"));
+
+        return outStatement;
+    }
+
+    public static PreparedStatement buildMineQueryStatement(Connection connection) throws SQLException {
+
+        PreparedStatement outStatement = null;
+        String queryString = "SELECT * FROM `warehouse_minehistory` WHERE `event_number` > ?";
+        outStatement = connection.prepareStatement(queryString);
+        outStatement.setInt(1, WarehousePushThread.mineIndex);
+        return outStatement;
+    }
+
+    void reset() {
+        this.zoneHash = null;
+        this.charHash = null;
+        this.mineGuildHash = null;
+        this.mineNationHash = null;
+        this.mineType = null;
+        this.locX = 0.0f;
+        this.locY = 0.0f;
+
+    }
+
+    public void release() {
+        this.reset();
+        recordPool.add(this);
+    }
+
+    public void write() {
+
+        try (Connection connection = DataWarehouse.connectionPool.getConnection();
+             PreparedStatement statement = this.buildMineInsertStatement(connection)) {
+
+            statement.execute();
+
+        } catch (SQLException e) {
+            e.printStackTrace();
+        }
+    }
+
+    private PreparedStatement buildMineInsertStatement(Connection connection) throws SQLException {
+
+        PreparedStatement outStatement = null;
+        String queryString = "INSERT INTO `warehouse_minehistory` (`zone_id`, `mine_type`, `char_id`, `mine_guildID`, `mine_nationID`, `loc_x`, `loc_y`, `eventType`, `datetime`) VALUES(?,?,?,?,?,?,?,?,?)";
+
+        outStatement = connection.prepareStatement(queryString);
+
+        // Bind character data
+
+        outStatement.setString(1, this.zoneHash);
+        outStatement.setString(2, this.mineType);
+        outStatement.setString(3, this.charHash);
+        outStatement.setString(4, this.mineGuildHash);
+        outStatement.setString(5, this.mineNationHash);
+
+        outStatement.setFloat(6, this.locX);
+        outStatement.setFloat(7, this.locY);
+        outStatement.setString(8, this.eventType.name());
+        outStatement.setTimestamp(9, Timestamp.valueOf(LocalDateTime.now()));
+
+        return outStatement;
+    }
+}
diff --git a/src/engine/db/archive/PvpRecord.java b/src/engine/db/archive/PvpRecord.java
new file mode 100644
index 00000000..a3199127
--- /dev/null
+++ b/src/engine/db/archive/PvpRecord.java
@@ -0,0 +1,312 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.db.archive;
+
+import engine.gameManager.ZoneManager;
+import engine.math.Vector3fImmutable;
+import engine.objects.Guild;
+import engine.objects.PlayerCharacter;
+import engine.objects.Zone;
+import engine.workthreads.WarehousePushThread;
+import org.pmw.tinylog.Logger;
+
+import java.sql.*;
+import java.time.LocalDateTime;
+import java.util.LinkedList;
+import java.util.concurrent.LinkedBlockingQueue;
+
+import static engine.Enum.DataRecordType;
+import static engine.Enum.PvpHistoryType;
+
+public class PvpRecord extends DataRecord {
+
+	private static final LinkedBlockingQueue<PvpRecord> recordPool = new LinkedBlockingQueue<>();
+
+	private PlayerCharacter player;
+	private PlayerCharacter victim;
+	private Vector3fImmutable location;
+	private boolean pvpExp;
+
+	private PvpRecord(PlayerCharacter player, PlayerCharacter victim, Vector3fImmutable location, boolean pvpExp) {
+		this.recordType = DataRecordType.PVP;
+		this.player = player;
+		this.victim = victim;
+		this.location = new Vector3fImmutable(location);
+		this.pvpExp = pvpExp;
+	}
+
+	public static PvpRecord borrow(PlayerCharacter player, PlayerCharacter victim, Vector3fImmutable location, boolean pvpExp) {
+
+		PvpRecord pvpRecord;
+
+		pvpRecord = recordPool.poll();
+
+		if (pvpRecord == null) {
+			pvpRecord = new PvpRecord(player, victim, location, pvpExp);
+		}
+		else {
+			pvpRecord.recordType = DataRecordType.PVP;
+			pvpRecord.player = player;
+			pvpRecord.victim = victim;
+			pvpRecord.location = new Vector3fImmutable(location);
+			pvpRecord.pvpExp = pvpExp;
+		}
+
+		return pvpRecord;
+	}
+
+	private static PreparedStatement buildHistoryStatement(Connection connection, int charUUID, PvpHistoryType historyType) throws SQLException {
+
+		PreparedStatement outStatement = null;
+		String queryString = "";
+
+		switch (historyType) {
+		case KILLS:
+			queryString = "SELECT DISTINCT `victim_id`, `datetime` FROM warehouse_pvphistory where char_id = ? " +
+					"ORDER BY `datetime` DESC LIMIT 10";
+			break;
+		case DEATHS:
+			queryString = "SELECT DISTINCT `char_id`,`datetime` FROM warehouse_pvphistory where `victim_id` = ? " +
+					"ORDER BY `datetime` DESC LIMIT 10";
+			break;
+		}
+
+		outStatement = connection.prepareStatement(queryString);
+		outStatement.setString(1, DataWarehouse.hasher.encrypt(charUUID));
+
+		return outStatement;
+	}
+
+	public static LinkedList<Integer> getCharacterPvPHistory(int charUUID, PvpHistoryType historyType) {
+
+		// Member variable declaration
+
+		LinkedList<Integer> outList = new LinkedList<>();
+
+		try (Connection connection = DataWarehouse.connectionPool.getConnection();
+				PreparedStatement statement = buildHistoryStatement(connection, charUUID, historyType);
+				ResultSet rs = statement.executeQuery()) {
+
+			while (rs.next()) {
+
+				switch (historyType) {
+				case KILLS:
+					outList.add((int) DataWarehouse.hasher.decrypt(rs.getString("victim_id"))[0]);
+					break;
+				case DEATHS:
+					outList.add((int) DataWarehouse.hasher.decrypt(rs.getString("char_id"))[0]);
+					break;
+				}
+			}
+		} catch (SQLException e) {
+			e.printStackTrace();
+		}
+		return outList;
+	}
+
+	private static PreparedStatement buildLuaHistoryQueryStatement(Connection connection, int charUUID) throws SQLException {
+
+		PreparedStatement outStatement = null;
+		String queryString = "CALL `pvpHistory`(?)";
+
+		outStatement = connection.prepareStatement(queryString);
+		outStatement.setString(1, DataWarehouse.hasher.encrypt(charUUID));
+
+		return outStatement;
+	}
+
+	public static String getPvpHistoryString(int charUUID) {
+
+		String outString;
+		String dividerString;
+
+		String newLine = System.getProperty("line.separator");
+
+		outString = "[LUA_PVP() DATA WAREHOUSE]" + newLine;
+		dividerString = "--------------------------------" + newLine;
+
+		try (Connection connection = DataWarehouse.connectionPool.getConnection();
+				PreparedStatement statement = buildLuaHistoryQueryStatement(connection, charUUID);
+				ResultSet rs = statement.executeQuery()) {
+
+			while (rs.next()) {
+
+				int killCount;
+				int deathCount;
+				float killRatio;
+
+				outString += "Total Magicbane murdered souls: " + rs.getInt("TOTALDEATHS") + newLine;
+				outString += dividerString;
+				outString += String.format("%-8s %-8s %-8s %-8s %n", "Period", "Kills", "Deaths", "K/D");
+				outString += dividerString;
+
+				killCount = rs.getInt("KILLCOUNT");
+				deathCount = rs.getInt("DEATHCOUNT");
+
+				if (deathCount == 0)
+					killRatio = (float) killCount;
+				else
+					killRatio = (float) killCount / deathCount;
+
+				try {
+					outString += String.format("%-8s %-8d %-8d %.2f %n", "Total", killCount, deathCount, killRatio);
+
+					killCount = rs.getInt("DAILYKILLS");
+					deathCount = rs.getInt("DAILYDEATHS");
+
+					if (deathCount == 0)
+						killRatio = (float) killCount;
+					else
+						killRatio = (float) killCount / deathCount;
+
+					outString += String.format("%-8s %-8d %-8d %.2f %n", "24hrs", killCount, deathCount, killRatio);
+
+					killCount = rs.getInt("HOURLYKILLS");
+					deathCount = rs.getInt("HOURLYDEATHS");
+
+					if (deathCount == 0)
+						killRatio = (float) killCount;
+					else
+						killRatio = (float) killCount / deathCount;
+
+					outString += String.format("%-8s %-8d %-8d %.2f %n", "1hr", killCount, deathCount, killRatio);
+				} catch (Exception e) {
+					Logger.error(e.toString());
+				}
+			}
+		} catch (SQLException e) {
+			e.printStackTrace();
+		}
+
+		return outString;
+	}
+
+	public static PreparedStatement buildPvpPushStatement(Connection connection, ResultSet rs) throws SQLException {
+
+		PreparedStatement outStatement = null;
+		String queryString = "INSERT INTO `warehouse_pvphistory` (`event_number`, `char_id`, `char_guild_id`, `char_nation_id`, `char_level`," +
+				" `victim_id`, `victim_guild_id`, `victim_nation_id`, `victim_level`," +
+				" `zone_id`, `zone_name`, `loc_x`, `loc_y`, `gave_exp`, `datetime`) " +
+				" VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)";
+
+		outStatement = connection.prepareStatement(queryString);
+
+		// Bind record data
+
+		outStatement.setInt(1, rs.getInt("event_number"));
+		outStatement.setString(2, rs.getString("char_id"));
+		outStatement.setString(3, rs.getString("char_guild_id"));
+		outStatement.setString(4, rs.getString("char_nation_id"));
+		outStatement.setInt(5, rs.getInt("char_level"));
+
+		// Bind victim data
+
+		outStatement.setString(6, rs.getString("victim_id"));
+		outStatement.setString(7, rs.getString("victim_guild_id"));
+		outStatement.setString(8, rs.getString("victim_nation_id"));
+		outStatement.setInt(9, rs.getInt("victim_level"));
+
+		outStatement.setString(10, rs.getString("zone_id"));
+		outStatement.setString(11, rs.getString("zone_name"));
+		outStatement.setFloat(12, rs.getFloat("loc_x"));
+		outStatement.setFloat(13, rs.getFloat("loc_y"));
+		outStatement.setBoolean(14, rs.getBoolean("gave_exp"));
+		outStatement.setTimestamp(15, rs.getTimestamp("datetime"));
+
+		return outStatement;
+	}
+
+	public static PreparedStatement buildPvpQueryStatement(Connection connection) throws SQLException {
+
+		PreparedStatement outStatement = null;
+		String queryString = "SELECT * FROM `warehouse_pvphistory` WHERE `event_number` > ?";
+		outStatement = connection.prepareStatement(queryString);
+		outStatement.setInt(1, WarehousePushThread.pvpIndex);
+		return outStatement;
+	}
+
+	void reset() {
+		this.player = null;
+		this.victim = null;
+		this.location = Vector3fImmutable.ZERO;
+		pvpExp = false;
+	}
+
+	public void release() {
+		this.reset();
+		recordPool.add(this);
+	}
+
+	private PreparedStatement buildPvPInsertStatement(Connection connection) throws SQLException {
+
+		Guild charGuild;
+		Guild victimGuild;
+		Zone zone;
+		PreparedStatement outStatement = null;
+
+		String queryString = "INSERT INTO `warehouse_pvphistory` (`char_id`, `char_guild_id`, `char_nation_id`, `char_level`," +
+				" `victim_id`, `victim_guild_id`, `victim_nation_id`, `victim_level`," +
+				" `zone_id`, `zone_name`, `loc_x`, `loc_y`, `gave_exp`, `datetime`) " +
+				" VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?)";
+
+		outStatement = connection.prepareStatement(queryString);
+
+		charGuild = this.player.getGuild();
+		victimGuild = this.victim.getGuild();
+
+		// Use a proxy in the situation where a char guild is null (errant)
+
+		
+		// Retrieve the zone name where the PvP event occurred
+
+		zone = ZoneManager.findSmallestZone(this.location);
+
+		outStatement.setString(1, DataWarehouse.hasher.encrypt(this.player.getObjectUUID()));
+		outStatement.setString(2, DataWarehouse.hasher.encrypt(charGuild.getObjectUUID()));
+		outStatement.setString(3, DataWarehouse.hasher.encrypt(charGuild.getNation().getObjectUUID()));
+		outStatement.setInt(4, this.player.getLevel());
+
+		// Bind victim data
+
+		outStatement.setString(5, DataWarehouse.hasher.encrypt(this.victim.getObjectUUID()));
+		outStatement.setString(6, DataWarehouse.hasher.encrypt(victimGuild.getObjectUUID()));
+		outStatement.setString(7, DataWarehouse.hasher.encrypt(victimGuild.getNation().getObjectUUID()));
+		outStatement.setInt(8, this.victim.getLevel());
+
+		outStatement.setString(9, DataWarehouse.hasher.encrypt(zone.getObjectUUID()));
+		outStatement.setString(10, zone.getName());
+		outStatement.setFloat(11, this.location.getX());
+		outStatement.setFloat(12, -this.location.getZ()); // flip sign on 'y' coordinate
+		outStatement.setBoolean(13, this.pvpExp);
+		outStatement.setTimestamp(14, Timestamp.valueOf(LocalDateTime.now()));
+
+		return outStatement;
+	}
+
+
+	public void write() {
+
+		try (Connection connection = DataWarehouse.connectionPool.getConnection();
+				PreparedStatement statement = buildPvPInsertStatement(connection)) {
+
+			statement.execute();
+
+		} catch (SQLException e) {
+			Logger.error( e.toString());
+		}
+
+		// Warehouse record for this pvp event written if code path reaches here.
+		// Time to update the respective kill counters.
+
+		CharacterRecord.advanceKillCounter(this.player);
+		CharacterRecord.advanceDeathCounter(this.victim);
+
+	}
+}
diff --git a/src/engine/db/archive/RealmRecord.java b/src/engine/db/archive/RealmRecord.java
new file mode 100644
index 00000000..556eebe2
--- /dev/null
+++ b/src/engine/db/archive/RealmRecord.java
@@ -0,0 +1,142 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.db.archive;
+
+import engine.Enum;
+import engine.objects.Realm;
+import engine.workthreads.WarehousePushThread;
+
+import java.sql.*;
+import java.time.LocalDateTime;
+import java.util.concurrent.LinkedBlockingQueue;
+
+public class RealmRecord extends DataRecord {
+
+    private static final LinkedBlockingQueue<RealmRecord> recordPool = new LinkedBlockingQueue<>();
+
+    private Realm realm;
+    private Enum.RecordEventType eventType;
+    private String cityHash;
+    private String guildHash;
+    private String charterType;
+    private LocalDateTime eventDateTime;
+
+    private RealmRecord(Realm realm) {
+        this.recordType = Enum.DataRecordType.REALM;
+        this.realm = realm;
+        this.eventType = Enum.RecordEventType.CAPTURE;
+
+    }
+
+    public static RealmRecord borrow(Realm realm, Enum.RecordEventType eventType) {
+        RealmRecord realmRecord;
+
+        realmRecord = recordPool.poll();
+
+        if (realmRecord == null) {
+            realmRecord = new RealmRecord(realm);
+            realmRecord.eventType = eventType;
+        }
+        else {
+            realmRecord.recordType = Enum.DataRecordType.REALM;
+            realmRecord.eventType = eventType;
+            realmRecord.realm = realm;
+
+        }
+
+        realmRecord.cityHash = realm.getRulingCity().getHash();
+        realmRecord.guildHash = realm.getRulingCity().getGuild().getHash();
+        realmRecord.charterType = Enum.CharterType.getCharterTypeByID(realmRecord.realm.getCharterType()).name();
+
+        if (realmRecord.eventType.equals(Enum.RecordEventType.CAPTURE))
+            realmRecord.eventDateTime =  realm.ruledSince;
+        else
+            realmRecord.eventDateTime = LocalDateTime.now();
+
+        return realmRecord;
+    }
+
+    public static PreparedStatement buildRealmPushStatement(Connection connection, ResultSet rs) throws SQLException {
+
+        PreparedStatement outStatement = null;
+        String queryString = "INSERT INTO `warehouse_realmhistory` (`event_number`, `realm_id`, `realm_name`, `charter`, `city_id`, `guild_id`, `eventType`, `datetime`) VALUES(?,?,?,?,?,?,?,?)";
+
+        outStatement = connection.prepareStatement(queryString);
+
+        // Bind record data
+
+        outStatement.setInt(1, rs.getInt("event_number"));
+        outStatement.setString(2, rs.getString("realm_id"));
+        outStatement.setString(3, rs.getString("realm_name"));
+        outStatement.setString(4, rs.getString("charter"));
+        outStatement.setString(5, rs.getString("city_id"));
+        outStatement.setString(6, rs.getString("guild_id"));
+        outStatement.setString(7, rs.getString("eventType"));
+        outStatement.setTimestamp(8, rs.getTimestamp("datetime"));
+
+        return outStatement;
+    }
+
+    public static PreparedStatement buildRealmQueryStatement(Connection connection) throws SQLException {
+
+        PreparedStatement outStatement = null;
+        String queryString = "SELECT * FROM `warehouse_realmhistory` WHERE `event_number` > ?";
+        outStatement = connection.prepareStatement(queryString);
+        outStatement.setInt(1, WarehousePushThread.realmIndex);
+        return outStatement;
+    }
+
+    void reset() {
+
+        this.realm = null;
+        this.cityHash = null;
+        this.guildHash = null;
+        this.eventDateTime = null;
+        this.charterType = null;
+    }
+
+    public void release() {
+        this.reset();
+        recordPool.add(this);
+    }
+
+    private PreparedStatement buildRealmInsertStatement(Connection connection) throws SQLException {
+
+        PreparedStatement outStatement = null;
+        String queryString = "INSERT INTO `warehouse_realmhistory` (`realm_id`, `realm_name`, `charter`, `city_id`, `guild_id`, `eventType`, `datetime`) VALUES(?,?,?,?,?,?,?)";
+        outStatement = connection.prepareStatement(queryString);
+
+        // Bind Record Data
+
+        outStatement.setString(1, realm.getHash());
+        outStatement.setString(2, realm.getRealmName());
+        outStatement.setString(3, charterType);
+        outStatement.setString(4, cityHash);
+        outStatement.setString(5, guildHash);
+        outStatement.setString(6, eventType.name());
+        outStatement.setTimestamp(7,  Timestamp.valueOf(this.eventDateTime));
+
+        return outStatement;
+    }
+
+    public void write() {
+
+        try (Connection connection = DataWarehouse.connectionPool.getConnection();
+             PreparedStatement statement = this.buildRealmInsertStatement(connection)) {
+
+            statement.execute();
+
+        } catch (SQLException e) {
+            e.printStackTrace();
+        }
+
+    }
+
+}
diff --git a/src/engine/db/handlers/dbAccountHandler.java b/src/engine/db/handlers/dbAccountHandler.java
new file mode 100644
index 00000000..ebf6048d
--- /dev/null
+++ b/src/engine/db/handlers/dbAccountHandler.java
@@ -0,0 +1,202 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.db.handlers;
+
+import engine.Enum;
+import engine.Enum.GameObjectType;
+import engine.gameManager.ConfigManager;
+import engine.gameManager.DbManager;
+import engine.objects.Account;
+import engine.objects.PlayerCharacter;
+import org.pmw.tinylog.Logger;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+
+public class dbAccountHandler extends dbHandlerBase {
+
+	public dbAccountHandler() {
+		this.localClass = Account.class;
+		this.localObjectType = Enum.GameObjectType.valueOf(this.localClass.getSimpleName());
+	}
+
+	public Account GET_ACCOUNT(int id) {
+		if (id == 0)
+			return null;
+		Account account = (Account) DbManager.getFromCache(GameObjectType.Account, id);
+		if (account != null)
+			return account;
+
+		prepareCallable("SELECT * FROM `obj_account` WHERE `UID`=?");
+		setLong(1, (long) id);
+
+		Account ac = null;
+		ac = (Account) getObjectSingle(id);
+
+		if (ac != null)
+			ac.runAfterLoad();
+
+		return ac;
+	}
+
+	public void SET_TRASH(String machineID) {
+
+		prepareCallable("INSERT INTO dyn_trash(`machineID`, `count`)"
+				+ " VALUES (?, 1) ON DUPLICATE KEY UPDATE `count` = `count` + 1;");
+
+		setString(1, machineID);
+		executeUpdate();
+
+	}
+
+	public ArrayList<String> GET_TRASH_LIST() {
+
+		ArrayList<String> machineList = new ArrayList<>();
+
+		prepareCallable("select `machineID` from `dyn_trash`");
+
+		try {
+			ResultSet rs = executeQuery();
+			while (rs.next()) {
+				machineList.add(rs.getString(1));
+			}
+		} catch (SQLException e) {
+			Logger.error( e);
+		} finally {
+			closeCallable();
+		}
+
+		return machineList;
+	}
+
+	public boolean DELETE_VAULT_FOR_ACCOUNT(final int accountID) {
+		prepareCallable("DELETE FROM `object` WHERE `parent`=? && `type`='item'");
+		setLong(1, (long) accountID);
+		return (executeUpdate() > 0);
+	}
+
+	public ArrayList<PlayerCharacter> GET_ALL_CHARS_FOR_MACHINE(String machineID) {
+
+		ArrayList<PlayerCharacter> trashList = new ArrayList<>();
+
+		prepareCallable("select DISTINCT UID from object \n" +
+				"where parent IN (select AccountID from dyn_login_history " +
+		        " WHERE`machineID`=?)");
+		setString(1, machineID);
+
+		try {
+			ResultSet rs = executeQuery();
+			while (rs.next()) {
+
+				PlayerCharacter trashPlayer;
+				int playerID;
+
+				playerID = rs.getInt(1);
+				trashPlayer = PlayerCharacter.getPlayerCharacter(playerID);
+
+				if (trashPlayer == null)
+					continue;;
+
+				if (trashPlayer.isDeleted() == false)
+					trashList.add(trashPlayer);
+			}
+		} catch (SQLException e) {
+			Logger.error( e);
+		} finally {
+			closeCallable();
+		}
+		return  trashList;
+	}
+
+	public void CLEAR_TRASH_TABLE() {
+		prepareCallable("DELETE FROM dyn_trash");
+		executeUpdate();
+	}
+
+	public Account GET_ACCOUNT(String uname) {
+
+		if (Account.AccountsMap.get(uname) != null)
+			return this.GET_ACCOUNT(Account.AccountsMap.get(uname));
+
+		prepareCallable("SELECT * FROM `obj_account` WHERE `acct_uname`=?");
+		setString(1, uname);
+		ArrayList<Account> temp = getObjectList();
+
+		if (temp.isEmpty())
+			return null;
+
+		if (temp.get(0) != null){
+			temp.get(0).runAfterLoad();
+
+			if (ConfigManager.serverType.equals(Enum.ServerType.LOGINSERVER))
+				Account.AccountsMap.put(uname, temp.get(0).getObjectUUID());
+
+		}
+		return temp.get(0);
+	}
+
+	public void SET_ACCOUNT_LOGIN(final Account acc, String playerName, final String ip, final String machineID) {
+
+		if (acc.getObjectUUID() == 0 || ip == null || ip.length() == 0)
+			return;
+
+		prepareCallable("INSERT INTO dyn_login_history(`AccountID`, `accountName`, `characterName`, `ip`, `machineID`, `timeStamp`)"
+				+ " VALUES (?, ?, ?, ?, ?, ?)");
+
+		setInt(1, acc.getObjectUUID());
+		setString(2, acc.getUname());
+		setString(3, playerName);
+		setString(4, ip);
+		setString(5, machineID);
+		setTimeStamp(6, System.currentTimeMillis());
+		executeUpdate();
+	}
+
+	public String SET_PROPERTY(final Account a, String name, Object new_value) {
+		prepareCallable("CALL account_SETPROP(?,?,?)");
+		setLong(1, (long) a.getObjectUUID());
+		setString(2, name);
+		setString(3, String.valueOf(new_value));
+		return getResult();
+	}
+
+	public String SET_PROPERTY(final Account a, String name, Object new_value, Object old_value) {
+		prepareCallable("CALL account_GETSETPROP(?,?,?,?)");
+		setLong(1, (long) a.getObjectUUID());
+		setString(2, name);
+		setString(3, String.valueOf(new_value));
+		setString(4, String.valueOf(old_value));
+		return getResult();
+	}
+
+
+	public void updateDatabase(final Account acc) {
+		prepareCallable("UPDATE `obj_account` SET `acct_passwd`=?, "
+				+ " `acct_lastCharUID`=?, `acct_salt`=?, `discordAccount`=?, " +
+				" status = ? WHERE `UID`=?");
+
+		setString(1, acc.getPasswd());
+		setInt(2, acc.getLastCharIDUsed());
+		setString(3, acc.getSalt());
+		setString(4, acc.discordAccount);
+		setString(5, acc.status.name());
+		setInt(6, acc.getObjectUUID());
+		executeUpdate();
+	}
+
+	public void INVALIDATE_LOGIN_CACHE(long accountUID, String objectType) {
+		prepareCallable("INSERT IGNORE INTO login_cachelist (`UID`, `type`) VALUES(?,?);");
+		setLong(1, accountUID);
+		setString(2, objectType);
+		executeUpdate();
+	}
+
+}
diff --git a/src/engine/db/handlers/dbBaneHandler.java b/src/engine/db/handlers/dbBaneHandler.java
new file mode 100644
index 00000000..2748095e
--- /dev/null
+++ b/src/engine/db/handlers/dbBaneHandler.java
@@ -0,0 +1,115 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.db.handlers;
+
+import engine.objects.Bane;
+import engine.objects.Building;
+import engine.objects.City;
+import engine.objects.PlayerCharacter;
+import org.joda.time.DateTime;
+import org.pmw.tinylog.Logger;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.logging.Level;
+
+public class dbBaneHandler extends dbHandlerBase {
+
+    public dbBaneHandler() {
+
+    }
+
+    public boolean CREATE_BANE(City city, PlayerCharacter owner, Building stone) {
+
+        prepareCallable("INSERT INTO `dyn_banes` (`cityUUID`, `ownerUUID`, `stoneUUID`, `placementDate`) VALUES(?,?,?,?)");
+        setLong(1, (long) city.getObjectUUID());
+        setLong(2, (long) owner.getObjectUUID());
+        setLong(3, (long) stone.getObjectUUID());
+        setTimeStamp(4, System.currentTimeMillis());
+
+        return (executeUpdate() > 0);
+
+    }
+
+    public Bane LOAD_BANE(int cityUUID) {
+
+        Bane newBane = null;
+
+        try {
+
+            prepareCallable("SELECT * from dyn_banes WHERE `dyn_banes`.`cityUUID` = ?");
+
+            setLong(1, (long) cityUUID);
+            ResultSet rs = executeQuery();
+
+            if (rs.next()) {
+                newBane = new Bane(rs);
+                Bane.addBane(newBane);
+            }
+
+        } catch (SQLException ex) {
+            java.util.logging.Logger.getLogger(dbBaneHandler.class.getName()).log(Level.SEVERE, null, ex);
+        } finally {
+            closeCallable();
+        }
+        return newBane;
+
+    }
+
+    public ConcurrentHashMap<Integer, Bane> LOAD_ALL_BANES() {
+
+        ConcurrentHashMap<Integer, Bane> baneList;
+        Bane thisBane;
+
+        baneList = new ConcurrentHashMap<>();
+
+        int recordsRead = 0;
+
+        prepareCallable("SELECT * FROM dyn_banes");
+
+        try {
+            ResultSet rs = executeQuery();
+
+            while (rs.next()) {
+
+                recordsRead++;
+                thisBane = new Bane(rs);
+                baneList.put(thisBane.getCityUUID(), thisBane);
+
+            }
+
+            Logger.info("read: " + recordsRead + " cached: " + baneList.size());
+
+        } catch (SQLException e) {
+            Logger.error( e.toString());
+        } finally {
+            closeCallable();
+        }
+        return baneList;
+    }
+
+    public boolean SET_BANE_TIME(DateTime toSet, int cityUUID) {
+        prepareCallable("UPDATE `dyn_banes` SET `liveDate`=? WHERE `cityUUID`=?");
+        setTimeStamp(1, toSet.getMillis());
+        setLong(2, cityUUID);
+        return (executeUpdate() > 0);
+    }
+
+    public boolean REMOVE_BANE(Bane bane) {
+
+        if (bane == null)
+            return false;
+
+        prepareCallable("DELETE FROM `dyn_banes` WHERE `cityUUID` = ?");
+        setLong(1, (long) bane.getCity().getObjectUUID());
+        return (executeUpdate() > 0);
+    }
+}
diff --git a/src/engine/db/handlers/dbBaseClassHandler.java b/src/engine/db/handlers/dbBaseClassHandler.java
new file mode 100644
index 00000000..3ae0f098
--- /dev/null
+++ b/src/engine/db/handlers/dbBaseClassHandler.java
@@ -0,0 +1,50 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.db.handlers;
+
+import engine.Enum;
+import engine.Enum.GameObjectType;
+import engine.gameManager.DbManager;
+import engine.objects.BaseClass;
+
+import java.util.ArrayList;
+
+public class dbBaseClassHandler extends dbHandlerBase {
+
+	public dbBaseClassHandler() {
+		this.localClass = BaseClass.class;
+		this.localObjectType = Enum.GameObjectType.BaseClass;
+	}
+
+	public BaseClass GET_BASE_CLASS(final int id) {
+
+		if (id == 0)
+			return null;
+		BaseClass baseClass = (BaseClass) DbManager.getFromCache(GameObjectType.BaseClass, id);
+		if (baseClass != null)
+			return baseClass;
+
+
+		prepareCallable("SELECT * FROM `static_rune_baseclass` WHERE `ID` = ?;");
+		setInt(1, id);
+		return (BaseClass) getObjectSingle(id);
+	}
+
+	public ArrayList<BaseClass> GET_BASECLASS_FOR_RACE(final int id) {
+		prepareCallable("SELECT b.* FROM `static_rune_baseclass` b, `static_rune_racebaseclass` r WHERE b.`ID` = r.`BaseClassID` && r.`RaceID` = ?");
+		setInt(1, id);
+		return getObjectList();
+	}
+
+	public ArrayList<BaseClass> GET_ALL_BASE_CLASSES(){
+		prepareCallable("SELECT * FROM `static_rune_baseclass`;");
+		return  getObjectList();
+	}
+}
diff --git a/src/engine/db/handlers/dbBlueprintHandler.java b/src/engine/db/handlers/dbBlueprintHandler.java
new file mode 100644
index 00000000..7ecd4dfe
--- /dev/null
+++ b/src/engine/db/handlers/dbBlueprintHandler.java
@@ -0,0 +1,86 @@
+package engine.db.handlers;
+
+import engine.objects.Blueprint;
+import org.pmw.tinylog.Logger;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.HashMap;
+
+public class dbBlueprintHandler extends dbHandlerBase {
+
+    public dbBlueprintHandler() {
+
+    }
+
+    public HashMap<Integer, Integer> LOAD_ALL_DOOR_NUMBERS() {
+
+        HashMap<Integer, Integer> doorInfo;
+        doorInfo = new HashMap<>();
+
+        int doorUUID;
+        int doorNum;
+        int recordsRead = 0;
+
+        prepareCallable("SELECT * FROM static_building_doors ORDER BY doorMeshUUID ASC");
+
+        try {
+            ResultSet rs = executeQuery();
+
+            while (rs.next()) {
+
+                recordsRead++;
+                doorUUID = rs.getInt("doorMeshUUID");
+                doorNum = rs.getInt("doorNumber");
+                doorInfo.put(doorUUID, doorNum);
+            }
+
+            Logger.info( "read: " + recordsRead + " cached: " + doorInfo.size());
+
+        } catch (SQLException e) {
+            Logger.error("LoadAllDoorNumbers: " + e.getErrorCode() + ' ' + e.getMessage(), e);
+        } finally {
+            closeCallable();
+        }
+        return doorInfo;
+    }
+
+    public HashMap<Integer, Blueprint> LOAD_ALL_BLUEPRINTS() {
+
+        HashMap<Integer, Blueprint> blueprints;
+        Blueprint thisBlueprint;
+
+        blueprints = new HashMap<>();
+        int recordsRead = 0;
+
+        prepareCallable("SELECT * FROM static_building_blueprint");
+
+        try {
+            ResultSet rs = executeQuery();
+
+            while (rs.next()) {
+
+                recordsRead++;
+                thisBlueprint = new Blueprint(rs);
+
+                blueprints.put(thisBlueprint.getBlueprintUUID(), thisBlueprint);
+
+                // load mesh cache
+                Blueprint._meshLookup.putIfAbsent(thisBlueprint.getMeshForRank(-1), thisBlueprint);
+                Blueprint._meshLookup.putIfAbsent(thisBlueprint.getMeshForRank(0), thisBlueprint);
+                Blueprint._meshLookup.putIfAbsent(thisBlueprint.getMeshForRank(1), thisBlueprint);
+                Blueprint._meshLookup.putIfAbsent(thisBlueprint.getMeshForRank(3), thisBlueprint);
+                Blueprint._meshLookup.putIfAbsent(thisBlueprint.getMeshForRank(7), thisBlueprint);
+
+            }
+
+            Logger.info( "read: " + recordsRead + " cached: " + blueprints.size());
+
+        } catch (SQLException e) {
+            Logger.error("LoadAllBlueprints: " + e.getErrorCode() + ' ' + e.getMessage(), e);
+        } finally {
+            closeCallable();
+        }
+        return blueprints;
+    }
+}
diff --git a/src/engine/db/handlers/dbBoonHandler.java b/src/engine/db/handlers/dbBoonHandler.java
new file mode 100644
index 00000000..ca7b6db1
--- /dev/null
+++ b/src/engine/db/handlers/dbBoonHandler.java
@@ -0,0 +1,51 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.db.handlers;
+
+import engine.objects.Boon;
+import org.pmw.tinylog.Logger;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+
+public class dbBoonHandler extends dbHandlerBase {
+
+	public dbBoonHandler() {
+	}
+	
+
+	public ArrayList<Boon>  GET_BOON_AMOUNTS_FOR_ITEMBASEUUID(int itemBaseUUID){
+        
+        ArrayList<Boon>boons = new ArrayList<>();
+        Boon thisBoon;
+        prepareCallable("SELECT * FROM `static_item_boons`  WHERE `itemBaseID` = ?");
+        setInt(1, itemBaseUUID);
+
+	try {
+		ResultSet rs = executeQuery();
+                    
+		while (rs.next()) {
+                        
+                     
+                      thisBoon = new Boon(rs);
+                      boons.add(thisBoon);
+		}
+                    
+  
+                            
+	} catch (SQLException e) {
+		Logger.error("GetBoonAmountsForItembaseUUID: " + e.getErrorCode() + ' ' + e.getMessage(), e);
+	} finally {
+		closeCallable();
+	}
+	return boons;
+}
+}
diff --git a/src/engine/db/handlers/dbBuildingHandler.java b/src/engine/db/handlers/dbBuildingHandler.java
new file mode 100644
index 00000000..68eb1ba0
--- /dev/null
+++ b/src/engine/db/handlers/dbBuildingHandler.java
@@ -0,0 +1,809 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.db.handlers;
+
+import engine.Enum;
+import engine.Enum.DbObjectType;
+import engine.Enum.ProtectionState;
+import engine.Enum.TaxType;
+import engine.gameManager.DbManager;
+import engine.math.Vector3fImmutable;
+import engine.objects.*;
+import engine.server.MBServerStatics;
+import org.joda.time.DateTime;
+import org.pmw.tinylog.Logger;
+
+import java.awt.geom.Line2D;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class dbBuildingHandler extends dbHandlerBase {
+
+	public dbBuildingHandler() {
+		this.localClass = Building.class;
+		this.localObjectType = Enum.GameObjectType.valueOf(this.localClass.getSimpleName());
+	}
+
+	public Building CREATE_BUILDING(Building b) {
+		try {
+			b = this.addBuilding(b);
+			b.setObjectTypeMask(MBServerStatics.MASK_BUILDING);
+		} catch (Exception e) {
+			Logger.error(e);
+			b = null;
+		}
+		return b;
+	}
+
+	/*
+	 *
+	 * @param worldServerID
+	 * @param OwnerUUID
+	 * @param name
+	 * @param meshUUID
+	 * @param meshScale
+	 * @param currentHP
+	 * @param protectionState
+	 * @param currentGold
+	 * @param rank
+	 * @param upgradeDate
+	 * @param blueprintUUID
+	 * @param w
+	 * @param rotY
+	 * @return
+	 */
+	public Building CREATE_BUILDING(int parentZoneID, int OwnerUUID, String name, int meshUUID,
+									Vector3fImmutable location, float meshScale, int currentHP,
+									ProtectionState protectionState, int currentGold, int rank,
+									DateTime upgradeDate, int blueprintUUID, float w, float rotY) {
+
+		Building toCreate = null;
+		try {
+
+			prepareCallable("CALL `building_CREATE`(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? ,? ,? ,?, ?);");
+
+			setInt(1, parentZoneID);
+			setInt(2, OwnerUUID);
+			setString(3, name);
+			setInt(4, meshUUID);
+			setFloat(5, location.x);
+			setFloat(6, location.y);
+			setFloat(7, location.z);
+			setFloat(8, meshScale);
+			setInt(9, currentHP);
+			setString(10, protectionState.name());
+			setInt(11, currentGold);
+			setInt(12, rank);
+
+			if (upgradeDate != null)
+				setTimeStamp(13, upgradeDate.getMillis());
+			else
+				setNULL(13, java.sql.Types.DATE);
+
+			setInt(14, blueprintUUID);
+			setFloat(15, w);
+			setFloat(16, rotY);
+
+			int objectUUID = (int) getUUID();
+
+			if (objectUUID > 0)
+				toCreate = GET_BUILDINGBYUUID(objectUUID);
+
+		} catch (Exception e) {
+			Logger.error("CREATE_BUILDING", e.getMessage());
+			return null;
+		}
+		return toCreate;
+
+	}
+
+	public boolean DELETE_FROM_DATABASE(final Building b) {
+
+		return removeFromBuildings(b);
+	}
+
+	public ArrayList<Building> GET_ALL_BUILDINGS_FOR_ZONE(Zone zone) {
+		prepareCallable("SELECT `obj_building`.*, `object`.`parent` FROM `object` INNER JOIN `obj_building` ON `obj_building`.`UID` = `object`.`UID` WHERE `object`.`parent` = ?;");
+		setLong(1, zone.getObjectUUID());
+		return getLargeObjectList();
+	}
+
+	public ArrayList<Building> GET_ALL_BUILDINGS() {
+		prepareCallable("SELECT `obj_building`.*, `object`.`parent` FROM `object` INNER JOIN `obj_building` ON `obj_building`.`UID` = `object`.`UID`;");
+		return getLargeObjectList();
+	}
+
+	public Building GET_BUILDINGBYUUID(int uuid) {
+		if (uuid == 0)
+			return null;
+
+		Building building = (Building) DbManager.getFromCache(Enum.GameObjectType.Building, uuid);
+
+		if (building != null)
+			return building;
+
+		prepareCallable("SELECT `obj_building`.*, `object`.`parent` FROM `object` INNER JOIN `obj_building` ON `obj_building`.`UID` = `object`.`UID` WHERE `object`.`UID` = ?;");
+
+		setLong(1, (long) uuid);
+		return (Building) getObjectSingle(uuid);
+
+	}
+
+	public Building GET_BUILDING_BY_MESH(final int meshID) {
+		Building toReturn = null;
+		prepareCallable("CALL building_GETBYMESH(?)");
+		setInt(1, meshID);
+		try {
+			ResultSet rs = executeQuery();
+			if (rs.next())
+				toReturn = new Building(rs);
+			rs.close();
+		} catch (SQLException e) {
+			Logger.error("Building", e);
+			return null;
+		} finally {
+			closeCallable();
+		}
+		return toReturn;
+	}
+
+	public String SET_PROPERTY(final Building b, String name, Object new_value) {
+		prepareCallable("CALL building_SETPROP(?,?,?)");
+		setLong(1, b.getObjectUUID());
+		setString(2, name);
+		setString(3, String.valueOf(new_value));
+		return getResult();
+	}
+
+	public String SET_PROPERTY(final Building b, String name, Object new_value, Object old_value) {
+		prepareCallable("CALL building_GETSETPROP(?,?,?,?)");
+		setLong(1, b.getObjectUUID());
+		setString(2, name);
+		setString(3, String.valueOf(new_value));
+		setString(4, String.valueOf(old_value));
+		return getResult();
+	}
+
+	public int MOVE_BUILDING(long buildingID, long parentID, float locX, float locY, float locZ) {
+		prepareCallable("UPDATE `object` INNER JOIN `obj_building` On `object`.`UID` = `obj_building`.`UID` SET `object`.`parent`=?, `obj_building`.`locationX`=?, `obj_building`.`locationY`=?, `obj_building`.`locationZ`=? WHERE `obj_building`.`UID`=?;");
+		setLong(1, parentID);
+		setFloat(2, locX);
+		setFloat(3, locY);
+		setFloat(4, locZ);
+		setLong(5, buildingID);
+		return executeUpdate();
+	}
+
+	private Building addBuilding(Building toAdd) {
+		prepareCallable("CALL `building_CREATE`(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);");
+		setLong(1, toAdd.getParentZoneID());
+		setLong(2, toAdd.getOwnerUUID());
+		setString(3, toAdd.getName());
+		setInt(4, toAdd.getMeshUUID());
+		setFloat(5, toAdd.getStatLat());
+		setFloat(6, toAdd.getStatAlt());
+		setFloat(7, toAdd.getStatLon());
+		setFloat(8, toAdd.getMeshScale().x);
+		setInt(9, (int) toAdd.getHealth());
+		setString(10, toAdd.getProtectionState().name());
+		setInt(11, toAdd.getStrongboxValue());
+		setInt(12, toAdd.getRank());
+
+		// Write Joda DateTime to database
+		// *** Refactor : Wrap this logic in our
+		// own override setDateTime() ?
+		if (toAdd.getUpgradeDateTime() != null)
+			setLocalDateTime(13, toAdd.getUpgradeDateTime());
+		else
+			setNULL(13, java.sql.Types.DATE);
+
+		setInt(14, toAdd.getBlueprintUUID());
+		setFloat(15, toAdd.getw());
+		setFloat(16, toAdd.getRot().y);
+
+		int objectUUID = (int) getUUID();
+
+		if (objectUUID > 0)
+			return GET_BUILDINGBYUUID(objectUUID);
+		return null;
+
+	}
+
+	private boolean removeFromBuildings(final Building b) {
+		prepareCallable("DELETE FROM `object` WHERE `UID` = ?");
+		setLong(1, b.getObjectUUID());
+		return (executeUpdate() > 0);
+	}
+
+	public boolean ClaimAsset(final long SetBuildingID, int newowner, int OldOwner) {
+
+		prepareCallable("UPDATE `obj_building` SET `ownerUUID`=? WHERE `UID`=? and `ownerUUID`= ?");
+		setInt(1, newowner);
+		setLong(2, SetBuildingID);
+		setLong(3, OldOwner);
+		return (executeUpdate() > 0);
+	}
+
+	public boolean CHANGE_NAME(Building b, String newName) {
+		prepareCallable("UPDATE `obj_building` SET `name`=? WHERE `UID`=?");
+		setString(1, newName);
+		setLong(2, b.getObjectUUID());
+		return (executeUpdate() > 0);
+	}
+
+	public boolean SET_RESERVE(Building b, int reserveAmount) {
+		prepareCallable("UPDATE `obj_building` SET `reserve`=? WHERE `UID`=?");
+		setInt(1, reserveAmount);
+		setLong(2, b.getObjectUUID());
+		return (executeUpdate() > 0);
+	}
+
+	//CAS update to rank
+	public boolean CHANGE_RANK(final long buildingID, int newRank) {
+		prepareCallable("UPDATE `obj_building` SET `rank`=? WHERE `UID`=?");
+		setInt(1, newRank);
+		setLong(2, buildingID);
+		return (executeUpdate() > 0);
+	}
+
+	public boolean UPDATE_BUILDING_HEALTH(final long buildingID, int NewHealth) {
+		prepareCallable("UPDATE `obj_building` SET `currentHP`=? WHERE `UID`=?");
+		setInt(1, NewHealth);
+		setLong(2, buildingID);
+		return (executeUpdate() > 0);
+	}
+
+	public boolean UPDATE_BUILDING_ALTITUDE(final long buildingID, float newAltitude) {
+		prepareCallable("UPDATE `obj_building` SET `locationY`=? WHERE `UID`=?");
+		setFloat(1, newAltitude);
+		setLong(2, buildingID);
+		return (executeUpdate() > 0);
+	}
+
+	public boolean UPDATE_PROTECTIONSTATE(final long buildingUUID, ProtectionState protectionState) {
+
+		try {
+			prepareCallable("UPDATE `obj_building` SET `protectionState`=? WHERE `UID`=?");
+			setString(1, protectionState.name());
+			setLong(2, buildingUUID);
+			return (executeUpdate() > 0);
+		} catch (Exception e) {
+			Logger.error(e.toString());
+			return false;
+		}
+	}
+
+	public boolean UPDATE_DOOR_LOCK(final int buildingUUID, int doorFlags) {
+
+		try {
+			prepareCallable("UPDATE obj_building SET doorState = ? WHERE UID = ?");
+
+			setInt(1, doorFlags);
+			setInt(2, buildingUUID);
+
+			executeUpdate();
+			return true;
+		} catch (Exception e) {
+			return false;
+		}
+	}
+
+	public boolean ADD_TO_FRIENDS_LIST(final long buildingID, final long friendID, final long guildID, final int friendType) {
+		prepareCallable("INSERT INTO `dyn_building_friends` (`buildingUID`, `playerUID`,`guildUID`, `friendType`) VALUES (?,?,?,?)");
+		setLong(1, buildingID);
+		setLong(2, friendID);
+		setLong(3, guildID);
+		setInt(4, friendType);
+		return (executeUpdate() > 0);
+	}
+
+	public boolean REMOVE_FROM_FRIENDS_LIST(final long buildingID, long friendID, long guildID, int friendType) {
+		prepareCallable("DELETE FROM `dyn_building_friends` WHERE `buildingUID`=? AND `playerUID`=? AND `guildUID` =? AND `friendType` = ?");
+		setLong(1, buildingID);
+		setLong(2, friendID);
+		setLong(3,guildID);
+		setInt(4, friendType);
+		return (executeUpdate() > 0);
+	}
+
+	public boolean REMOVE_FROM_CONDEMNED_LIST(final long buildingID, long friendID, long guildID, int friendType) {
+		prepareCallable("DELETE FROM `dyn_building_condemned` WHERE `buildingUID`=? AND `playerUID`=? AND `guildUID` =? AND `friendType` = ?");
+		setLong(1, buildingID);
+		setLong(2, friendID);
+		setLong(3,guildID);
+		setInt(4, friendType);
+		return (executeUpdate() > 0);
+	}
+
+	public void CLEAR_FRIENDS_LIST(final long buildingID) {
+		prepareCallable("DELETE FROM `dyn_building_friends` WHERE `buildingUID`=?");
+		setLong(1, buildingID);
+		executeUpdate();
+	}
+
+	public void CLEAR_CONDEMNED_LIST(final long buildingID) {
+		prepareCallable("DELETE FROM `dyn_building_condemned` WHERE `buildingUID`=?");
+		setLong(1, buildingID);
+		executeUpdate();
+	}
+
+	public boolean CLEAR_PATROL(final long buildingID) {
+		prepareCallable("DELETE FROM `dyn_building_patrol_points` WHERE `buildingUID`=?");
+		setLong(1, buildingID);
+		return (executeUpdate() > 0);
+	}
+
+	public void LOAD_ALL_FRIENDS_FOR_BUILDING(Building building) {
+
+		if (building == null)
+			return;
+
+		prepareCallable("SELECT * FROM `dyn_building_friends` WHERE `buildingUID` = ?");
+		setInt(1,building.getObjectUUID());
+
+		try {
+			ResultSet rs = executeQuery();
+
+			//shrines cached in rs for easy cache on creation.
+			while (rs.next()) {
+				BuildingFriends friend = new BuildingFriends(rs);
+				switch(friend.getFriendType()){
+				case 7:
+					building.getFriends().put(friend.getPlayerUID(), friend);
+					break;
+				case 8:
+				case 9:
+					building.getFriends().put(friend.getGuildUID(), friend);
+					break;
+				}
+			}
+
+		} catch (SQLException e) {
+			Logger.error("LOAD friends for building: " + e.getErrorCode() + ' ' + e.getMessage(), e);
+		} finally {
+			closeCallable();
+		}
+
+	}
+
+	public void LOAD_ALL_CONDEMNED_FOR_BUILDING(Building building) {
+
+		if (building == null)
+			return;
+
+		prepareCallable("SELECT * FROM `dyn_building_condemned` WHERE `buildingUID` = ?");
+		setInt(1,building.getObjectUUID());
+
+		try {
+			ResultSet rs = executeQuery();
+
+			//shrines cached in rs for easy cache on creation.
+			while (rs.next()) {
+				Condemned condemn = new Condemned(rs);
+				switch(condemn.getFriendType()){
+				case 2:
+					building.getCondemned().put(condemn.getPlayerUID(), condemn);
+					break;
+				case 4:
+				case 5:
+					building.getCondemned().put(condemn.getGuildUID(), condemn);
+					break;
+				}
+			}
+
+		} catch (SQLException e) {
+			Logger.error("LOAD Condemned for building: " + e.getErrorCode() + ' ' + e.getMessage(), e);
+		} finally {
+			closeCallable();
+		}
+
+	}
+
+	public ArrayList<Vector3fImmutable> LOAD_PATROL_POINTS(Building building) {
+		if (building == null)
+			return null;
+		ArrayList<Vector3fImmutable> patrolPoints = new ArrayList<>();
+
+
+		prepareCallable("SELECT * FROM `dyn_building_patrol_points` WHERE `buildingUID` = ?");
+		setInt(1,building.getObjectUUID());
+
+		try {
+			ResultSet rs = executeQuery();
+
+			//shrines cached in rs for easy cache on creation.
+			while (rs.next()) {
+				float x1 = rs.getFloat("patrolX");
+				float y1 = rs.getFloat("patrolY");
+				float z1 = rs.getFloat("patrolZ");
+				Vector3fImmutable patrolPoint = new Vector3fImmutable(x1,y1,z1);
+				patrolPoints.add(patrolPoint);
+			}
+
+		} catch (SQLException e) {
+			Logger.error("LOAD Patrol Points for building: " + e.getErrorCode() + ' ' + e.getMessage(), e);
+		} finally {
+			closeCallable();
+		}
+
+		return patrolPoints;
+
+	}
+
+	public boolean ADD_TO_CONDEMNLIST(final long parentUID, final long playerUID, final long guildID, final int friendType) {
+		prepareCallable("INSERT INTO `dyn_building_condemned` (`buildingUID`, `playerUID`,`guildUID`, `friendType`) VALUES (?,?,?,?)");
+		setLong(1, parentUID);
+		setLong(2, playerUID);
+		setLong(3, guildID);
+		setInt(4, friendType);
+		return (executeUpdate() > 0);
+	}
+
+	public boolean ADD_TO_PATROL(final long parentUID, final Vector3fImmutable patrolPoint) {
+		prepareCallable("INSERT INTO `dyn_building_patrol_points` (`buildingUID`, `patrolX`,`patrolY`, `patrolZ`) VALUES (?,?,?,?)");
+		setLong(1, parentUID);
+		setFloat(2, (int)patrolPoint.x);
+		setFloat(3, (int)patrolPoint.y);
+		setFloat(4, (int)patrolPoint.z);
+		return (executeUpdate() > 0);
+	}
+
+	public boolean ADD_TO_COLLIDE(final long parentUID, final float startX, final float startY, final float endX, final float endY) {
+		prepareCallable("INSERT INTO `static_building_colliders` (`MeshID`, `startX`,`startY`, `endX`, `endY`) VALUES (?,?,?,?,?)");
+		setLong(1, parentUID);
+		setFloat(2, startX);
+		setFloat(3, startY);
+		setFloat(4, endX);
+		setFloat(5, endY);
+		return (executeUpdate() > 0);
+	}
+
+	public ArrayList<Line2D.Float> GET_COLLIDERS(final long meshID) {
+		ArrayList<Line2D.Float> colliders = new ArrayList<>();
+		prepareCallable("SELECT * FROM `static_building_colliders` WHERE `MeshID`=? AND `doorID` =0");
+		setLong(1, meshID);
+		try {
+			ResultSet rs = executeQuery();
+			while (rs.next()) {
+				int startX = rs.getInt("startX");
+				int startY = rs.getInt("startY");
+				int endX = rs.getInt("endX");
+				int endY = rs.getInt("endY");
+				colliders.add(new Line2D.Float(startX,startY,endX,endY));
+			}
+
+			rs.close();
+		} catch (SQLException e) {
+			Logger.error("dbBuildingHandler.GET_COLLIDERS", e);
+		} finally {
+			closeCallable();
+		}
+		return colliders;
+	}
+
+	public ArrayList<Line2D.Float> GET_DOOR_COLLIDERS(final long meshID) {
+		ArrayList<Line2D.Float> colliders = new ArrayList<>();
+		prepareCallable("SELECT * FROM `static_building_colliders` WHERE `MeshID`=? AND `doorID` <> 0");
+		setLong(1, meshID);
+		try {
+			ResultSet rs = executeQuery();
+			while (rs.next()) {
+				int startX = rs.getInt("startX");
+				int startY = rs.getInt("startY");
+				int endX = rs.getInt("endX");
+				int endY = rs.getInt("endY");
+				colliders.add(new Line2D.Float(startX,startY,endX,endY));
+			}
+
+			rs.close();
+		} catch (SQLException e) {
+			Logger.error("dbBuildingHandler.GET_COLLIDERS", e);
+		} finally {
+			closeCallable();
+		}
+		return colliders;
+	}
+	public HashMap<Integer, ArrayList<BuildingRegions>> LOAD_BUILDING_REGIONS() {
+
+		HashMap<Integer, ArrayList<BuildingRegions>> regions;
+		BuildingRegions thisRegions;
+
+
+		regions = new HashMap<>();
+		int recordsRead = 0;
+
+		prepareCallable("SELECT * FROM static_building_regions");
+
+		try {
+			ResultSet rs = executeQuery();
+
+			while (rs.next()) {
+
+				recordsRead++;
+				thisRegions = new BuildingRegions(rs);
+				if (regions.get(thisRegions.getBuildingID()) == null){
+					ArrayList<BuildingRegions> regionsList = new ArrayList<>();
+					regionsList.add(thisRegions);
+					regions.put(thisRegions.getBuildingID(), regionsList);
+				}
+				else{
+					ArrayList<BuildingRegions>regionsList = regions.get(thisRegions.getBuildingID());
+					regionsList.add(thisRegions);
+					regions.put(thisRegions.getBuildingID(), regionsList);
+				}
+			}
+
+			Logger.info( "read: " + recordsRead + " cached: " + regions.size());
+
+		} catch (SQLException e) {
+			Logger.error(": " + e.getErrorCode() + ' ' + e.getMessage(), e);
+		} finally {
+			closeCallable();
+		}
+		return regions;
+	}
+	
+	public HashMap<Integer, MeshBounds> LOAD_MESH_BOUNDS() {
+
+		HashMap<Integer, MeshBounds> meshBoundsMap;
+		MeshBounds meshBounds;
+
+		meshBoundsMap = new HashMap<>();
+		int recordsRead = 0;
+
+		prepareCallable("SELECT * FROM static_mesh_bounds");
+
+		try {
+			ResultSet rs = executeQuery();
+
+			while (rs.next()) {
+
+				recordsRead++;
+				meshBounds = new MeshBounds(rs);
+				
+				meshBoundsMap.put(meshBounds.meshID, meshBounds);
+				
+			}
+
+			Logger.info( "read: " + recordsRead + " cached: " + meshBoundsMap.size());
+
+		} catch (SQLException e) {
+			Logger.error("LoadMeshBounds: " + e.getErrorCode() + ' ' + e.getMessage(), e);
+		} finally {
+			closeCallable();
+		}
+		return meshBoundsMap;
+	}
+
+	public HashMap<Integer, ArrayList<StaticColliders>> LOAD_ALL_STATIC_COLLIDERS() {
+
+		HashMap<Integer, ArrayList<StaticColliders>> colliders;
+		StaticColliders thisColliders;
+
+
+		colliders = new HashMap<>();
+		int recordsRead = 0;
+
+		prepareCallable("SELECT * FROM static_building_colliders");
+
+		try {
+			ResultSet rs = executeQuery();
+
+			while (rs.next()) {
+
+				recordsRead++;
+				thisColliders = new StaticColliders(rs);
+				if (colliders.get(thisColliders.getMeshID()) == null){
+					ArrayList<StaticColliders> colliderList = new ArrayList<>();
+					colliderList.add(thisColliders);
+					colliders.put(thisColliders.getMeshID(), colliderList);
+				}
+				else{
+					ArrayList<StaticColliders>colliderList = colliders.get(thisColliders.getMeshID());
+					colliderList.add(thisColliders);
+					colliders.put(thisColliders.getMeshID(), colliderList);
+				}
+
+
+			}
+
+			Logger.info( "read: " + recordsRead + " cached: " + colliders.size());
+
+		} catch (SQLException e) {
+			Logger.error("LoadAllBlueprints: " + e.getErrorCode() + ' ' + e.getMessage(), e);
+		} finally {
+			closeCallable();
+		}
+		return colliders;
+	}
+
+	// This public class inserted here as it's a generic utility function
+	// with no other good place for it.  If you find a good home for it
+	// feel free to move it.  -
+
+	public final DbObjectType GET_UID_ENUM(long object_UID) {
+
+		DbObjectType storedEnum = DbObjectType.INVALID;
+		String typeString;
+
+		if (object_UID == 0)
+			return storedEnum;
+
+		// Set up call to stored procedure
+		prepareCallable("CALL object_UID_ENUM(?)");
+		setLong(1, object_UID);
+
+		try {
+
+			// Evaluate database ordinal and return enum
+			storedEnum = DbObjectType.valueOf(getString("type").toUpperCase());
+
+		} catch (Exception e) {
+			storedEnum = DbObjectType.INVALID;
+			Logger.error("UID_ENUM ", "Orphaned Object? Lookup failed for UID: " + object_UID);
+		} finally {
+			closeCallable();
+		}
+		return storedEnum;
+	}
+
+	public ConcurrentHashMap<Integer, Integer> GET_FRIENDS(final long buildingID) {
+		ConcurrentHashMap<Integer, Integer> friendsList = new ConcurrentHashMap<>();
+		prepareCallable("SELECT * FROM `dyn_building_friends` WHERE `buildingUID`=?");
+		setLong(1, buildingID);
+		try {
+			ResultSet rs = executeQuery();
+			while (rs.next()) {
+				int friendType = rs.getInt("friendType");
+				switch (friendType) {
+				case 7:
+					friendsList.put(rs.getInt("playerUID"), 7);
+					break;
+				case 8:
+					friendsList.put(rs.getInt("guildUID"), 8);
+					break;
+				case 9:
+					friendsList.put(rs.getInt("guildUID"), 9);
+				}
+			}
+
+			rs.close();
+		} catch (SQLException e) {
+			Logger.error("dbBuildingHandler.GET_FRIENDS_GUILD_IC", e);
+		} finally {
+			closeCallable();
+		}
+		return friendsList;
+	}
+
+	public boolean updateBuildingRank(final Building b, int Rank) {
+
+		prepareCallable("UPDATE `obj_building` SET `rank`=?,"
+				+ "`upgradeDate`=?, `meshUUID`=?, `currentHP`=? "
+				+ "WHERE `UID` = ?");
+
+		setInt(1, Rank);
+		setNULL(2, java.sql.Types.DATE);
+		setInt(3, b.getBlueprint().getMeshForRank(Rank));
+		setInt(4, b.getBlueprint().getMaxHealth(Rank));
+		setInt(5, b.getObjectUUID());
+		return (executeUpdate() > 0);
+	}
+
+	public boolean updateReverseKOS(final Building b, boolean reverse) {
+
+		prepareCallable("UPDATE `obj_building` SET `reverseKOS`=? "
+				+ "WHERE `UID` = ?");
+		setBoolean(1, reverse);
+		setInt(2, b.getObjectUUID());
+		return (executeUpdate() > 0);
+	}
+
+	public boolean updateActiveCondemn(final Condemned condemn, boolean active) {
+
+		prepareCallable("UPDATE `dyn_building_condemned` SET `active`=? "
+				+ "WHERE`buildingUID` = ? AND `playerUID` = ? AND `guildUID` = ? AND `friendType` = ?");
+		setBoolean(1, active);
+		setInt(2, condemn.getParent());
+		setInt(3, condemn.getPlayerUID());
+		setInt(4, condemn.getGuildUID());
+		setInt(5, condemn.getFriendType());
+		return (executeUpdate() > 0);
+	}
+
+	public boolean updateBuildingOwner(final Building building, int ownerUUID) {
+
+		prepareCallable("UPDATE `obj_building` SET `ownerUUID`=? "
+				+ " WHERE `UID` = ?");
+
+		setInt(1, ownerUUID);
+		setInt(2, building.getObjectUUID());
+		return (executeUpdate() > 0);
+	}
+
+	public boolean updateBuildingUpgradeTime(LocalDateTime upgradeDateTime, Building toUpgrade, int costToUpgrade) {
+
+		prepareCallable("UPDATE obj_building SET upgradeDate=?, currentGold=? "
+				+ "WHERE UID = ?");
+
+		if (upgradeDateTime == null)
+			setNULL(1, java.sql.Types.DATE);
+		else
+			setTimeStamp(1, upgradeDateTime.atZone(ZoneId.systemDefault())
+					.toInstant().toEpochMilli());
+
+		setInt(2, toUpgrade.getStrongboxValue() - costToUpgrade);
+		setInt(3, toUpgrade.getObjectUUID());
+		return (executeUpdate() > 0);
+	}
+
+	public boolean updateMaintDate(Building building) {
+
+		prepareCallable("UPDATE obj_building SET maintDate=? "
+				+ "WHERE UID = ?");
+
+		if (building.maintDateTime == null)
+			setNULL(1, java.sql.Types.DATE);
+		else
+			setLocalDateTime(1, building.maintDateTime);
+
+		setInt(2, building.getObjectUUID());
+
+		return (executeUpdate() > 0);
+	}
+
+	public boolean addTaxes(Building building, TaxType taxType, int amount, boolean enforceKOS){
+		prepareCallable("UPDATE obj_building SET taxType=?, taxAmount = ?, enforceKOS = ? "
+				+ "WHERE UID = ?");
+
+		setString(1, taxType.name());
+		setInt(2, amount);
+		setBoolean(3, enforceKOS);
+		setInt(4, building.getObjectUUID());
+
+		return (executeUpdate() > 0);
+
+	}
+
+	public boolean removeTaxes(Building building){
+		prepareCallable("UPDATE obj_building SET taxType=?, taxAmount = ?, enforceKOS = ?, taxDate = ? "
+				+ "WHERE UID = ?");
+
+		setString(1, TaxType.NONE.name());
+		setInt(2, 0);
+		setBoolean(3, false);
+		setNULL(4, java.sql.Types.DATE);
+		setInt(5, building.getObjectUUID());
+
+
+
+		return (executeUpdate() > 0);
+
+	}
+
+	public boolean acceptTaxes(Building building) {
+
+		prepareCallable("UPDATE obj_building SET taxDate=? "
+				+ "WHERE UID = ?");
+
+		setTimeStamp(1, DateTime.now().plusDays(7).getMillis());
+		setInt(2, building.getObjectUUID());
+
+		return (executeUpdate() > 0);
+	}
+
+
+
+}
diff --git a/src/engine/db/handlers/dbBuildingLocationHandler.java b/src/engine/db/handlers/dbBuildingLocationHandler.java
new file mode 100644
index 00000000..c9dadd64
--- /dev/null
+++ b/src/engine/db/handlers/dbBuildingLocationHandler.java
@@ -0,0 +1,27 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.db.handlers;
+
+import engine.objects.BuildingLocation;
+
+import java.util.ArrayList;
+
+public class dbBuildingLocationHandler extends dbHandlerBase {
+
+	public dbBuildingLocationHandler() {
+		this.localClass = BuildingLocation.class;
+        this.localObjectType = engine.Enum.GameObjectType.valueOf(this.localClass.getSimpleName());
+	}
+
+	public ArrayList<BuildingLocation> LOAD_ALL_BUILDING_LOCATIONS() {
+		prepareCallable("SELECT * FROM `static_building_location`;");
+		return getObjectList();
+	}
+}
diff --git a/src/engine/db/handlers/dbCSSessionHandler.java b/src/engine/db/handlers/dbCSSessionHandler.java
new file mode 100644
index 00000000..d76aa810
--- /dev/null
+++ b/src/engine/db/handlers/dbCSSessionHandler.java
@@ -0,0 +1,100 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.db.handlers;
+
+import engine.gameManager.DbManager;
+import engine.objects.Account;
+import engine.objects.PlayerCharacter;
+import engine.session.CSSession;
+import engine.util.StringUtils;
+import org.pmw.tinylog.Logger;
+
+import java.net.InetAddress;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+public class dbCSSessionHandler extends dbHandlerBase {
+
+	public dbCSSessionHandler() {
+		this.localClass = CSSession.class;
+		this.localObjectType = engine.Enum.GameObjectType.valueOf(this.localClass.getSimpleName());
+	}
+
+	public boolean ADD_CSSESSION(String secKey, Account acc, InetAddress inet, String machineID) {
+        prepareCallable("INSERT INTO `dyn_session` (`secretKey`, `accountID`, `discordAccount`, `sessionIP`, machineID) VALUES (?,?,?,INET_ATON(?),?)");
+        setString(1, secKey);
+		setLong(2, acc.getObjectUUID());
+		setString(3, acc.discordAccount);
+        setString(4, StringUtils.InetAddressToClientString(inet));
+        setString(5, machineID);
+        return (executeUpdate() != 0);
+    }
+	// This method returns population metrics from the database
+
+	public String GET_POPULATION_STRING() {
+
+		String outString = null;
+
+		// Set up call to stored procedure
+		prepareCallable("CALL GET_POPULATION_STRING()");
+
+		try {
+
+			// Evaluate database ordinal and return enum
+			outString = getString("popstring");
+
+		} catch (Exception e) {
+			Logger.error( "Failure in stored procedure:" + e.getMessage());
+		} finally {
+			closeCallable();
+		}
+		return outString;
+	}
+
+	public boolean DELETE_UNUSED_CSSESSION(String secKey) {
+		prepareCallable("DELETE FROM `dyn_session` WHERE `secretKey`=? && `characterID` IS NULL");
+		setString(1, secKey);
+		return (executeUpdate() != 0);
+	}
+
+	public boolean DELETE_CSSESSION(String secKey) {
+		prepareCallable("DELETE FROM `dyn_session` WHERE `secretKey`=?");
+		setString(1, secKey);
+		return (executeUpdate() != 0);
+	}
+
+	public boolean UPDATE_CSSESSION(String secKey, int charID) {
+		prepareCallable("UPDATE `dyn_session` SET `characterID`=? WHERE `secretKey`=?");
+		setInt(1, charID);
+		setString(2, secKey);
+		return (executeUpdate() != 0);
+	}
+
+	public CSSession GET_CSSESSION(String secKey) {
+		CSSession css = null;
+		prepareCallable("SELECT `accountID`, `characterID`, `machineID` FROM `dyn_session` WHERE `secretKey`=?");
+		setString(1, secKey);
+		try {
+
+			ResultSet rs = executeQuery();
+
+			if (rs.next()) {
+				css = new CSSession(secKey, DbManager.AccountQueries.GET_ACCOUNT(rs.getInt("accountID")), PlayerCharacter.getPlayerCharacter(rs
+						.getInt("characterID")), getString("machineID"));
+			}
+			rs.close();
+		} catch (SQLException e) {
+			Logger.error("Error with seckey: " + secKey);
+		} finally {
+			closeCallable();
+		}
+		return css;
+	}
+}
diff --git a/src/engine/db/handlers/dbCharacterPowerHandler.java b/src/engine/db/handlers/dbCharacterPowerHandler.java
new file mode 100644
index 00000000..31a64f8a
--- /dev/null
+++ b/src/engine/db/handlers/dbCharacterPowerHandler.java
@@ -0,0 +1,113 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.db.handlers;
+
+import engine.Enum;
+import engine.gameManager.DbManager;
+import engine.objects.CharacterPower;
+import engine.objects.PlayerCharacter;
+import engine.server.MBServerStatics;
+import org.pmw.tinylog.Logger;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class dbCharacterPowerHandler extends dbHandlerBase {
+
+	public dbCharacterPowerHandler() {
+		this.localClass = CharacterPower.class;
+		this.localObjectType = Enum.GameObjectType.valueOf(this.localClass.getSimpleName());
+	}
+
+	public CharacterPower ADD_CHARACTER_POWER(CharacterPower toAdd) {
+		if (CharacterPower.getOwner(toAdd) == null || toAdd.getPower() == null) {
+			Logger.error("dbCharacterSkillHandler.ADD_Power", toAdd.getObjectUUID() + " missing owner or powersBase");
+			return null;
+		}
+
+		prepareCallable("INSERT INTO `dyn_character_power` (`CharacterID`, `powersBaseToken`, `trains`) VALUES (?, ?, ?);");
+		setLong(1, (long)CharacterPower.getOwner(toAdd).getObjectUUID());
+		setInt(2, toAdd.getPower().getToken());
+		setInt(3, toAdd.getTrains());
+		int powerID = insertGetUUID();
+		return GET_CHARACTER_POWER(powerID);
+
+	}
+
+	public int DELETE_CHARACTER_POWER(final int objectUUID) {
+		prepareCallable("DELETE FROM `dyn_character_power` WHERE `UID` = ?");
+		setLong(1, (long)objectUUID);
+		return executeUpdate();
+	}
+
+	public CharacterPower GET_CHARACTER_POWER(int objectUUID) {
+
+		CharacterPower cp = (CharacterPower) DbManager.getFromCache(Enum.GameObjectType.CharacterPower, objectUUID);
+		if (cp != null)
+			return cp;
+		prepareCallable("SELECT * FROM `dyn_character_power` WHERE `UID` = ?");
+		setLong(1, (long)objectUUID);
+		return (CharacterPower) getObjectSingle(objectUUID);
+	}
+
+	public ConcurrentHashMap<Integer, CharacterPower> GET_POWERS_FOR_CHARACTER(PlayerCharacter pc) {
+		ConcurrentHashMap<Integer, CharacterPower> powers = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW);
+		int objectUUID = pc.getObjectUUID();
+		prepareCallable("SELECT * FROM `dyn_character_power` WHERE CharacterID = ?");
+		setLong(1, (long)objectUUID);
+		ResultSet rs = executeQuery();
+		try {
+			while (rs.next()) {
+				CharacterPower cp = new CharacterPower(rs, pc);
+				if (cp.getPower() != null)
+					powers.put(cp.getPower().getToken(), cp);
+			}
+			rs.close();
+		} catch (SQLException e) {
+			Logger.error("CharacterPower.getCharacterPowerForCharacter", "Exception:" + e.getMessage());
+		} finally {
+			closeCallable();
+		}
+		return powers;
+	}
+
+	public void UPDATE_TRAINS(final CharacterPower pow) {
+		//skip update if nothing changed
+		if (!pow.isTrained())
+			return;
+
+		prepareCallable("UPDATE `dyn_character_power` SET `trains`=? WHERE `UID`=?");
+		setShort(1, (short)pow.getTrains());
+		setInt(2, pow.getObjectUUID());
+		executeUpdate();
+		pow.setTrained(false);
+	}
+
+	public void updateDatabase(final CharacterPower pow) {
+		if (pow.getPower() == null) {
+			Logger.error( "Failed to find powersBase for Power " + pow.getObjectUUID());
+			return;
+		}
+		if (CharacterPower.getOwner(pow) == null) {
+			Logger.error( "Failed to find owner for Power " + pow.getObjectUUID());
+			return;
+		}
+
+
+		prepareCallable("UPDATE `dyn_character_power` SET `PowersBaseToken`=?, `CharacterID`=?, `trains`=? WHERE `UID`=?");
+		setInt(1, pow.getPower().getToken());
+		setInt(2, CharacterPower.getOwner(pow).getObjectUUID());
+		setShort(3, (short)pow.getTrains());
+		setInt(4, pow.getObjectUUID());
+		executeUpdate();
+		pow.setTrained(false);
+	}
+}
diff --git a/src/engine/db/handlers/dbCharacterRuneHandler.java b/src/engine/db/handlers/dbCharacterRuneHandler.java
new file mode 100644
index 00000000..868774f8
--- /dev/null
+++ b/src/engine/db/handlers/dbCharacterRuneHandler.java
@@ -0,0 +1,63 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.db.handlers;
+
+import engine.Enum;
+import engine.gameManager.DbManager;
+import engine.objects.CharacterRune;
+
+import java.util.ArrayList;
+
+public class dbCharacterRuneHandler extends dbHandlerBase {
+
+	public dbCharacterRuneHandler() {
+		this.localClass = CharacterRune.class;
+		this.localObjectType = Enum.GameObjectType.valueOf(this.localClass.getSimpleName());
+	}
+
+	public CharacterRune ADD_CHARACTER_RUNE(final CharacterRune toAdd) {
+		prepareCallable("INSERT INTO `dyn_character_rune` (`CharacterID`, `RuneBaseID`) VALUES (?, ?);");
+		setLong(1, (long)toAdd.getPlayerID());
+		setInt(2, toAdd.getRuneBaseID());
+		int runeID = insertGetUUID();
+		return GET_CHARACTER_RUNE(runeID);
+	}
+
+	public CharacterRune GET_CHARACTER_RUNE(int runeID) {
+
+		CharacterRune charRune = (CharacterRune) DbManager.getFromCache(Enum.GameObjectType.CharacterRune, runeID);
+		if (charRune != null)
+			return charRune;
+		prepareCallable("SELECT * FROM `dyn_character_rune` WHERE `UID`=?");
+		setInt(1, runeID);
+		return (CharacterRune) getObjectSingle(runeID);
+	}
+
+
+	public boolean DELETE_CHARACTER_RUNE(final CharacterRune cr) {
+		prepareCallable("DELETE FROM `dyn_character_rune` WHERE `UID`=?;");
+		setLong(1, (long)cr.getObjectUUID());
+		return (executeUpdate() != 0);
+	}
+
+	public ArrayList<CharacterRune> GET_RUNES_FOR_CHARACTER(final int characterId) {
+		prepareCallable("SELECT * FROM `dyn_character_rune` WHERE `CharacterID` = ?");
+		setInt(1, characterId);
+		return getObjectList();
+	}
+
+	public void updateDatabase(final CharacterRune cr) {
+		prepareCallable("UPDATE `dyn_character_rune` SET `CharacterID`=?, `RuneBaseID`=? WHERE `UID` = ?");
+		setInt(1, cr.getPlayerID());
+		setInt(2, cr.getRuneBaseID());
+		setLong(3, (long) cr.getObjectUUID());
+		executeUpdate();
+	}
+}
diff --git a/src/engine/db/handlers/dbCharacterSkillHandler.java b/src/engine/db/handlers/dbCharacterSkillHandler.java
new file mode 100644
index 00000000..e4a93779
--- /dev/null
+++ b/src/engine/db/handlers/dbCharacterSkillHandler.java
@@ -0,0 +1,116 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.db.handlers;
+
+import engine.Enum;
+import engine.gameManager.DbManager;
+import engine.objects.AbstractCharacter;
+import engine.objects.CharacterSkill;
+import engine.objects.PlayerCharacter;
+import engine.server.MBServerStatics;
+import org.pmw.tinylog.Logger;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class dbCharacterSkillHandler extends dbHandlerBase {
+
+	public dbCharacterSkillHandler() {
+		this.localClass = CharacterSkill.class;
+		this.localObjectType = Enum.GameObjectType.valueOf(this.localClass.getSimpleName());
+	}
+
+	public CharacterSkill ADD_SKILL(CharacterSkill toAdd) {
+		if (CharacterSkill.GetOwner(toAdd) == null || toAdd.getSkillsBase() == null) {
+			Logger.error("dbCharacterSkillHandler.ADD_SKILL", toAdd.getObjectUUID() + " missing owner or skillsBase");
+			return null;
+		}
+
+		prepareCallable("INSERT INTO `dyn_character_skill` (`CharacterID`, `skillsBaseID`, `trains`) VALUES (?, ?, ?);");
+		setLong(1, (long)CharacterSkill.GetOwner(toAdd).getObjectUUID());
+		setInt(2, toAdd.getSkillsBase().getObjectUUID());
+		setInt(3, toAdd.getNumTrains());
+		int skillID = insertGetUUID();
+		return GET_SKILL(skillID);
+	}
+
+	public boolean DELETE_SKILL(final int objectUUID) {
+		prepareCallable("DELETE FROM `dyn_character_skill` WHERE `UID` = ?");
+		setLong(1, (long)objectUUID);
+		return (executeUpdate() != 0);
+	}
+
+	public CharacterSkill GET_SKILL(final int objectUUID) {
+		CharacterSkill skill = (CharacterSkill) DbManager.getFromCache(Enum.GameObjectType.CharacterSkill, objectUUID);
+		if (skill != null)
+			return skill;
+		prepareCallable("SELECT * FROM `dyn_character_skill` WHERE `UID` = ?");
+		setInt(1, objectUUID);
+		return (CharacterSkill) getObjectSingle(objectUUID);
+	}
+
+	public ConcurrentHashMap<String, CharacterSkill> GET_SKILLS_FOR_CHARACTER(final AbstractCharacter ac) {
+		ConcurrentHashMap<String, CharacterSkill> skills = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW);
+		if (ac == null || (!(ac.getObjectType().equals(Enum.GameObjectType.PlayerCharacter))))
+			return skills;
+		PlayerCharacter pc = (PlayerCharacter) ac;
+		int objectUUID = pc.getObjectUUID();
+
+		prepareCallable("SELECT * FROM `dyn_character_skill` WHERE `CharacterID` = ?");
+		setInt(1, objectUUID);
+		try {
+			ResultSet rs = executeQuery();
+			while (rs.next()) {
+				CharacterSkill cs = new CharacterSkill(rs, pc);
+				if (cs.getSkillsBase() != null)
+					skills.put(cs.getSkillsBase().getName(), cs);
+			}
+			rs.close();
+		} catch (SQLException e) {
+			Logger.error("CharacterSkill.getCharacterSkillForCharacter", e);
+		} finally {
+			closeCallable();
+		}
+		return skills;
+	}
+
+
+	public void UPDATE_TRAINS(final CharacterSkill cs) {
+		if (!cs.isTrained())
+			return;
+
+		prepareCallable("UPDATE `dyn_character_skill` SET `trains`=? WHERE `UID` = ?");
+		setShort(1, (short)cs.getNumTrains());
+		setLong(2, (long)cs.getObjectUUID());
+		if (executeUpdate() != 0)
+			cs.syncTrains();
+	}
+
+	public void updateDatabase(final CharacterSkill cs) {
+		if (cs.getSkillsBase() == null) {
+			Logger.error("Failed to find skillsBase for Skill " + cs.getObjectUUID());
+			return;
+		}
+		if (CharacterSkill.GetOwner(cs) == null) {
+			Logger.error("Failed to find owner for Skill " + cs.getObjectUUID());
+			return;
+		}
+
+		prepareCallable("UPDATE `dyn_character_skill` SET `skillsBaseID`=?, `CharacterID`=?, `trains`=? WHERE `UID`=?");
+		setInt(1, cs.getSkillsBase().getObjectUUID());
+		setInt(2, CharacterSkill.GetOwner(cs).getObjectUUID());
+		setShort(3, (short)cs.getNumTrains());
+		setLong(4, (long)cs.getObjectUUID());
+		if (executeUpdate() != 0)
+			cs.syncTrains();
+	}
+
+}
diff --git a/src/engine/db/handlers/dbCityHandler.java b/src/engine/db/handlers/dbCityHandler.java
new file mode 100644
index 00000000..13101ffe
--- /dev/null
+++ b/src/engine/db/handlers/dbCityHandler.java
@@ -0,0 +1,196 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.db.handlers;
+
+import engine.Enum;
+import engine.gameManager.DbManager;
+import engine.objects.AbstractGameObject;
+import engine.objects.Building;
+import engine.objects.City;
+import engine.objects.Zone;
+import org.pmw.tinylog.Logger;
+
+import java.net.UnknownHostException;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.time.LocalDateTime;
+import java.util.ArrayList;
+
+public class dbCityHandler extends dbHandlerBase {
+
+	public dbCityHandler() {
+		this.localClass = City.class;
+		this.localObjectType = Enum.GameObjectType.valueOf(this.localClass.getSimpleName());
+	}
+
+	public ArrayList<AbstractGameObject> CREATE_CITY(int ownerID, int parentZoneID, int realmID, float xCoord, float yCoord, float zCoord, float rotation, float W, String name, LocalDateTime established) {
+		prepareCallable("CALL `city_CREATE`(?, ?, ?, ?, ?, ?, ?, ?, ?,?,?)");
+		LocalDateTime upgradeTime = LocalDateTime.now().plusHours(2);
+		setLong(1, (long) ownerID); //objectUUID of owning player
+		setLong(2, (long) parentZoneID); //objectUUID of parent (continent) zone
+		setLong(3, (long) realmID); //objectUUID of realm city belongs in
+		setFloat(4, xCoord); //xOffset from parentZone center
+		setFloat(5, yCoord); //yOffset from parentZone center
+		setFloat(6, zCoord); //zOffset from parentZone center
+		setString(7, name); //city name
+		setLocalDateTime(8, established);
+		setFloat(9, rotation);
+		setFloat(10, W);
+		setLocalDateTime(11, upgradeTime);
+		ArrayList<AbstractGameObject> list = new ArrayList<>();
+
+		try {
+			boolean work = execute();
+			if (work) {
+				ResultSet rs = this.cs.get().getResultSet();
+				while (rs.next()) {
+					addObject(list, rs);
+				}
+				rs.close();
+			} else {
+				Logger.info("City Placement Failed: " + this.cs.get().toString());
+				return list; //city creation failure
+			}
+			while (this.cs.get().getMoreResults()) {
+				ResultSet rs = this.cs.get().getResultSet();
+				while (rs.next()) {
+					addObject(list, rs);
+				}
+				rs.close();
+			}
+		} catch (SQLException e) {
+			Logger.info("City Placement Failed, SQLException: " + this.cs.get().toString() + e.toString());
+			return list; //city creation failure
+		} catch (UnknownHostException e) {
+			Logger.info("City Placement Failed, UnknownHostException: " + this.cs.get().toString());
+			return list; //city creation failure
+		} finally {
+			closeCallable();
+		}
+		return list;
+	}
+
+	public static void addObject(ArrayList<AbstractGameObject> list, ResultSet rs) throws SQLException, UnknownHostException {
+		String type = rs.getString("type");
+		switch (type) {
+		case "zone":
+			Zone zone = new Zone(rs);
+			DbManager.addToCache(zone);
+			list.add(zone);
+			break;
+		case "building":
+			Building building = new Building(rs);
+			DbManager.addToCache(building);
+			list.add(building);
+			break;
+		case "city":
+			City city = new City(rs);
+			DbManager.addToCache(city);
+			list.add(city);
+			break;
+		}
+	}
+
+	public ArrayList<City> GET_CITIES_BY_ZONE(final int objectUUID) {
+		prepareCallable("SELECT `obj_city`.*, `object`.`parent` FROM `obj_city` INNER JOIN `object` ON `object`.`UID` = `obj_city`.`UID` WHERE `object`.`parent`=?;");
+		setLong(1, (long) objectUUID);
+
+        return getObjectList();
+	}
+
+	public City GET_CITY(final int cityId) {
+		City city = (City) DbManager.getFromCache(Enum.GameObjectType.City, cityId);
+		if (city != null)
+			return city;
+		prepareCallable("SELECT `obj_city`.*, `object`.`parent` FROM `obj_city` INNER JOIN `object` ON `object`.`UID` = `obj_city`.`UID` WHERE `object`.`UID`=?;");
+		setLong(1, (long) cityId);
+		city = (City) getObjectSingle(cityId);
+		return city;
+	}
+
+	public String SET_PROPERTY(final City c, String name, Object new_value) {
+		prepareCallable("CALL city_SETPROP(?,?,?)");
+		setLong(1, (long) c.getObjectUUID());
+		setString(2, name);
+		setString(3, String.valueOf(new_value));
+		return getResult();
+	}
+
+	public String SET_PROPERTY(final City c, String name, Object new_value, Object old_value) {
+		prepareCallable("CALL city_GETSETPROP(?,?,?,?)");
+		setLong(1, (long) c.getObjectUUID());
+		setString(2, name);
+		setString(3, String.valueOf(new_value));
+		setString(4, String.valueOf(old_value));
+		return getResult();
+	}
+
+	public boolean updateforceRename(City city, boolean value) {
+
+		prepareCallable("UPDATE `obj_city` SET `forceRename`=?"
+				+ " WHERE `UID` = ?");
+		setByte(1, (value == true) ? (byte) 1 : (byte) 0);
+		setInt(2, city.getObjectUUID());
+		return (executeUpdate() > 0);
+	}
+
+	public boolean updateOpenCity(City city, boolean value) {
+
+		prepareCallable("UPDATE `obj_city` SET `open`=?"
+				+ " WHERE `UID` = ?");
+		setByte(1, (value == true) ? (byte) 1 : (byte) 0);
+		setInt(2, city.getObjectUUID());
+		return (executeUpdate() > 0);
+	}
+
+	public boolean updateTOL(City city, int tolID) {
+
+		prepareCallable("UPDATE `obj_city` SET `treeOfLifeUUID`=?"
+				+ " WHERE `UID` = ?");
+		setInt(1,tolID);
+		setInt(2, city.getObjectUUID());
+		return (executeUpdate() > 0);
+	}
+
+	public boolean renameCity(City city, String name) {
+
+		prepareCallable("UPDATE `obj_city` SET `name`=?"
+				+ " WHERE `UID` = ?");
+		setString(1, name);
+		setInt(2, city.getObjectUUID());
+		return (executeUpdate() > 0);
+	}
+
+	public boolean updateSiegesWithstood(City city, int value) {
+
+		prepareCallable("UPDATE `obj_city` SET `siegesWithstood`=?"
+				+ " WHERE `UID` = ?");
+		setInt(1, value);
+		setInt(2, city.getObjectUUID());
+		return (executeUpdate() > 0);
+	}
+
+	public boolean updateRealmTaxDate(City city, LocalDateTime localDateTime) {
+
+		prepareCallable("UPDATE `obj_city` SET `realmTaxDate` =?"
+				+ " WHERE `UID` = ?");
+		setLocalDateTime(1, localDateTime);
+		setInt(2,city.getObjectUUID());
+		return (executeUpdate() > 0);
+	}
+
+	public boolean DELETE_CITY(final City city) {
+
+		prepareCallable("DELETE FROM `object` WHERE `UID` = ? AND `type` = 'city'");
+		setInt(1, city.getObjectUUID());
+		return (executeUpdate() != 0);
+	}
+
+}
diff --git a/src/engine/db/handlers/dbContractHandler.java b/src/engine/db/handlers/dbContractHandler.java
new file mode 100644
index 00000000..f8b41433
--- /dev/null
+++ b/src/engine/db/handlers/dbContractHandler.java
@@ -0,0 +1,157 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.db.handlers;
+
+import engine.Enum;
+import engine.gameManager.DbManager;
+import engine.objects.Contract;
+import engine.objects.ItemBase;
+import engine.objects.MobEquipment;
+import org.pmw.tinylog.Logger;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+
+public class dbContractHandler extends dbHandlerBase {
+
+	public dbContractHandler() {
+		this.localClass = Contract.class;
+		this.localObjectType = Enum.GameObjectType.valueOf(this.localClass.getSimpleName());
+	}
+
+	public Contract GET_CONTRACT(final int objectUUID) {
+		Contract contract = (Contract) DbManager.getFromCache(Enum.GameObjectType.Contract, objectUUID);
+		if (contract != null)
+			return contract;
+		if (objectUUID == 0)
+			return null;
+		prepareCallable("SELECT * FROM `static_npc_contract` WHERE `ID` = ?");
+		setInt(1, objectUUID);
+		return (Contract) getObjectSingle(objectUUID);
+	}
+
+	public ArrayList<Contract> GET_CONTRACT_BY_RACE(final int objectUUID) {
+
+		ArrayList<Contract> contracts = new ArrayList<>();
+
+		prepareCallable("SELECT * FROM static_npc_contract WHERE `mobbaseID` =?;");
+		setLong(1, objectUUID);
+
+		try {
+			ResultSet rs = executeQuery();
+
+			//shrines cached in rs for easy cache on creation.
+			while (rs.next()) {
+				Contract contract = new Contract(rs);
+				if (contract != null)
+					contracts.add(contract);
+			}
+
+		} catch (SQLException e) {
+			Logger.error( e.getErrorCode() + ' ' + e.getMessage(), e);
+		} finally {
+			closeCallable();
+		}
+		return contracts;
+	}
+
+	public void GET_GENERIC_INVENTORY(final Contract contract) {
+
+		prepareCallable("SELECT * FROM `static_npc_inventoryset` WHERE `inventorySet` = ?;");
+		setInt(1, contract.inventorySet);
+
+		try {
+			ResultSet rs = executeQuery();
+
+			while (rs.next()) {
+
+				//handle item base
+				int itemBaseID = rs.getInt("itembaseID");
+
+				ItemBase ib = ItemBase.getItemBase(itemBaseID);
+
+				if (ib != null) {
+
+					MobEquipment me = new MobEquipment(ib, 0, 0);
+					contract.getSellInventory().add(me);
+
+					//handle magic effects
+					String prefix = rs.getString("prefix");
+					int pRank = rs.getInt("pRank");
+					String suffix = rs.getString("suffix");
+					int sRank = rs.getInt("sRank");
+
+					if (prefix != null) {
+						me.setPrefix(prefix, pRank);
+						me.setIsID(true);
+					}
+
+					if (suffix != null) {
+						me.setSuffix(suffix, sRank);
+						me.setIsID(true);
+					}
+
+				}
+			}
+		} catch (SQLException e) {
+			Logger.error("SQL Error number: " + e.getErrorCode() + ' ' + e.getMessage());
+		} finally {
+			closeCallable();
+		}
+	}
+
+	public void GET_SELL_LISTS(final Contract con) {
+		prepareCallable("SELECT * FROM `static_npc_contract_selltype` WHERE `contractID` = ?;");
+		setInt(1, con.getObjectUUID());
+		try {
+			ResultSet rs = executeQuery();
+			ArrayList<Integer> item = con.getBuyItemType();
+			ArrayList<Integer> skill = con.getBuySkillToken();
+			ArrayList<Integer> unknown = con.getBuyUnknownToken();
+			while (rs.next()) {
+				int type = rs.getInt("type");
+				int value = rs.getInt("value");
+				if (type == 1) {
+					item.add(value);
+				} else if (type == 2) {
+					skill.add(value);
+				} else if (type == 3) {
+					unknown.add(value);
+				}
+			}
+		} catch (SQLException e) {
+			Logger.error("SQL Error number: " + e.getErrorCode() + ' ' + e.getMessage());
+		} finally {
+			closeCallable();
+		}
+	}
+
+	public boolean updateAllowedBuildings(final Contract con, final long slotbitvalue) {
+		prepareCallable("UPDATE `static_npc_contract` SET `allowedBuildingTypeID`=? WHERE `contractID`=?");
+		setLong(1, slotbitvalue);
+		setInt(2, con.getContractID());
+		return (executeUpdate() > 0);
+	}
+
+	public boolean updateDatabase(final Contract con) {
+		prepareCallable("UPDATE `static_npc_contract` SET `contractID`=?, `name`=?, "
+				+ "`mobbaseID`=?, `classID`=?, vendorDialog=?, iconID=?, allowedBuildingTypeID=? WHERE `ID`=?");
+		setInt(1, con.getContractID());
+		setString(2, con.getName());
+		setInt(3, con.getMobbaseID());
+		setInt(4, con.getClassID());
+		setInt(5, (con.getVendorDialog() != null) ? con.getVendorDialog().getObjectUUID() : 0);
+		setInt(6, con.getIconID());
+		setInt(8, con.getObjectUUID());
+		setLong(7, con.getAllowedBuildings().toLong());
+		return (executeUpdate() > 0);
+	}
+}
diff --git a/src/engine/db/handlers/dbEffectsBaseHandler.java b/src/engine/db/handlers/dbEffectsBaseHandler.java
new file mode 100644
index 00000000..9e360278
--- /dev/null
+++ b/src/engine/db/handlers/dbEffectsBaseHandler.java
@@ -0,0 +1,181 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.db.handlers;
+
+public class dbEffectsBaseHandler extends dbHandlerBase {
+
+	public dbEffectsBaseHandler() {
+
+	}
+
+
+
+	public boolean CreateEffectBase(int token, String IDString,String name,int flags){
+		prepareCallable("INSERT INTO `wpak_static_power_effectbase` (`token`,`IDString`,`name`,`flags`) VALUES (?,?,?,?)");
+		setInt(1,token);
+		setString(2,IDString);
+		setString(3,name);
+		setInt(4,flags);
+
+		return (executeUpdate() > 0);
+	}
+	
+	public boolean CreateEffectBaseRAW(String IDString,String type,String detail){
+		prepareCallable("INSERT INTO `wpak_effect_effectbase_raw` (`token`,`IDString`,`name`,`flags`) VALUES (?,?,?,?)");
+		setString(1,IDString);
+		setString(2,type);
+		setString(3,detail);
+
+		return (executeUpdate() > 0);
+	}
+
+	public boolean CreateEffectSource(String IDString,String source){
+		prepareCallable("INSERT INTO `wpak_static_power_sourcetype` (`IDString`,`source`) VALUES (?,?)");
+
+		setString(1,IDString);
+		setString(2,source);
+
+		return (executeUpdate() > 0);
+	}
+	
+	public boolean CreateEffectSourceRAW(String IDString,String type,String detail){
+		prepareCallable("INSERT INTO `wpak_effect_source_raw` (`effectID`,`type`, `text`) VALUES (?,?,?)");
+
+		setString(1,IDString);
+		setString(2,type);
+		setString(3,detail);
+
+		return (executeUpdate() > 0);
+	}
+
+	public boolean CreateEffectCondition(String IDString,String powerOrEffect,String type,float amount,float ramp,byte useAddFormula,String damageType1,String damageType2,String damageType3){
+		prepareCallable("INSERT INTO `wpak_static_power_failcondition` (`IDString`,`powerOrEffect`,`type`,`amount`,`ramp`,`useAddFormula`,`damageType1`,`damageType2`,`damageType3`) VALUES (?,?,?,?,?,?,?,?,?)");
+		setString(1,IDString);
+		setString(2,powerOrEffect);
+		setString(3,type);
+		setFloat(4,amount);
+		setFloat(5,ramp);
+		setByte(6,useAddFormula);
+		setString(7,damageType1);
+		setString(8,damageType2);
+		setString(9,damageType3);
+
+		return (executeUpdate() > 0);
+	}
+	
+	public boolean CreateEffectConditionRAW(String IDString,String type,String detail){
+		prepareCallable("INSERT INTO `wpak_effect_condition_raw` (`effectID`,`type`, `text`) VALUES (?,?,?)");
+		setString(1,IDString);
+		setString(2,type);
+		setString(3,detail);
+		return (executeUpdate() > 0);
+	}
+
+	public boolean CreateEffectMod(String IDString,String modType,float minMod,float maxMod,float percentMod,float ramp,byte useRampAdd,String type,String string1,String string2){
+		prepareCallable("INSERT INTO `wpak_static_power_effectmod` (`IDString`,`modType`,`minMod`,`maxMod`,`percentMod`,`ramp`,`useRampAdd`,`type`,`string1`,`string2`) VALUES (?,?,?,?,?,?,?,?,?,?)");
+		setString(1, IDString);
+		setString(2, modType);
+		setFloat(3, minMod);
+		setFloat(4, maxMod);
+		setFloat(5, percentMod);
+		setFloat(6, ramp);
+		setByte(7, useRampAdd);
+		setString(8, type);
+		setString(9, string1);
+		setString(10, string2);
+
+		return (executeUpdate() > 0);
+	}
+	
+	public boolean CreateEffectModRAW(String IDString,String type,String detail){
+		prepareCallable("INSERT INTO `wpak_effect_mod_raw` (`effectID`,`type`, `text`) VALUES (?,?,?)");
+		setString(1,IDString);
+		setString(2,type);
+		setString(3,detail);
+
+		return (executeUpdate() > 0);
+	}
+	
+
+	public boolean CreatePowerPowerAction(String IDString,String type,String effectID,String effectID2,String deferredPowerID,float levelCap,float levelCapRamp,String damageType,int numIterations,String effectSourceToRemove,String trackFilter,int maxTrack,int mobID,int mobLevel,int simpleDamage,String transferFromType,String transferToType,float transferAmount,float transferRamp,float transferEfficiency,float transferEfficiencyRamp,int flags){
+		prepareCallable("INSERT INTO `wpak_static_power_poweraction` (`IDString`,`type`,`effectID`,`effectID2`,`deferredPowerID`,`levelCap`,`levelCapRamp`,`damageType`,`numIterations`,`effectSourceToRemove`,`trackFilter`,`maxTrack`,`mobID`,`mobLevel`,`simpleDamage`,`transferFromType`,`transferToType`,`transferAmount`,`transferRamp`,`transferEfficiency`,`transferEfficiencyRamp`,`flags`) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)");
+
+		setString(1,IDString);
+		setString(2,type);
+		setString(3,effectID);
+		setString(4,effectID2);
+		setString(5,deferredPowerID);
+		setFloat(6,levelCap);
+		setFloat(7,levelCapRamp);
+		setString(8,damageType);
+		setInt(9,numIterations);
+		setString(10,effectSourceToRemove);
+		setString(11,trackFilter);
+		setInt(12,maxTrack);
+		setInt(13,mobID);
+		setInt(14,mobLevel);
+		setInt(15,simpleDamage);
+		setString(16,transferFromType);
+		setString(17,transferToType);
+		setFloat(18,transferAmount);
+		setFloat(19,transferRamp);
+		setFloat(20,transferEfficiency);
+		setFloat(21,transferEfficiencyRamp);
+		setInt(22,flags);
+
+		return (executeUpdate() > 0);
+	}
+	
+	public boolean CreatePowerPowerActionRAW(String IDString,String type,String detail){
+		prepareCallable("INSERT INTO `wpak_effect_poweraction_raw` (`effectID`,`type`, `text`) VALUES (?,?,?)");
+
+		setString(1,IDString);
+		setString(2,type);
+		setString(3,detail);
+
+		return (executeUpdate() > 0);
+	}
+
+	public boolean ClearAllEffectBase(){
+		prepareCallable("DELETE from `wpak_static_power_effectbase`");
+		executeUpdate();
+
+		prepareCallable(" DELETE from `wpak_static_power_sourcetype` ");
+		executeUpdate();
+
+		prepareCallable(" DELETE from `wpak_static_power_failcondition` WHERE `powerOrEffect` = ?");
+		setString(1,"Effect");
+		executeUpdate();
+
+		prepareCallable(" DELETE from `wpak_static_power_effectmod` ");
+		executeUpdate();
+
+		return true;
+
+	}
+
+	public boolean ResetIncrement(){
+		prepareCallable("ALTER TABLE `wpak_static_power_effectbase` AUTO_INCREMENT = 1");
+		executeUpdate();
+
+		prepareCallable("ALTER TABLE `wpak_static_power_sourcetype` AUTO_INCREMENT = 1");
+		executeUpdate();
+
+		prepareCallable("ALTER TABLE `wpak_static_power_failcondition` AUTO_INCREMENT = 1");
+		executeUpdate();
+
+		prepareCallable("ALTER TABLE `wpak_static_power_effectmod` AUTO_INCREMENT = 1");
+		executeUpdate();
+
+
+		return true;
+	}
+
+}
diff --git a/src/engine/db/handlers/dbEffectsResourceCostHandler.java b/src/engine/db/handlers/dbEffectsResourceCostHandler.java
new file mode 100644
index 00000000..cd86d0b6
--- /dev/null
+++ b/src/engine/db/handlers/dbEffectsResourceCostHandler.java
@@ -0,0 +1,30 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.db.handlers;
+
+import engine.objects.EffectsResourceCosts;
+
+import java.util.ArrayList;
+
+public class dbEffectsResourceCostHandler extends dbHandlerBase {
+
+	public dbEffectsResourceCostHandler() {
+		this.localClass = EffectsResourceCosts.class;
+        this.localObjectType = engine.Enum.GameObjectType.valueOf(this.localClass.getSimpleName());
+	}
+
+	
+	
+	public ArrayList<EffectsResourceCosts> GET_ALL_EFFECT_RESOURCES(String idString) {
+		prepareCallable("SELECT * FROM `static_power_effectcost`  WHERE `IDString` = ?");
+		setString(1, idString);
+		return getObjectList();
+	}
+}
diff --git a/src/engine/db/handlers/dbEnchantmentHandler.java b/src/engine/db/handlers/dbEnchantmentHandler.java
new file mode 100644
index 00000000..383f3d8c
--- /dev/null
+++ b/src/engine/db/handlers/dbEnchantmentHandler.java
@@ -0,0 +1,51 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.db.handlers;
+
+import engine.server.MBServerStatics;
+import org.pmw.tinylog.Logger;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class dbEnchantmentHandler extends dbHandlerBase {
+
+	public ConcurrentHashMap<String, Integer> GET_ENCHANTMENTS_FOR_ITEM(final int id) {
+		ConcurrentHashMap<String, Integer> enchants = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW);
+		prepareCallable("SELECT * FROM `dyn_item_enchantment` WHERE `ItemID`=?;");
+		setLong(1, (long)id);
+		try {
+			ResultSet resultSet = executeQuery();
+			while (resultSet.next())
+				enchants.put(resultSet.getString("powerAction"), resultSet.getInt("rank"));
+		} catch (SQLException e) {
+			Logger.error("SQL Error number: " + e.getErrorCode());
+		} finally {
+			closeCallable();
+		}
+		return enchants;
+	}
+
+	public boolean CREATE_ENCHANTMENT_FOR_ITEM(long itemID, String powerAction, int rank) {
+		prepareCallable("INSERT INTO `dyn_item_enchantment` (`itemID`, `powerAction`, `rank`) VALUES (?, ?, ?);");
+		setLong(1, itemID);
+		setString(2, powerAction);
+		setInt(3, rank);
+		return (executeUpdate() != 0);
+	}
+
+	public boolean CLEAR_ENCHANTMENTS(long itemID) {
+		prepareCallable("DELETE FROM `dyn_item_enchantment` WHERE `itemID`=?;");
+		setLong(1, itemID);
+		return (executeUpdate() != 0);
+	}
+
+}
diff --git a/src/engine/db/handlers/dbGuildHandler.java b/src/engine/db/handlers/dbGuildHandler.java
new file mode 100644
index 00000000..43d43593
--- /dev/null
+++ b/src/engine/db/handlers/dbGuildHandler.java
@@ -0,0 +1,486 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.db.handlers;
+
+import engine.Enum;
+import engine.Enum.GuildHistoryType;
+import engine.gameManager.DbManager;
+import engine.objects.*;
+import engine.server.MBServerStatics;
+import org.joda.time.DateTime;
+import org.pmw.tinylog.Logger;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+
+public class dbGuildHandler extends dbHandlerBase {
+
+	public dbGuildHandler() {
+		this.localClass = Guild.class;
+		this.localObjectType = engine.Enum.GameObjectType.valueOf(this.localClass.getSimpleName());
+	}
+
+	public int BANISH_FROM_GUILD_OFFLINE(final int target, boolean sourceIsGuildLeader) {
+		if (!sourceIsGuildLeader)  //one IC cannot banish another IC
+			prepareCallable("UPDATE `obj_character` SET `guildUID`=NULL, `guild_isInnerCouncil`=0, `guild_isTaxCollector`=0,"
+					+ " `guild_isRecruiter`=0, `guild_isFullMember`=0, `guild_title`=0 WHERE `UID`=? && `guild_isInnerCouncil`=0");
+		else
+			prepareCallable("UPDATE `obj_character` SET `guildUID`=NULL, `guild_isInnerCouncil`=0, `guild_isTaxCollector`=0,"
+					+ " `guild_isRecruiter`=0, `guild_isFullMember`=0, `guild_title`=0 WHERE `UID`=?");
+		setLong(1, (long) target);
+		return executeUpdate();
+	}
+
+
+
+	public boolean ADD_TO_BANISHED_FROM_GUILDLIST(int target, long characterID) {
+		prepareCallable("INSERT INTO  `dyn_guild_banishlist` (`GuildID`, `CharacterID`) VALUES (?,?)");
+		setLong(1, (long) target);
+		setLong(2, characterID);
+		return (executeUpdate() > 0);
+	}
+
+	public boolean REMOVE_FROM_BANISH_LIST(int target, long characterID) {
+		prepareCallable("DELETE FROM `dyn_guild_banishlist` (`GuildID`, `CharacterID`) VALUES (?,?)");
+		setLong(1, (long) target);
+		setLong(2, characterID);
+		return (executeUpdate() > 0);
+	}
+
+	public boolean ADD_TO_GUILDHISTORY(int target, PlayerCharacter pc, DateTime historyDate, GuildHistoryType historyType) {
+		prepareCallable("INSERT INTO  `dyn_character_guildhistory` (`GuildID`, `CharacterID`, `historyDate`, `historyType`) VALUES (?,?,?,?)");
+		setLong(1, (long) target);
+		setLong(2, pc.getObjectUUID());
+
+		if (historyDate == null)
+			setNULL(3, java.sql.Types.DATE);
+		else
+			setTimeStamp(3, historyDate.getMillis());
+		setString(4,historyType.name());
+		return (executeUpdate() > 0);
+	}
+
+	//TODO Need to get this working.
+	public ArrayList<Guild> GET_GUILD_HISTORY_OF_PLAYER(final int id) {
+		prepareCallable("SELECT g.* FROM `obj_guild` g, `dyn_character_guildhistory` l WHERE  g.`UID` = l.`GuildID` && l.`CharacterID` = ?");
+		setLong(1, (long) id);
+		return getObjectList();
+	}
+
+	public String GET_GUILD_LIST(int guildType) {
+
+		String newLine = System.getProperty("line.separator");
+		String outputStr = null;
+		ResultSet resultSet;
+
+		// Setup and execute stored procedure
+
+		prepareCallable("CALL `guild_GETLIST`(?)");
+		setInt(1, guildType);
+		resultSet = executeQuery();
+
+		// Build formatted string with data from query
+
+		outputStr += newLine;
+		outputStr += String.format("%-10s %-30s %-10s %-10s", "UUID", "Name", "GL UUID", "TOL_UUID");
+		outputStr += newLine;
+
+		try {
+
+			while (resultSet.next()) {
+
+				outputStr += String.format("%-10d %-30s %-10d %-10d", resultSet.getInt(1),
+						resultSet.getString(2), resultSet.getInt(3), resultSet.getInt(4));
+				outputStr += newLine;
+
+			}
+
+			// Exception handling
+
+		} catch (SQLException e) {
+			Logger.error( e.getMessage());
+		} finally {
+			closeCallable();
+		}
+
+		return outputStr;
+	}
+
+
+
+
+	public ArrayList<Guild> GET_GUILD_ALLIES(final int id) {
+		prepareCallable("SELECT g.* FROM `obj_guild` g, `dyn_guild_allianceenemylist` l "
+				+ "WHERE l.isAlliance = 1 && l.OtherGuildID = g.UID && l.GuildID=?");
+		setLong(1, (long) id);
+		return getObjectList();
+
+	}
+
+	public static ArrayList<PlayerCharacter> GET_GUILD_BANISHED(final int id) {
+
+		return new ArrayList<>();
+
+		// Bugfix
+		// prepareCallable("SELECT * FROM `obj_character`, `dyn_guild_banishlist` WHERE `obj_character.char_isActive` = 1 AND `dyn_guild_banishlist.CharacterID` = `obj_character.UID` AND `obj_character.GuildID`=?");
+
+		//prepareCallable("SELECT * FROM `obj_character` `,` `dyn_guild_banishlist` WHERE obj_character.char_isActive = 1 AND dyn_guild_banishlist.CharacterID = obj_character.UID AND dyn_guild_banishlist.GuildID = ?");
+		//setLong(1, (long) id);
+
+		//return getObjectList();
+	}
+
+	public ArrayList<Guild> GET_GUILD_ENEMIES(final int id) {
+		prepareCallable("SELECT g.* FROM `obj_guild` g, `dyn_guild_allianceenemylist` l "
+				+ "WHERE l.isAlliance = 0 && l.OtherGuildID = g.UID && l.GuildID=?");
+		setLong(1, (long) id);
+		return getObjectList();
+	}
+
+	public ArrayList<PlayerCharacter> GET_GUILD_KOS_CHARACTER(final int id) {
+		prepareCallable("SELECT c.* FROM `obj_character` c, `dyn_guild_characterkoslist` l WHERE c.`char_isActive` = 1 && l.`KOSCharacterID` = c.`UID` && l.`GuildID`=?");
+		setLong(1, (long) id);
+		return getObjectList();
+	}
+
+	public ArrayList<Guild> GET_GUILD_KOS_GUILD(final int id) {
+		prepareCallable("SELECT g.* FROM `obj_guild` g, `dyn_guild_guildkoslist` l "
+				+ "WHERE l.KOSGuildID = g.UID && l.GuildID = ?");
+		setLong(1, (long) id);
+		return getObjectList();
+	}
+
+	public ArrayList<Guild> GET_SUB_GUILDS(final int guildID) {
+		prepareCallable("SELECT `obj_guild`.*, `object`.`parent` FROM `object` INNER JOIN `obj_guild` ON `obj_guild`.`UID` = `object`.`UID` WHERE `object`.`parent` = ?;");
+		setInt(1, guildID);
+		return getObjectList();
+	}
+
+
+	public Guild GET_GUILD(int id) {
+		Guild guild = (Guild) DbManager.getFromCache(Enum.GameObjectType.Guild, id);
+		if (guild != null)
+			return guild;
+		if (id == 0)
+			return Guild.getErrantGuild();
+		prepareCallable("SELECT `obj_guild`.*, `object`.`parent` FROM `obj_guild` INNER JOIN `object` ON `object`.`UID` = `obj_guild`.`UID` WHERE `object`.`UID`=?");
+		setLong(1, (long) id);
+		return (Guild) getObjectSingle(id);
+	}
+	
+	public ArrayList<Guild> GET_ALL_GUILDS() {
+		
+		prepareCallable("SELECT `obj_guild`.*, `object`.`parent` FROM `obj_guild` INNER JOIN `object` ON `object`.`UID` = `obj_guild`.`UID`");
+		
+		return getObjectList();
+	}
+
+	public boolean IS_CREST_UNIQUE(final GuildTag gt) {
+		boolean valid = false;
+		if (gt.backgroundColor01 == gt.backgroundColor02) {
+			//both background colors the same, ignore backgroundDesign
+			prepareCallable("SELECT `name` FROM `obj_guild` WHERE `backgroundColor01`=? && `backgroundColor02`=? && `symbolColor`=? && `symbol`=?;");
+			setInt(1, gt.backgroundColor01);
+			setInt(2, gt.backgroundColor02);
+			setInt(3, gt.symbolColor);
+			setInt(4, gt.symbol);
+			
+		} else {
+			prepareCallable("SELECT `name` FROM `obj_guild` WHERE `backgroundColor01`=? && `backgroundColor02`=? && `symbolColor`=? && `backgroundDesign`=? && `symbol`=?;");
+			setInt(1, gt.backgroundColor01);
+			setInt(2, gt.backgroundColor02);
+			setInt(3, gt.symbolColor);
+			setInt(4, gt.backgroundDesign);
+			setInt(5, gt.symbol);
+		}
+		try {
+			ResultSet rs = executeQuery();
+			if (!rs.next())
+				valid = true;
+			rs.close();
+		} catch (SQLException e) {
+			Logger.error(e.getMessage());
+		}
+		return valid;
+	}
+
+	public String SET_PROPERTY(final Guild g, String name, Object new_value) {
+		prepareCallable("CALL guild_SETPROP(?,?,?)");
+		setLong(1, (long) g.getObjectUUID());
+		setString(2, name);
+		setString(3, String.valueOf(new_value));
+		return getResult();
+	}
+
+	public String SET_PROPERTY(final Guild g, String name, Object new_value, Object old_value) {
+		prepareCallable("CALL guild_GETSETPROP(?,?,?,?)");
+		setLong(1, (long) g.getObjectUUID());
+		setString(2, name);
+		setString(3, String.valueOf(new_value));
+		setString(4, String.valueOf(old_value));
+		return getResult();
+	}
+
+	public boolean SET_GUILD_OWNED_CITY(int guildID, int cityID) {
+		prepareCallable("UPDATE `obj_guild` SET `ownedCity`=? WHERE `UID`=?");
+		setLong(1, (long) cityID);
+		setLong(2, (long) guildID);
+		return (executeUpdate() > 0);
+	}
+
+	public boolean SET_GUILD_LEADER(int objectUUID,int guildID) {
+		prepareCallable("UPDATE `obj_guild` SET `leaderUID`=? WHERE `UID`=?");
+		setLong(1, (long) objectUUID);
+		setLong(2, (long) guildID);
+		return (executeUpdate() > 0);
+	}
+
+
+	public boolean IS_NAME_UNIQUE(final String name) {
+		boolean valid = false;
+		prepareCallable("SELECT `name` FROM `obj_guild` WHERE `name`=?;");
+		setString(1, name);
+		try {
+			ResultSet rs = executeQuery();
+			if (!rs.next())
+				valid = true;
+			rs.close();
+		} catch (SQLException e) {
+			Logger.warn(e.getMessage());
+		}
+		return valid;
+
+	}
+
+	public Guild SAVE_TO_DATABASE(Guild g) {
+		prepareCallable("CALL `guild_CREATE`(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
+	
+		GuildTag gt = g.getGuildTag();
+		if ( gt == null)
+			return null;
+		setLong(1, MBServerStatics.worldUUID);
+		setLong(2, g.getGuildLeaderUUID());
+		setString(3, g.getName());
+		setInt(4, gt.backgroundColor01);
+		setInt(5, gt.backgroundColor02);
+		setInt(6, gt.symbolColor);
+		setInt(7, gt.backgroundDesign);
+		setInt(8 , gt.symbol);
+		setInt(9, g.getCharter());
+		setString(10, g.getLeadershipType());
+		setString(11, g.getMotto());
+
+		int objectUUID = (int) getUUID();
+		if (objectUUID > 0)
+			return GET_GUILD(objectUUID);
+		return null;
+	}
+
+	public boolean UPDATE_GUILD_RANK_OFFLINE(int target, int newRank, int guildId) {
+		prepareCallable("UPDATE `obj_character` SET `guild_title`=? WHERE `UID`=? && `guildUID`=?");
+		setInt(1, newRank);
+		setInt(2, target);
+		setInt(3, guildId);
+		return (executeUpdate() > 0);
+	}
+
+	public boolean UPDATE_PARENT(int guildUID, int parentUID) {
+		prepareCallable("UPDATE `object` SET `parent`=? WHERE `UID`=?");
+		setInt(1, parentUID);
+		setInt(2, guildUID);
+		return (executeUpdate() > 0);
+	}
+
+	public int DELETE_GUILD(final Guild guild) {
+		prepareCallable("DELETE FROM `object` WHERE `UID` = ?");
+		setLong(1, (long) guild.getObjectUUID());
+		return executeUpdate();
+	}
+
+	public boolean UPDATE_MINETIME(int guildUID, int mineTime) {
+		prepareCallable("UPDATE `obj_guild` SET `mineTime`=? WHERE `UID`=?");
+		setInt(1, mineTime);
+		setInt(2, guildUID);
+		return (executeUpdate() > 0);
+	}
+
+	public int UPDATE_GUILD_STATUS_OFFLINE(int target, boolean isInnerCouncil, boolean isRecruiter, boolean isTaxCollector, int guildId) {
+		int updateMask = 0;
+		prepareCallable("SELECT `guild_isInnerCouncil`, `guild_isTaxCollector`, `guild_isRecruiter` FROM `obj_character` WHERE `UID`=? && `guildUID`=?");
+		setLong(1, (long) target);
+		setLong(2, (long) guildId);
+		try {
+			ResultSet rs = executeQuery();
+
+			//If the first query had no results, neither will the second
+			if (rs.first()) {
+				//Determine what is different
+				if (rs.getBoolean("guild_isInnerCouncil") != isInnerCouncil)
+					updateMask |= 4;
+				if (rs.getBoolean("guild_isRecruiter") != isRecruiter)
+					updateMask |= 2;
+				if (rs.getBoolean("guild_isTaxCollector") != isTaxCollector)
+					updateMask |= 1;
+			}
+			rs.close();
+		} catch (SQLException e) {
+			Logger.error( e.toString());
+		}
+		prepareCallable("UPDATE `obj_character` SET `guild_isInnerCouncil`=?, `guild_isTaxCollector`=?, `guild_isRecruiter`=?, `guild_isFullMember`=? WHERE `UID`=? && `guildUID`=?");
+		setBoolean(1, isInnerCouncil);
+		setBoolean(2, isRecruiter);
+		setBoolean(3, isTaxCollector);
+		setBoolean(4, ((updateMask > 0))); //If you are becoming an officer, or where an officer, your a full member...
+		setLong(5, (long) target);
+		setLong(6, (long) guildId);
+		return executeUpdate();
+
+	}
+
+
+	// *** Refactor: Why are we saving tags/charter in update?
+	//               It's not like this shit ever changes.
+
+	public boolean updateDatabase(final Guild g) {
+		prepareCallable("UPDATE `obj_guild` SET `name`=?, `backgroundColor01`=?, `backgroundColor02`=?, `symbolColor`=?, `backgroundDesign`=?, `symbol`=?, `charter`=?, `motd`=?, `icMotd`=?, `nationMotd`=?, `leaderUID`=? WHERE `UID`=?");
+		setString(1, g.getName());
+		setInt(2, g.getGuildTag().backgroundColor01);
+		setInt(3, g.getGuildTag().backgroundColor02);
+		setInt(4, g.getGuildTag().symbolColor);
+		setInt(5, g.getGuildTag().backgroundDesign);
+		setInt(6, g.getGuildTag().symbol);
+		setInt(7, g.getCharter());
+		setString(8, g.getMOTD());
+		setString(9, g.getICMOTD());
+		setString(10, "");
+		setInt(11, g.getGuildLeaderUUID());
+		setLong(12, (long) g.getObjectUUID());
+		return (executeUpdate() != 0);
+	}
+	public boolean ADD_TO_ALLIANCE_LIST(final long sourceGuildID, final long targetGuildID, boolean isRecommended, boolean isAlly, String recommender) {
+		prepareCallable("INSERT INTO `dyn_guild_allianceenemylist` (`GuildID`, `OtherGuildID`,`isRecommended`, `isAlliance`, `recommender`) VALUES (?,?,?,?,?)");
+		setLong(1, sourceGuildID);
+		setLong(2, targetGuildID);
+		setBoolean(3, isRecommended);
+		setBoolean(4, isAlly);
+		setString(5, recommender);
+		return (executeUpdate() > 0);
+	}
+
+	public boolean REMOVE_FROM_ALLIANCE_LIST(final long sourceGuildID, long targetGuildID) {
+		prepareCallable("DELETE FROM `dyn_guild_allianceenemylist` WHERE `GuildID`=? AND `OtherGuildID`=?");
+		setLong(1, sourceGuildID);
+		setLong(2, targetGuildID);
+		return (executeUpdate() > 0);
+	}
+
+	public boolean UPDATE_RECOMMENDED(final long sourceGuildID, long targetGuildID) {
+		prepareCallable("UPDATE `dyn_guild_allianceenemylist` SET `isRecommended` = ? WHERE `GuildID`=? AND `OtherGuildID`=?");
+		setByte(1,(byte)0);
+		setLong(2, sourceGuildID);
+		setLong(3, targetGuildID);
+		return (executeUpdate() > 0);
+	}
+
+	public boolean UPDATE_ALLIANCE(final long sourceGuildID, long targetGuildID, boolean isAlly) {
+		prepareCallable("UPDATE `dyn_guild_allianceenemylist` SET `isAlliance` = ? WHERE `GuildID`=? AND `OtherGuildID`=?");
+		setBoolean(1,isAlly);
+		setLong(2, sourceGuildID);
+		setLong(3, targetGuildID);
+		return (executeUpdate() > 0);
+	}
+
+	public boolean UPDATE_ALLIANCE_AND_RECOMMENDED(final long sourceGuildID, long targetGuildID, boolean isAlly) {
+		prepareCallable("UPDATE `dyn_guild_allianceenemylist` SET `isRecommended` = ?, `isAlliance` = ? WHERE `GuildID`=? AND `OtherGuildID`=?");
+		setByte(1,(byte)0);
+		setBoolean(2,isAlly);
+		setLong(3, sourceGuildID);
+		setLong(4, targetGuildID);
+
+		return (executeUpdate() > 0);
+	}
+
+	public void LOAD_ALL_ALLIANCES_FOR_GUILD(Guild guild) {
+
+		if (guild == null)
+			return;
+
+		prepareCallable("SELECT * FROM `dyn_guild_allianceenemylist` WHERE `GuildID` = ?");
+		setInt(1,guild.getObjectUUID());
+
+		try {
+			ResultSet rs = executeQuery();
+
+			//shrines cached in rs for easy cache on creation.
+			while (rs.next()) {
+				GuildAlliances guildAlliance = new GuildAlliances(rs);
+				guild.guildAlliances.put(guildAlliance.getAllianceGuild(), guildAlliance);
+			}
+
+
+		} catch (SQLException e) {
+			Logger.error( e.getMessage());
+		} finally {
+			closeCallable();
+		}
+
+	}
+
+	public void LOAD_GUILD_HISTORY_FOR_PLAYER(PlayerCharacter pc) {
+
+		if (pc == null)
+			return;
+
+		prepareCallable("SELECT * FROM `dyn_character_guildhistory` WHERE `CharacterID` = ?");
+		setInt(1,pc.getObjectUUID());
+
+		try {
+			ArrayList<GuildHistory> tempList = new ArrayList<>();
+			ResultSet rs = executeQuery();
+
+			//shrines cached in rs for easy cache on creation.
+			while (rs.next()) {
+				GuildHistory guildHistory = new GuildHistory(rs);
+				tempList.add(guildHistory);
+			}
+
+			pc.setGuildHistory(tempList);
+
+
+		} catch (SQLException e) {
+			Logger.error(e.getMessage());
+		} finally {
+			closeCallable();
+		}
+
+	}
+	
+	//TODO uncomment this when finished with guild history warehouse integration
+//	public HashMap<Integer, GuildRecord> GET_WAREHOUSE_GUILD_HISTORY(){
+//		
+//		HashMap<Integer, GuildRecord> tempMap = new HashMap<>();
+//		prepareCallable("SELECT * FROM `warehouse_guildhistory` WHERE `eventType` = 'CREATE'");
+//		try {
+//			ResultSet rs = executeQuery();
+//			
+//			while (rs.next()) {
+//				GuildRecord guildRecord = new GuildRecord(rs);
+//				tempMap.put(guildRecord.guildID, guildRecord);
+//			}
+//		}catch (Exception e){
+//			Logger.error(e);
+//		}
+//		return tempMap;
+//		
+//	}
+
+
+}
diff --git a/src/engine/db/handlers/dbHandlerBase.java b/src/engine/db/handlers/dbHandlerBase.java
new file mode 100644
index 00000000..2531d854
--- /dev/null
+++ b/src/engine/db/handlers/dbHandlerBase.java
@@ -0,0 +1,470 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.db.handlers;
+
+import engine.Enum;
+import engine.Enum.GameObjectType;
+import engine.gameManager.ConfigManager;
+import engine.gameManager.DbManager;
+import engine.objects.AbstractGameObject;
+import engine.objects.AbstractWorldObject;
+import engine.server.MBServerStatics;
+import org.pmw.tinylog.Logger;
+
+import java.sql.*;
+import java.time.LocalDateTime;
+import java.util.ArrayList;
+import java.util.HashSet;
+
+public abstract class dbHandlerBase {
+
+	/*
+	 * CallableStatements handled below this line!
+	 */
+	protected Class<? extends AbstractGameObject> localClass = null;
+	protected GameObjectType localObjectType;
+	protected final ThreadLocal<CallableStatement> cs = new ThreadLocal<>();
+
+	protected final void prepareCallable(final String sql) {
+		try {
+			this.cs.set((CallableStatement) DbManager.getConn().prepareCall(sql, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY));
+		} catch (SQLException e) {
+			Logger.error("DbManager.getConn", e);
+			Logger.error("SQL Error number: " + e.getErrorCode());
+		}
+	}
+
+	protected final void setDate(int parameterIndex, Date value) {
+		try {
+			this.cs.get().setDate(parameterIndex, value);
+		} catch (SQLException e) {
+			Logger.error("SQL Error number: " + e.getErrorCode());
+		}
+	}
+
+	protected final void setInt(int parameterIndex, int value) {
+		try {
+			this.cs.get().setInt(parameterIndex, value);
+		} catch (SQLException e) {
+			Logger.error("SQL Error number: " + e.getErrorCode());
+		}
+	}
+
+	protected final void setLong(int parameterIndex, long value) {
+		try {
+			this.cs.get().setLong(parameterIndex, value);
+		} catch (SQLException e) {
+			Logger.error("SQL Error number: " + e.getErrorCode());
+		}
+	}
+
+	protected final void setFloat(int parameterIndex, float value) {
+		try {
+			this.cs.get().setFloat(parameterIndex, value);
+		} catch (SQLException e) {
+			Logger.error("SQL Error number: " + e.getErrorCode());
+		}
+	}
+
+	protected final void setShort(int parameterIndex, short value) {
+		try {
+			this.cs.get().setShort(parameterIndex, value);
+		} catch (SQLException e) {
+			Logger.error("SQL Error number: " + e.getErrorCode());
+		}
+	}
+
+	protected final void setString(int parameterIndex, String value) {
+		try {
+			this.cs.get().setString(parameterIndex, value);
+		} catch (SQLException e) {
+			Logger.error("SQL Error number: " + e.getErrorCode());
+		}
+	}
+
+	protected final void setBytes(int parameterIndex, byte[] value) {
+		try {
+			this.cs.get().setBytes(parameterIndex, value);
+		} catch (SQLException e) {
+			Logger.error("SQL Error number: " + e.getErrorCode());
+		}
+	}
+
+	protected final void setByte(int parameterIndex, byte value) {
+		try {
+			this.cs.get().setByte(parameterIndex, value);
+		} catch (SQLException e) {
+			Logger.error("SQL Error number: " + e.getErrorCode());
+		}
+	}
+
+	protected final void setBoolean(int parameterIndex, boolean value) {
+		try {
+			this.cs.get().setBoolean(parameterIndex, value);
+		} catch (SQLException e) {
+			Logger.error("SQL Error number: " + e.getErrorCode());
+		}
+	}
+
+	protected final void setNULL(int parameterIndex, int type) {
+		try {
+			this.cs.get().setNull(parameterIndex, type);
+		} catch (SQLException e) {
+			Logger.error("SQL Error number: " + e.getErrorCode());
+		}
+	}
+
+	protected final void setLocalDateTime(int parameterIndex, LocalDateTime localDateTime) {
+
+		try {
+			this.cs.get().setTimestamp(parameterIndex, Timestamp.valueOf(localDateTime));
+		} catch (SQLException e) {
+			Logger.error("SQL Error number: " + e.getErrorCode());
+		}
+	}
+
+	protected final void setTimeStamp(int parameterIndex, long time) {
+		try {
+			this.cs.get().setTimestamp(parameterIndex, new java.sql.Timestamp(time));
+		} catch (SQLException e) {
+			Logger.error("SQL Error number: " + e.getErrorCode());
+		}
+	}
+
+	protected final boolean execute() {
+		try {
+			return this.cs.get().execute();
+		} catch (SQLException e) {
+			Logger.error("SQL Error number: " + e.getErrorCode());
+			logSQLCommand();
+		}
+		return false;
+	}
+
+	protected final ResultSet executeQuery() {
+		try {
+			return this.cs.get().executeQuery();
+		} catch (SQLException e) {
+			Logger.error("SQL Error number: " + e.getErrorCode());
+			logSQLCommand();
+		}
+		return null;
+	}
+
+	protected final int executeUpdate() {
+		return executeUpdate(true);
+	}
+
+	protected final int executeUpdate(boolean close) {
+		try {
+			return this.cs.get().executeUpdate();
+		} catch (SQLException e) {
+			Logger.error("SQL Error number: " + e.getErrorCode());
+			logSQLCommand();
+		} finally {
+			if (close)
+				closeCallable();
+		}
+		return 0;
+	}
+
+	protected final void logSQLCommand() {
+		try {
+			Logger.error("Failed SQL Command: " + this.cs.get().toString());
+		} catch (Exception e) {
+
+		}
+	}
+
+	// Common return values from the database when calling stored procedures, abstracted to this layer
+	protected final String getResult(){
+		try {
+			ResultSet rs = this.executeQuery();
+			if (rs.next() && !isError(rs))
+				return rs.getString("result");
+		} catch (SQLException e) {
+			Logger.error(e);
+			logSQLCommand();
+		} finally {
+			closeCallable();
+		}
+		return null;
+	}
+
+	// Used for Stored procedures that return true when they succeed.
+	protected final boolean worked() {
+		try {
+			ResultSet rs = this.executeQuery();
+			if (rs.next() && !isError(rs))
+				return rs.getBoolean("result");
+		} catch (SQLException e) {
+			Logger.error(e);
+			logSQLCommand();
+		} finally {
+			closeCallable();
+		}
+		return false;
+	}
+
+	// Common return values from the database when calling stored procedures, abstracted to this layer
+	protected final long getUUID(){
+		try {
+			ResultSet rs = this.executeQuery();
+			if (rs.next() && !isError(rs))
+				return rs.getLong("UID");
+		} catch (SQLException e) {
+			Logger.error(e);
+			logSQLCommand();
+		} finally {
+			closeCallable();
+		}
+		return -1;
+	}
+
+	protected final String getString(String field) {
+		try {
+			ResultSet rs = this.executeQuery();
+			if (rs.next())
+				return rs.getString(field);
+		} catch (SQLException e) {
+			Logger.error(e);
+			logSQLCommand();
+		} finally {
+			closeCallable();
+		}
+		return "";
+	}
+
+	protected final long getLong(String field) {
+		try {
+			ResultSet rs = this.executeQuery();
+			if (rs.next())
+				return rs.getLong(field);
+		} catch (SQLException e) {
+			Logger.error(e);
+			logSQLCommand();
+		} finally {
+			closeCallable();
+		}
+		return 0L;
+	}
+
+	protected final int getInt(String field) {
+		try {
+			ResultSet rs = this.executeQuery();
+			if (rs.next())
+				return rs.getInt(field);
+		} catch (SQLException e) {
+			Logger.error(e);
+			logSQLCommand();
+		} finally {
+			closeCallable();
+		}
+		return 0;
+	}
+
+	protected final int insertGetUUID() {
+		int key = 0;
+		try {
+			this.cs.get().executeUpdate();
+			ResultSet rs = this.cs.get().getGeneratedKeys();
+			if (rs.next())
+				key = rs.getInt(1);
+		} catch (SQLException e) {
+			Logger.error(e);
+			logSQLCommand();
+		} finally {
+			closeCallable();
+		}
+		return key;
+	}
+
+	protected final boolean isError(ResultSet rs) throws SQLException {
+		ResultSetMetaData rsmd = rs.getMetaData();
+		if (rsmd.getColumnCount() > 0 && !rsmd.getColumnName(1).equals("errorno"))
+			return false;
+		printError(rs);
+		return true;
+	}
+
+	protected final void printError(ResultSet rs) {
+		try {
+			int errorNum = rs.getInt("errorno");
+			String errorMsg = rs.getString("errormsg");
+			Logger.error("SQLError: errorNum: " + errorNum + ", errorMsg: " + errorMsg);
+			logSQLCommand();
+		} catch (SQLException e) {}
+	}
+
+	protected final void getColumNames(ResultSet rs) throws SQLException {
+		ResultSetMetaData rsmd = rs.getMetaData();
+		int numColumns = rsmd.getColumnCount();
+		String out = "Column names for resultSet: ";
+		for (int i=1; i<numColumns+1; i++)
+			out += i + ": " + rsmd.getColumnName(i) + ", ";
+		Logger.info(out);
+	}
+
+	// Default actions to the objects table, generic to all objects
+	protected final long SET_PARENT(long objUID, long new_value, long old_value) {
+		prepareCallable("CALL object_GETSETPARENT(?,?,?)");
+		setLong(1, objUID);
+		setLong(2, new_value);
+		setLong(3, old_value);
+		return getUUID();
+	}
+
+	// NOTE: CALLING THIS FUNCTION CASCADE DELETES OBJECTS FROM THE DATABASE
+	protected final long REMOVE(long objUID) {
+		prepareCallable("CALL object_PURGECASCADE(?)");
+		setLong(1, objUID);
+		return getUUID();
+	}
+
+	protected <T extends AbstractGameObject> AbstractGameObject getObjectSingle(int id) {
+		return getObjectSingle(id, false, true);
+	}
+
+	protected <T extends AbstractGameObject> AbstractGameObject getObjectSingle(int id, boolean forceFromDB, boolean storeInCache) {
+
+		if (cs.get() == null){
+			return null;
+		}
+
+		if (!forceFromDB) {
+			if (DbManager.inCache(localObjectType, id)) {
+				closeCallable();
+				return DbManager.getFromCache(localObjectType, id);
+			}
+		}
+
+		AbstractGameObject out = null;
+
+		try {
+			if (MBServerStatics.DB_ENABLE_QUERY_OUTPUT)
+				Logger.info( "[GetObjectList] Executing query:" + cs.get().toString());
+
+			ResultSet rs = cs.get().executeQuery();
+
+			if (rs.next()) {
+				out = localClass.getConstructor(ResultSet.class).newInstance(rs);
+
+				if (storeInCache)
+					DbManager.addToCache(out);
+			}
+
+			rs.close();
+
+		} catch (Exception e) {
+			Logger.error("AbstractGameObject", e);
+			out = null;
+		} finally {
+			closeCallable();
+		}
+
+		// Only call runAfterLoad() for objects instanced on the world server
+
+		if ((out != null && out instanceof AbstractWorldObject) &&
+				(ConfigManager.serverType.equals(Enum.ServerType.WORLDSERVER) ||
+						(out.getObjectType() == GameObjectType.Guild)))
+			((AbstractWorldObject)out).runAfterLoad();
+
+		return out;
+	}
+
+	protected void closeCallable() {
+		try {
+			if (this.cs.get() != null)
+				this.cs.get().close();
+		} catch (SQLException e) {}
+	}
+
+	protected <T extends AbstractGameObject> ArrayList<T> getObjectList() {
+		return getObjectList(20, false);
+	}
+
+	protected <T extends AbstractGameObject> ArrayList<T> getLargeObjectList() {
+		return getObjectList(2000, false);
+	}
+
+	@SuppressWarnings("unchecked")
+	protected <T extends AbstractGameObject> ArrayList<T> getObjectList(int listSize, boolean forceFromDB) {
+
+		String query = "No Callable Statement accessable.";
+
+		ArrayList<T> out = new ArrayList<>(listSize);
+
+		if (this.cs.get() == null)
+			return out;
+
+		try {
+
+			CallableStatement css = this.cs.get();
+
+			if (css != null)
+				query = this.cs.get().toString();
+
+			if (MBServerStatics.DB_ENABLE_QUERY_OUTPUT)
+				Logger.info( "[GetObjectList] Executing query:" + query);
+
+			ResultSet rs = this.cs.get().executeQuery();
+
+			while (rs.next()) {
+
+				int id = rs.getInt(1);
+
+				if (!forceFromDB && DbManager.inCache(localObjectType, id)) {
+					out.add((T) DbManager.getFromCache(localObjectType, id));
+				} else {
+					AbstractGameObject toAdd = localClass.getConstructor(ResultSet.class).newInstance(rs);
+					DbManager.addToCache(toAdd);
+					out.add((T) toAdd);
+
+					if (toAdd != null && toAdd instanceof AbstractWorldObject)
+						((AbstractWorldObject)toAdd).runAfterLoad();
+
+				}
+			}
+			rs.close();
+		} catch (Exception e) {
+			Logger.error(localClass.getCanonicalName(), "List Failure: " + query, e);
+			e.printStackTrace();
+			return new ArrayList<>(); // Do we want a null return on error?
+		} finally {
+			closeCallable();
+		}
+
+		return out;
+	}
+
+	/* Prepared Statements handled below this line */
+
+	protected HashSet<Integer> getIntegerList(final int columnNumber) {
+
+		if (MBServerStatics.DB_ENABLE_QUERY_OUTPUT)
+			Logger.info("[GetIntegerList] Executing query:" + this.cs.toString());
+
+		HashSet<Integer> out = new HashSet<>();
+
+		try {
+			ResultSet rs = executeQuery();
+
+			while (rs.next()) {
+				out.add(rs.getInt(columnNumber));
+			}
+			rs.close();
+		} catch (SQLException e) {
+			Logger.error("SQL Error number: " + e.getErrorCode());
+		} finally {
+			closeCallable();
+		}
+		return out;
+	}
+}
diff --git a/src/engine/db/handlers/dbHeightMapHandler.java b/src/engine/db/handlers/dbHeightMapHandler.java
new file mode 100644
index 00000000..1ea5d54c
--- /dev/null
+++ b/src/engine/db/handlers/dbHeightMapHandler.java
@@ -0,0 +1,47 @@
+package engine.db.handlers;
+
+import engine.InterestManagement.HeightMap;
+import org.pmw.tinylog.Logger;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+public class dbHeightMapHandler extends dbHandlerBase {
+
+	public dbHeightMapHandler() {
+
+
+	}
+
+	public void LOAD_ALL_HEIGHTMAPS() {
+
+		HeightMap thisHeightmap;
+
+		int recordsRead = 0;
+		int worthlessDupes = 0;
+
+		HeightMap.heightMapsCreated = 0;
+
+		prepareCallable("SELECT * FROM static_zone_heightmap INNER JOIN static_zone_size ON static_zone_size.loadNum = static_zone_heightmap.zoneLoadID");
+
+		try {
+			ResultSet rs = executeQuery();
+
+			while (rs.next()) {
+
+				recordsRead++;
+				thisHeightmap = new HeightMap(rs);
+
+				if (thisHeightmap.getHeightmapImage() == null) {
+					Logger.info( "Imagemap for " + thisHeightmap.getHeightMapID() + " was null");
+					continue;
+				}
+			}
+		} catch (SQLException e) {
+			Logger.error("LoadAllHeightMaps: " + e.getErrorCode() + ' ' + e.getMessage(), e);
+		} finally {
+			closeCallable();
+		}
+	}
+
+}
diff --git a/src/engine/db/handlers/dbItemBaseHandler.java b/src/engine/db/handlers/dbItemBaseHandler.java
new file mode 100644
index 00000000..6217f7d3
--- /dev/null
+++ b/src/engine/db/handlers/dbItemBaseHandler.java
@@ -0,0 +1,152 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.db.handlers;
+
+import engine.objects.EquipmentSetEntry;
+import engine.objects.ItemBase;
+import org.pmw.tinylog.Logger;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.HashMap;
+
+public class dbItemBaseHandler extends dbHandlerBase {
+
+	public dbItemBaseHandler() {
+
+	}
+
+	public void LOAD_BAKEDINSTATS(ItemBase itemBase) {
+
+		try {
+			prepareCallable("SELECT * FROM `static_item_bakedinstat` WHERE `itemID` = ?");
+			setInt(1, itemBase.getUUID());
+
+			ResultSet rs = executeQuery();
+
+			while (rs.next()) {
+
+				if (rs.getBoolean("fromUse"))
+					itemBase.getUsedStats().put(rs.getInt("token"), rs.getInt("numTrains"));
+				else
+					itemBase.getBakedInStats().put(rs.getInt("token"), rs.getInt("numTrains"));
+			}
+		} catch (SQLException e) {
+			Logger.error( e.toString());
+		} finally {
+			closeCallable();
+		}
+	}
+
+	public void LOAD_ANIMATIONS(ItemBase itemBase) {
+
+		ArrayList<Integer> tempList = new ArrayList<>();
+		ArrayList<Integer> tempListOff = new ArrayList<>();
+		try {
+			prepareCallable("SELECT * FROM `static_itembase_animations` WHERE `itemBaseUUID` = ?");
+			setInt(1, itemBase.getUUID());
+
+			ResultSet rs = executeQuery();
+
+			while (rs.next()) {
+				int animation = rs.getInt("animation");
+
+				boolean rightHand = rs.getBoolean("rightHand");
+
+				if (rightHand)
+					tempList.add(animation);
+				else
+					tempListOff.add(animation);
+
+			}
+		} catch (SQLException e) {
+			Logger.error( e.toString());
+		} finally {
+			closeCallable();
+		}
+
+		itemBase.setAnimations(tempList);
+		itemBase.setOffHandAnimations(tempListOff);
+	}
+
+	public void LOAD_ALL_ITEMBASES() {
+
+		ItemBase itemBase;
+
+		int recordsRead = 0;
+
+		prepareCallable("SELECT * FROM static_itembase");
+
+		try {
+			ResultSet rs = executeQuery();
+
+			while (rs.next()) {
+
+				recordsRead++;
+				itemBase = new ItemBase(rs);
+
+				// Add ItemBase to internal cache
+
+				ItemBase.addToCache(itemBase);
+			}
+
+			Logger.info( "read: " + recordsRead + "cached: " + ItemBase.getUUIDCache().size());
+
+		} catch (SQLException e) {
+			Logger.error( e.toString());
+		} finally {
+			closeCallable();
+		}
+	}
+
+	public HashMap<Integer, ArrayList<EquipmentSetEntry>> LOAD_EQUIPMENT_FOR_NPC_AND_MOBS() {
+
+		HashMap<Integer, ArrayList<EquipmentSetEntry>> equipmentSets;
+		EquipmentSetEntry equipmentSetEntry;
+		int	equipSetID;
+
+		equipmentSets = new HashMap<>();
+		int recordsRead = 0;
+
+		prepareCallable("SELECT * FROM static_npc_equipmentset");
+
+		try {
+			ResultSet rs = executeQuery();
+
+			while (rs.next()) {
+
+				recordsRead++;
+
+				equipSetID = rs.getInt("equipmentSet");
+				equipmentSetEntry = new EquipmentSetEntry(rs);
+
+				if (equipmentSets.get(equipSetID) == null){
+					ArrayList<EquipmentSetEntry> equipList = new ArrayList<>();
+					equipList.add(equipmentSetEntry);
+					equipmentSets.put(equipSetID, equipList);
+				}
+				else{
+					ArrayList<EquipmentSetEntry>equipList = equipmentSets.get(equipSetID);
+					equipList.add(equipmentSetEntry);
+					equipmentSets.put(equipSetID, equipList);
+				}
+			}
+
+			Logger.info("read: " + recordsRead + " cached: " + equipmentSets.size());
+
+		} catch (SQLException e) {
+			Logger.error( e.toString());
+		} finally {
+			closeCallable();
+		}
+		return equipmentSets;
+	}
+}
diff --git a/src/engine/db/handlers/dbItemHandler.java b/src/engine/db/handlers/dbItemHandler.java
new file mode 100644
index 00000000..6a2e14f3
--- /dev/null
+++ b/src/engine/db/handlers/dbItemHandler.java
@@ -0,0 +1,430 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.db.handlers;
+
+import engine.Enum.ItemContainerType;
+import engine.Enum.ItemType;
+import engine.Enum.OwnerType;
+import engine.objects.*;
+import org.pmw.tinylog.Logger;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+
+
+public class dbItemHandler extends dbHandlerBase {
+
+	public dbItemHandler() {
+		this.localClass = Item.class;
+		this.localObjectType = engine.Enum.GameObjectType.valueOf(this.localClass.getSimpleName());
+	}
+
+	public Item ADD_ITEM(Item toAdd) {
+		prepareCallable("CALL `item_CREATE`(?, ?, ?, ?, ?, ?, ?, ?, ?,?);");
+		setInt(1, toAdd.getOwnerID());
+		setInt(2, toAdd.getItemBaseID());
+		setInt(3, toAdd.getChargesRemaining());
+		setInt(4, toAdd.getDurabilityCurrent());
+		setInt(5, toAdd.getDurabilityMax());
+		if (toAdd.getNumOfItems() < 1)
+			setInt(6, 1);
+		else
+			setInt(6, toAdd.getNumOfItems());
+
+		switch (toAdd.containerType) {
+			case INVENTORY:
+				setString(7, "inventory");
+				break;
+			case EQUIPPED:
+				setString(7, "equip");
+				break;
+			case BANK:
+				setString(7, "bank");
+				break;
+			case VAULT:
+				setString(7, "vault");
+				break;
+			case FORGE:
+				setString(7, "forge");
+				break;
+				default:
+					setString(7, "none"); //Shouldn't be here
+					break;
+		}
+
+		setByte(8, toAdd.getEquipSlot());
+		setInt(9, toAdd.getFlags());
+		setString(10, toAdd.getCustomName());
+		int objectUUID = (int) getUUID();
+
+		if (objectUUID > 0)
+			return GET_ITEM(objectUUID);
+		return null;
+	}
+
+	public boolean DELETE_ITEM(final Item item) {
+		prepareCallable("DELETE FROM `object` WHERE `UID`=? && `type`='item' limit 1");
+		setLong(1, (long) item.getObjectUUID());
+		return (executeUpdate() > 0);
+	}
+
+	public boolean DELETE_ITEM(final int itemUUID) {
+		prepareCallable("DELETE FROM `object` WHERE `UID`=? && `type`='item' limit 1");
+		setLong(1, (long) itemUUID);
+		return (executeUpdate() > 0);
+	}
+
+	public String GET_OWNER(int ownerID) {
+		prepareCallable("SELECT `type` FROM `object` WHERE `UID`=?");
+		setLong(1, (long) ownerID);
+		return getString("type");
+	}
+
+	public boolean DO_TRADE(HashSet<Integer> from1, HashSet<Integer> from2,
+			CharacterItemManager man1, CharacterItemManager man2,
+			Item inventoryGold1, Item inventoryGold2, int goldFrom1, int goldFrom2) {
+
+		AbstractCharacter ac1 = man1.getOwner();
+		AbstractCharacter ac2 = man2.getOwner();
+		if (ac1 == null || ac2 == null || inventoryGold1 == null || inventoryGold2 == null)
+			return false;
+
+		prepareCallable("CALL `item_TRADE`(?, ?, ?, ?, ?, ?, ?, ?)");
+		setString(1, formatTradeString(from1));
+		setLong(2, (long) ac1.getObjectUUID());
+		setString(3, formatTradeString(from2));
+		setLong(4, (long) ac2.getObjectUUID());
+		setInt(5, goldFrom1);
+		setLong(6, (long) inventoryGold1.getObjectUUID());
+		setInt(7, goldFrom2);
+		setLong(8, (long) inventoryGold2.getObjectUUID());
+        return worked();
+	}
+
+	private static String formatTradeString(HashSet<Integer> list) {
+		int size = list.size();
+
+		String ret = "";
+		if (size == 0)
+			return ret;
+		boolean start = true;
+		for (int i : list) {
+			if (start){
+				ret += i;
+				start = false;
+			}
+			else
+			ret += "," + i;
+		}
+		return ret;
+	}
+
+	public ArrayList<Item> GET_EQUIPPED_ITEMS(final int targetId) {
+		prepareCallable("SELECT `obj_item`.*, `object`.`parent`, `object`.`type` FROM `object` INNER JOIN `obj_item` ON `object`.`UID` = `obj_item`.`UID` WHERE `object`.`parent`=? && `obj_item`.`item_container`='equip';");
+		setLong(1, (long) targetId);
+		return getObjectList();
+	}
+
+	public Item GET_ITEM(final int id) {
+
+
+		prepareCallable("SELECT `obj_item`.*, `object`.`parent`, `object`.`type` FROM `object` INNER JOIN `obj_item` ON `object`.`UID` = `obj_item`.`UID` WHERE `object`.`UID`=?;");
+		setLong(1, (long) id);
+		return (Item) getObjectSingle(id);
+	}
+
+	public Item GET_GOLD_FOR_PLAYER(final int playerID, final int goldID, int worldID) {
+		prepareCallable("SELECT `obj_item`.*, `object`.`parent`, `object`.`type` FROM `object` INNER JOIN `obj_item` ON `object`.`UID` = `obj_item`.`UID` WHERE `object`.`parent`=? AND `obj_item`.`item_itembaseID`=?;");
+		setInt(1, playerID);
+		setInt(2, goldID);
+		int objectUUID = (int) getUUID();
+		return (Item) getObjectSingle(objectUUID);
+
+	}
+
+	public ArrayList<Item> GET_ITEMS_FOR_ACCOUNT(final int accountId) {
+		prepareCallable("SELECT `obj_item`.*, `object`.`parent`, `object`.`type` FROM `object` INNER JOIN `obj_item` ON `object`.`UID` = `obj_item`.`UID` WHERE `object`.`parent`=?;");
+		setLong(1, (long) accountId);
+		return getObjectList();
+	}
+
+	public ArrayList<Item> GET_ITEMS_FOR_NPC(final int npcId) {
+		prepareCallable("SELECT `obj_item`.*, `object`.`parent`, `object`.`type` FROM `object` INNER JOIN `obj_item` ON `object`.`UID` = `obj_item`.`UID` WHERE `object`.`parent`=?");
+		setLong(1, (long) npcId);
+		return getObjectList();
+	}
+
+	public ArrayList<Item> GET_ITEMS_FOR_PC(final int id) {
+		prepareCallable("SELECT `obj_item`.*, `object`.`parent`, `object`.`type` FROM `object` INNER JOIN `obj_item` ON `object`.`UID` = `obj_item`.`UID` WHERE `object`.`parent`=?");
+		setLong(1, (long) id);
+		return getLargeObjectList();
+	}
+
+	public ArrayList<Item> GET_ITEMS_FOR_PLAYER_AND_ACCOUNT(final int playerID, final int accountID) {
+		prepareCallable("SELECT `obj_item`.*, `object`.`parent`, `object`.`type` FROM `object` INNER JOIN `obj_item` ON `object`.`UID` = `obj_item`.`UID` WHERE (`object`.`parent`=? OR `object`.`parent`=?)");
+		setLong(1, (long) playerID);
+		setLong(2, (long) accountID);
+		return getLargeObjectList();
+	}
+
+	public boolean MOVE_GOLD(final Item from, final Item to, final int amt) {
+		int newFromAmt = from.getNumOfItems() - amt;
+		int newToAmt = to.getNumOfItems() + amt;
+		prepareCallable("UPDATE `obj_item` SET `item_numberOfItems` = CASE WHEN `UID`=?  THEN ? WHEN `UID`=? THEN ? END WHERE `UID` IN (?, ?);");
+		setLong(1, (long) from.getObjectUUID());
+		setInt(2, newFromAmt);
+		setLong(3, (long) to.getObjectUUID());
+		setInt(4, newToAmt);
+		setLong(5, (long) from.getObjectUUID());
+		setLong(6, (long) to.getObjectUUID());
+		return (executeUpdate() != 0);
+	}
+
+	public boolean ORPHAN_INVENTORY(final HashSet<Item> inventory) {
+		boolean worked = true;
+		for (Item item : inventory) {
+
+			if (item.getItemBase().getType().equals(ItemType.GOLD))
+				continue;
+
+			prepareCallable("UPDATE `obj_item` LEFT JOIN `object` ON `object`.`UID` = `obj_item`.`UID` SET `object`.`parent`=NULL, `obj_item`.`item_container`='none' WHERE `object`.`UID`=?;");
+			setLong(1, (long) item.getObjectUUID());
+			if (executeUpdate() == 0)
+				worked = false;
+			else
+				item.zeroItem();
+		}
+		return worked;
+	}
+
+	public Item PURCHASE_ITEM_FROM_VENDOR(final PlayerCharacter pc, final ItemBase ib) {
+		Item item = null;
+		byte charges = 0;
+		charges = (byte) ib.getNumCharges();
+		short durability = (short) ib.getDurability();
+
+		Item temp = new Item(ib, pc.getObjectUUID(),
+				OwnerType.PlayerCharacter, charges, charges, durability, durability,
+				true, false,ItemContainerType.INVENTORY, (byte) 0,
+                new ArrayList<>(),"");
+		try {
+			item = this.ADD_ITEM(temp);
+		} catch (Exception e) {
+			Logger.error(e);
+		}
+		return item;
+	}
+
+	public HashSet<Integer> GET_ITEMS_FOR_VENDOR(final int vendorID) {
+		prepareCallable("SELECT ID FROM static_itembase WHERE vendorType = ?");
+		setInt(1, vendorID);
+		return getIntegerList(1);
+	}
+
+	public ArrayList<Item> GET_ITEMS_FOR_VENDOR_FORGING(final int npcID) {
+		prepareCallable("SELECT `obj_item`.*, `object`.`parent` FROM `object` INNER JOIN `obj_item` ON `object`.`UID` = `obj_item`.`UID` WHERE `object`.`parent`=? AND `obj_item`.`item_container` =?");
+		setLong(1, (long) npcID);
+		setString(2, "forge");
+		return getObjectList();
+	}
+
+	public String SET_PROPERTY(final Item i, String name, Object new_value) {
+		prepareCallable("CALL item_SETPROP(?,?,?)");
+		setLong(1, (long) i.getObjectUUID());
+		setString(2, name);
+		setString(3, String.valueOf(new_value));
+		return getResult();
+	}
+
+	public String SET_PROPERTY(final Item i, String name, Object new_value, Object old_value) {
+		prepareCallable("CALL item_GETSETPROP(?,?,?,?)");
+		setLong(1, (long) i.getObjectUUID());
+		setString(2, name);
+		setString(3, String.valueOf(new_value));
+		setString(4, String.valueOf(old_value));
+		return getResult();
+	}
+
+	//Used to transfer a single item between owners or equip or vault or bank or inventory
+	public boolean UPDATE_OWNER(final Item item, int newOwnerID, boolean ownerNPC, boolean ownerPlayer,
+			boolean ownerAccount, ItemContainerType containerType, int slot) {
+
+		prepareCallable("CALL `item_TRANSFER_OWNER`(?, ?, ?, ? )");
+		setLong(1, (long) item.getObjectUUID());
+		if (newOwnerID != 0)
+			setLong(2, (long) newOwnerID);
+		else
+			setNULL(2, java.sql.Types.BIGINT);
+		
+		switch (containerType) {
+			case INVENTORY:
+				setString(3, "inventory");
+				break;
+			case EQUIPPED:
+				setString(3, "equip");
+				break;
+			case BANK:
+				setString(3, "bank");
+				break;
+			case VAULT:
+				setString(3, "vault");
+				break;
+			case FORGE:
+				setString(3, "forge");
+				break;
+			default:
+				setString(3, "none"); //Shouldn't be here
+				break;
+		}
+		setInt(4, slot);
+		return worked();
+	}
+
+	public boolean SET_DURABILITY(final Item item, int value) {
+		prepareCallable("UPDATE `obj_item` SET `item_durabilityCurrent`=? WHERE `UID`=? AND `item_durabilityCurrent`=?");
+		setInt(1, value);
+		setLong(2, (long) item.getObjectUUID());
+		setInt(3, (int) item.getDurabilityCurrent());
+		return (executeUpdate() != 0);
+
+	}
+
+	//Update an item except ownership
+	public boolean UPDATE_DATABASE(final Item item) {
+		prepareCallable("UPDATE `obj_item` SET `item_itembaseID`=?, `item_chargesRemaining`=?, `item_durabilityCurrent`=?, `item_durabilityMax`=?, `item_numberOfItems`=? WHERE `UID`=?");
+		setInt(1, item.getItemBaseID());
+		setInt(2, item.getChargesRemaining());
+		setInt(3, item.getDurabilityCurrent());
+		setInt(4, item.getDurabilityMax());
+		setInt(5, item.getNumOfItems());
+		setLong(6, (long) item.getObjectUUID());
+		return (executeUpdate() != 0);
+	}
+
+	public boolean UPDATE_ROLL_COMPLETE(final Item item) {
+		prepareCallable("UPDATE `obj_item` SET `item_container` = ?, `item_dateToUpgrade` = ? WHERE `UID` = ?");
+		setString(1, "forge");
+		setLong(2, 0L);
+		setLong(3, (long) item.getObjectUUID());
+		return (executeUpdate() != 0);
+	}
+
+	public boolean SET_DATE_TO_UPGRADE(final Item item, long date) {
+		prepareCallable("UPDATE `obj_item` SET `item_dateToUPGRADE` = ? WHERE `UID` = ?");
+		setLong(1, date);
+		setLong(2, (long) item.getObjectUUID());
+		return (executeUpdate() != 0);
+	}
+
+	public boolean UPDATE_FORGE_TO_INVENTORY(final Item item) {
+		prepareCallable("UPDATE `obj_item` SET `item_container` = ? WHERE `UID` = ? AND `item_container` = 'forge';");
+		setString(1, "inventory");
+		setLong(2, (long) item.getObjectUUID());
+		return (executeUpdate() != 0);
+	}
+
+	/**
+	 * Attempts to update the quantity of this gold item
+	 *
+	 * @param value New quantity of gold
+	 * @return True on success
+	 */
+	public boolean UPDATE_GOLD(final Item item, int value) {
+		if (item == null)
+			return false;
+		return UPDATE_GOLD(item, value, item.getNumOfItems());
+	}
+
+	/**
+	 * Attempts to update the quantity of this gold item using CAS
+	 *
+	 * @return True on success
+	 */
+	public boolean UPDATE_GOLD(final Item item, int newValue, int oldValue) {
+
+		if (item.getItemBase().getType().equals(ItemType.GOLD) == false)
+			return false;
+
+		prepareCallable("UPDATE `obj_item` SET `item_numberOfItems`=? WHERE `UID`=?");
+		setInt(1, newValue);
+		setLong(2, (long) item.getObjectUUID());
+		return (executeUpdate() != 0);
+	}
+
+	/**
+	 * Attempts to update the value of two Gold items simultaneously.
+	 *
+	 * @param value New gold quantity for this item
+	 * @param otherGold Other Gold item being modified
+	 * @param valueOtherGold New quantity of gold for other item
+	 * @return True on success
+	 */
+	public boolean UPDATE_GOLD(Item gold, int value, Item otherGold, int valueOtherGold) {
+
+		if (gold.getItemBase().getType().equals(ItemType.GOLD) == false)
+			return false;
+
+		if (otherGold.getItemBase().getType().equals(ItemType.GOLD) == false)
+			return false;
+
+		int firstOld = gold.getNumOfItems();
+		int secondOld = gold.getNumOfItems();
+
+		prepareCallable("UPDATE `obj_item` SET `item_numberOfItems` = CASE WHEN `UID`=? AND `item_numberOfItems`=? THEN ? WHEN `UID`=? AND `item_numberOfItems`=? THEN ? END WHERE `UID` IN (?, ?);");
+		setLong(1, (long) gold.getObjectUUID());
+		setInt(2, firstOld);
+		setInt(3, value);
+		setLong(4, (long) otherGold.getObjectUUID());
+		setInt(5, secondOld);
+		setInt(6, valueOtherGold);
+		setLong(7, (long) gold.getObjectUUID());
+		setLong(8, (long) otherGold.getObjectUUID());
+		return (executeUpdate() != 0);
+	}
+
+	public boolean UPDATE_REMAINING_CHARGES(final Item item) {
+		prepareCallable("UPDATE `obj_item` SET `item_chargesRemaining` = ? WHERE `UID` = ?");
+		setInt(1, item.getChargesRemaining());
+		setLong(2, (long) item.getObjectUUID());
+		return (executeUpdate() != 0);
+	}
+
+	// This is necessary because default number of items is 1.
+	// When we create gold, we want it to start at 0 quantity.
+
+	public boolean ZERO_ITEM_STACK(Item item) {
+		prepareCallable("UPDATE `obj_item` SET `item_numberOfItems`=0 WHERE `UID` = ?");
+		setLong(1, (long) item.getObjectUUID());
+		return (executeUpdate() != 0);
+	}
+
+	public boolean UPDATE_FLAGS(Item item) {
+		prepareCallable("UPDATE `obj_item` SET `item_flags`=? WHERE `UID` = ?");
+		setInt(1, item.getFlags());
+		setLong(2, (long) item.getObjectUUID());
+		return (executeUpdate() != 0);
+	}
+
+	public boolean UPDATE_VALUE(Item item,int value) {
+		prepareCallable("UPDATE `obj_item` SET `item_value`=? WHERE `UID` = ?");
+		setInt(1, value);
+		setLong(2, (long) item.getObjectUUID());
+		return (executeUpdate() != 0);
+	}
+
+	public boolean UPDATE_FLAGS(Item item, int flags) {
+		prepareCallable("UPDATE `obj_item` SET `item_flags`=? WHERE `UID` = ?");
+		setInt(1, flags);
+		setLong(2, (long) item.getObjectUUID());
+		return (executeUpdate() != 0);
+	}
+
+
+}
diff --git a/src/engine/db/handlers/dbKitHandler.java b/src/engine/db/handlers/dbKitHandler.java
new file mode 100644
index 00000000..b558ec82
--- /dev/null
+++ b/src/engine/db/handlers/dbKitHandler.java
@@ -0,0 +1,36 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.db.handlers;
+
+import engine.objects.Kit;
+
+import java.util.ArrayList;
+
+public class dbKitHandler extends dbHandlerBase {
+
+	public dbKitHandler() {
+		this.localClass = Kit.class;
+		this.localObjectType = engine.Enum.GameObjectType.valueOf(this.localClass.getSimpleName());
+	}
+
+	public ArrayList<Kit> GET_KITS_FOR_RACE_AND_BASECLASS(int raceID, int baseClassID) {
+		prepareCallable("SELECT vk.* FROM `static_rune_validkit` vk, `static_rune_racebaseclass` rbc WHERE rbc.`RaceID` = ? "
+				+ "&& rbc.`BaseClassID` = ? && rbc.`ID` = vk.`RaceBaseClassesID`");
+		setInt(1, raceID);
+		setInt(2, baseClassID);
+		return getObjectList();
+	}
+
+	public ArrayList<Kit> GET_ALL_KITS() {
+		prepareCallable("SELECT * FROM `static_rune_validkit`");
+
+		return getObjectList();
+	}
+}
diff --git a/src/engine/db/handlers/dbLootTableHandler.java b/src/engine/db/handlers/dbLootTableHandler.java
new file mode 100644
index 00000000..cab88c5d
--- /dev/null
+++ b/src/engine/db/handlers/dbLootTableHandler.java
@@ -0,0 +1,233 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.db.handlers;
+
+import engine.loot.LootGroup;
+import engine.loot.LootManager;
+import engine.loot.ModifierGroup;
+import engine.loot.ModifierTable;
+import engine.objects.Item;
+import engine.objects.LootTable;
+import org.pmw.tinylog.Logger;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+public class dbLootTableHandler extends dbHandlerBase {
+
+    public dbLootTableHandler() {
+
+    }
+
+
+    public void populateLootGroups() {
+        int recordsRead = 0;
+        prepareCallable("SELECT `groupID`, `minRoll`, `maxRoll`, `lootTableID`, `pModTableID`, `sModTableID` FROM `static_lootgroups`");
+        
+        try {
+            ResultSet rs = executeQuery();
+            if (rs != null)
+                while (rs.next()) {
+                    recordsRead++;
+                    LootTable lootTable = LootTable.getLootGroup(rs.getInt("groupID"));
+                    lootTable.addRow(rs.getFloat("minRoll"), rs.getFloat("maxRoll"), rs.getInt("lootTableID"), rs.getInt("pModTableID"), rs.getInt("sModTableID"), "");
+                }
+            
+            Logger.info("read: " + recordsRead + " cached: " + LootTable.getLootGroups().size());
+        } catch (SQLException e) {
+        } finally {
+            closeCallable();
+        }
+    }
+
+    public void populateLootTables() {
+        int recordsRead = 0;
+        
+        prepareCallable("SELECT `lootTable`, `minRoll`, `maxRoll`, `itemBaseUUID`, `minSpawn`, `maxSpawn` FROM `static_loottables`");
+        
+        try {
+            ResultSet rs = executeQuery();
+            if (rs != null)
+                while (rs.next()) {
+                    recordsRead++;
+                    LootTable lootTable = LootTable.getLootTable(rs.getInt("lootTable"));
+                    lootTable.addRow(rs.getFloat("minRoll"), rs.getFloat("maxRoll"), rs.getInt("itemBaseUUID"), rs.getInt("minSpawn"), rs.getInt("maxSpawn"), "");
+                }
+            
+             Logger.info("read: " + recordsRead + " cached: " + LootTable.getLootTables().size());
+        } catch (SQLException e) {
+        } finally {
+            closeCallable();
+        }
+    }
+
+    public void populateModTables() {
+        
+        int recordsRead = 0;
+                
+        prepareCallable("SELECT `modTable`,`minRoll`,`maxRoll`,`value`,`action` FROM `static_modtables`");
+        
+        try {
+            ResultSet rs = executeQuery();
+            if (rs != null)
+                while (rs.next()) {
+                    recordsRead++;
+                    LootTable lootTable = LootTable.getModTable(rs.getInt("modTable"));
+                    lootTable.addRow(rs.getFloat("minRoll"), rs.getFloat("maxRoll"), rs.getInt("value"), 0, 0, rs.getString("action"));
+                }
+            Logger.info("read: " + recordsRead + " cached: " + LootTable.getModTables().size());
+        } catch (SQLException e) {
+        } finally {
+            closeCallable();
+        }
+    }
+
+    public void populateModGroups() {
+        
+        int recordsRead = 0;
+        
+        prepareCallable("SELECT `modGroup`,`minRoll`,`maxRoll`,`subTableID` FROM `static_modgroups`");
+        
+        try {
+            ResultSet rs = executeQuery();
+            if (rs != null)
+                while (rs.next()) {
+                    recordsRead++;
+                    LootTable lootTable = LootTable.getModGroup(rs.getInt("modGroup"));
+                    lootTable.addRow(rs.getFloat("minRoll"), rs.getFloat("maxRoll"), rs.getInt("subTableID"), 0, 0, "");
+                }
+            Logger.info("read: " + recordsRead + " cached: " + LootTable.getModGroups().size());
+        } catch (SQLException e) {
+        } finally {
+            closeCallable();
+        }
+    }
+
+    public void LOAD_ENCHANT_VALUES() {
+        
+        prepareCallable("SELECT `IDString`, `minMod` FROM `static_power_effectmod` WHERE `modType` = ?");
+        setString(1,"Value");
+        
+        try {
+            ResultSet rs = executeQuery();
+            while (rs.next()) {
+                Item.addEnchantValue(rs.getString("IDString"), rs.getInt("minMod"));
+            }
+        } catch (SQLException e) {
+            Logger.error( e);
+        } finally {
+            closeCallable();
+        }
+    }
+    
+    public void LOAD_ALL_LOOTGROUPS() {
+        
+            LootGroup lootGroup;
+            int recordsRead = 0;
+            
+		prepareCallable("SELECT * FROM static_lootgroups");
+
+		try {
+			ResultSet rs = executeQuery();
+                        
+			while (rs.next()) {
+                            
+                          recordsRead++;
+                          lootGroup = new LootGroup(rs);
+                          LootManager.addLootGroup(lootGroup);
+			}
+                        
+                        Logger.info( "read: " + recordsRead);
+                                
+		} catch (SQLException e) {
+			Logger.error( e.getErrorCode() + ' ' + e.getMessage(), e);
+		} finally {
+			closeCallable();
+		}
+	}
+    
+        public void LOAD_ALL_LOOTTABLES() {
+        
+            engine.loot.LootTable lootTable;
+            int recordsRead = 0;
+            
+		prepareCallable("SELECT * FROM static_loottables");
+
+		try {
+			ResultSet rs = executeQuery();
+                        
+			while (rs.next()) {
+                            
+                          recordsRead++;
+                          lootTable = new engine.loot.LootTable(rs);
+                          LootManager.addLootTable(lootTable);
+			}
+                        
+                        Logger.info("read: " + recordsRead);
+                                
+		} catch (SQLException e) {
+			Logger.error( e.getErrorCode() + ' ' + e.getMessage(), e);
+		} finally {
+			closeCallable();
+		}
+	}
+        
+        public void LOAD_ALL_MODGROUPS() {
+        
+            ModifierGroup modGroup;
+            int recordsRead = 0;
+            
+		prepareCallable("SELECT * FROM static_modgroups");
+
+		try {
+			ResultSet rs = executeQuery();
+                        
+			while (rs.next()) {
+                            
+                          recordsRead++;
+                          modGroup = new ModifierGroup(rs);
+                          LootManager.addModifierGroup(modGroup);
+			}
+                        
+                        Logger.info( "read: " + recordsRead);
+                                
+		} catch (SQLException e) {
+			Logger.error(e.getErrorCode() + ' ' + e.getMessage(), e);
+		} finally {
+			closeCallable();
+		}
+	}
+        
+        public void LOAD_ALL_MODTABLES() {
+        
+            ModifierTable modTable;
+            int recordsRead = 0;
+            
+		prepareCallable("SELECT * FROM static_modtables");
+
+		try {
+			ResultSet rs = executeQuery();
+                        
+			while (rs.next()) {
+                            
+                          recordsRead++;
+                          modTable = new ModifierTable(rs);
+                          LootManager.addModifierTable(modTable);
+			}
+                        
+                        Logger.info( "read: " + recordsRead);
+                                
+		} catch (SQLException e) {
+			Logger.error( e.getErrorCode() + ' ' + e.getMessage(), e);
+		} finally {
+			closeCallable();
+		}
+	}
+}
diff --git a/src/engine/db/handlers/dbMenuHandler.java b/src/engine/db/handlers/dbMenuHandler.java
new file mode 100644
index 00000000..426f4c4a
--- /dev/null
+++ b/src/engine/db/handlers/dbMenuHandler.java
@@ -0,0 +1,28 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.db.handlers;
+
+import engine.objects.MenuOption;
+
+import java.util.ArrayList;
+
+public class dbMenuHandler extends dbHandlerBase {
+
+	public dbMenuHandler() {
+		this.localClass = MenuOption.class;
+        this.localObjectType = engine.Enum.GameObjectType.valueOf(this.localClass.getSimpleName());
+	}
+
+	public ArrayList<MenuOption> GET_MENU_OPTIONS(final int id) {
+		prepareCallable("SELECT * FROM `static_npc_menuoption` WHERE menuID = ?");
+		setInt(1, id);
+		return getObjectList();
+	}
+}
diff --git a/src/engine/db/handlers/dbMineHandler.java b/src/engine/db/handlers/dbMineHandler.java
new file mode 100644
index 00000000..acc8384b
--- /dev/null
+++ b/src/engine/db/handlers/dbMineHandler.java
@@ -0,0 +1,117 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+
+
+
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.db.handlers;
+
+import engine.Enum;
+import engine.gameManager.DbManager;
+import engine.objects.Mine;
+import engine.objects.MineProduction;
+import engine.objects.Resource;
+
+import java.time.LocalDateTime;
+import java.util.ArrayList;
+
+public class dbMineHandler extends dbHandlerBase {
+
+	public dbMineHandler() {
+		this.localClass = Mine.class;
+		this.localObjectType = Enum.GameObjectType.valueOf(this.localClass.getSimpleName());
+	}
+
+	public Mine GET_MINE(int id) {
+
+		if (id == 0)
+			return null;
+
+		Mine mine = (Mine) DbManager.getFromCache(Enum.GameObjectType.Mine, id);
+		if (mine != null)
+			return mine;
+
+		prepareCallable("SELECT `obj_building`.*, `object`.`parent` FROM `object` INNER JOIN `obj_building` ON `obj_building`.`UID` = `object`.`UID` WHERE `object`.`UID` = ?;");
+
+		setLong(1, (long) id);
+		return (Mine) getObjectSingle(id);
+
+	}
+
+	public ArrayList<Mine> GET_ALL_MINES_FOR_SERVER() {
+		prepareCallable("SELECT `obj_mine`.*, `object`.`parent` FROM `object` INNER JOIN `obj_mine` ON `obj_mine`.`UID` = `object`.`UID`");
+		return getObjectList();
+	}
+
+	public boolean CHANGE_OWNER(Mine mine, int playerUID) {
+		prepareCallable("UPDATE `obj_mine` SET `mine_ownerUID`=? WHERE `UID`=?");
+		setInt(1, playerUID);
+		setLong(2, (long) mine.getObjectUUID());
+		return (executeUpdate() > 0);
+	}
+
+	public boolean CHANGE_RESOURCE(Mine mine, Resource resource) {
+		prepareCallable("UPDATE `obj_mine` SET `mine_resource`=? WHERE `UID`=?");
+		setString(1, resource.name());
+		setLong(2, (long) mine.getObjectUUID());
+		return (executeUpdate() > 0);
+	}
+
+	public boolean CHANGE_TYPE(Mine mine, MineProduction productionType) {
+		prepareCallable("UPDATE `obj_mine` SET `mine_type`=? WHERE `UID`=?");
+		setString(1, productionType.name());
+		setLong(2, (long) mine.getObjectUUID());
+		return (executeUpdate() > 0);
+	}
+
+	public boolean CHANGE_MINE_TIME(Mine mine, LocalDateTime mineOpenTime) {
+		prepareCallable("UPDATE `obj_mine` SET `mine_openDate`=? WHERE `UID`=?");
+		setLocalDateTime(1, mineOpenTime);
+		setLong(2, (long) mine.getObjectUUID());
+		return (executeUpdate() > 0);
+	}
+
+	public boolean SET_FLAGS(Mine mine, int newFlags) {
+		prepareCallable("UPDATE `obj_mine` SET `flags`=? WHERE `UID`=?");
+		setInt(1, newFlags);
+		setLong(2, (long) mine.getObjectUUID());
+		return (executeUpdate() > 0);
+	}
+
+	public String SET_PROPERTY(final Mine m, String name, Object new_value) {
+		prepareCallable("CALL mine_SETPROP(?,?,?)");
+		setLong(1, (long) m.getObjectUUID());
+		setString(2, name);
+		setString(3, String.valueOf(new_value));
+		return getResult();
+	}
+
+	// Advance all the mine windows respective to the current day
+	// at boot time.  This ensures that mines always go live
+	// no matter what date in the database
+
+	public String SET_PROPERTY(final Mine m, String name, Object new_value, Object old_value) {
+		prepareCallable("CALL mine_GETSETPROP(?,?,?,?)");
+		setLong(1, (long) m.getObjectUUID());
+		setString(2, name);
+		setString(3, String.valueOf(new_value));
+		setString(4, String.valueOf(old_value));
+		return getResult();
+	}
+
+}
diff --git a/src/engine/db/handlers/dbMobBaseHandler.java b/src/engine/db/handlers/dbMobBaseHandler.java
new file mode 100644
index 00000000..33f98990
--- /dev/null
+++ b/src/engine/db/handlers/dbMobBaseHandler.java
@@ -0,0 +1,351 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.db.handlers;
+
+import engine.Enum.GameObjectType;
+import engine.gameManager.DbManager;
+import engine.objects.*;
+import engine.server.MBServerStatics;
+import org.pmw.tinylog.Logger;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.HashMap;
+
+public class dbMobBaseHandler extends dbHandlerBase {
+
+	public dbMobBaseHandler() {
+		this.localClass = MobBase.class;
+		this.localObjectType = engine.Enum.GameObjectType.valueOf(this.localClass.getSimpleName());
+	}
+
+    public MobBase GET_MOBBASE(int id, boolean forceDB) {
+
+
+		if (id == 0)
+			return null;
+
+		MobBase mobBase = (MobBase) DbManager.getFromCache(GameObjectType.MobBase, id);
+
+		if ( mobBase != null)
+			return mobBase;
+
+		prepareCallable("SELECT * FROM `static_npc_mobbase` WHERE `ID`=?");
+		setInt(1, id);
+		return (MobBase) getObjectSingle(id, forceDB, true);
+	}
+
+	public ArrayList<MobBase> GET_ALL_MOBBASES() {
+		prepareCallable("SELECT * FROM `static_npc_mobbase`;");
+		return  getObjectList();
+	}
+
+	public void SET_AI_DEFAULTS() {
+		prepareCallable("SELECT * FROM `static_ai_defaults`");
+		try {
+			ResultSet rs = executeQuery();
+			while (rs.next()) {
+				MBServerStatics.AI_BASE_AGGRO_RANGE = rs.getInt("aggro_range");
+				MBServerStatics.AI_PATROL_DIVISOR = rs.getInt("patrol_chance");
+				MBServerStatics.AI_DROP_AGGRO_RANGE = rs.getInt("drop_aggro_range");
+				MBServerStatics.AI_POWER_DIVISOR = rs.getInt("cast_chance");
+				MBServerStatics.AI_RECALL_RANGE = rs.getInt("recall_range");
+				MBServerStatics.AI_PET_HEEL_DISTANCE = rs.getInt("pet_heel_distance");
+			}
+			rs.close();
+		} catch (SQLException e) {
+			Logger.error( e.getMessage());
+		} finally {
+			closeCallable();
+		}
+
+	}
+
+	public boolean UPDATE_AI_DEFAULTS() {
+		prepareCallable("UPDATE `static_ai_defaults` SET `aggro_range` = ?,`patrol_chance`= ?,`drop_aggro_range`= ?,`cast_chance`= ?,`recall_range`= ? WHERE `ID` = 1");
+		setInt(1, MBServerStatics.AI_BASE_AGGRO_RANGE);
+		setInt(2, MBServerStatics.AI_PATROL_DIVISOR);
+		setInt(3, MBServerStatics.AI_DROP_AGGRO_RANGE);
+		setInt(4, MBServerStatics.AI_POWER_DIVISOR);
+		setInt(5, MBServerStatics.AI_RECALL_RANGE);
+		return (executeUpdate() > 0);
+
+	}
+
+	public boolean UPDATE_FLAGS(int mobBaseID, long flags) {
+		prepareCallable("UPDATE `static_npc_mobbase` SET `flags` = ? WHERE `ID` = ?");
+		setLong(1, flags);
+		setInt(2, mobBaseID);
+		return (executeUpdate() > 0);
+
+	}
+
+	public HashMap<Integer, Integer> LOAD_STATIC_POWERS(int mobBaseUUID) {
+		HashMap<Integer, Integer> powersList = new HashMap<>();
+		prepareCallable("SELECT * FROM `static_npc_mobbase_powers` WHERE `mobbaseUUID`=?");
+		setInt(1, mobBaseUUID);
+		try {
+			ResultSet rs = executeQuery();
+			while (rs.next()) {
+
+				powersList.put(rs.getInt("token"), rs.getInt("rank"));
+			}
+			rs.close();
+		} catch (SQLException e) {
+			Logger.error( e.getMessage());
+		} finally {
+			closeCallable();
+		}
+		return powersList;
+
+	}
+
+	public ArrayList<MobBaseEffects> LOAD_STATIC_EFFECTS(int mobBaseUUID) {
+		ArrayList<MobBaseEffects> effectsList = new ArrayList<>();
+
+		prepareCallable("SELECT * FROM `static_npc_mobbase_effects` WHERE `mobbaseUUID` = ?");
+		setInt(1, mobBaseUUID);
+
+		try {
+			ResultSet rs = executeQuery();
+			while (rs.next()) {
+				MobBaseEffects mbs = new MobBaseEffects(rs);
+				effectsList.add(mbs);
+			}
+			rs.close();
+		} catch (SQLException e) {
+			Logger.error( e.getMessage());
+		} finally {
+			closeCallable();
+		}
+		return effectsList;
+
+	}
+
+	public ArrayList<MobBaseEffects> GET_RUNEBASE_EFFECTS(int runeID) {
+		ArrayList<MobBaseEffects> effectsList = new ArrayList<>();
+		prepareCallable("SELECT * FROM `static_npc_mobbase_effects` WHERE `mobbaseUUID` = ?");
+		setInt(1, runeID);
+
+		try {
+			ResultSet rs = executeQuery();
+			while (rs.next()) {
+
+				MobBaseEffects mbs = new MobBaseEffects(rs);
+				effectsList.add(mbs);
+			}
+			rs.close();
+		} catch (SQLException e) {
+			Logger.error (e.getMessage());
+		} finally {
+			closeCallable();
+		}
+
+		return effectsList;
+
+	}
+
+	public MobBaseStats LOAD_STATS(int mobBaseUUID) {
+		MobBaseStats mbs = MobBaseStats.GetGenericStats();
+
+		prepareCallable("SELECT * FROM `static_npc_mobbase_stats` WHERE `mobbaseUUID` = ?");
+		setInt(1, mobBaseUUID);
+		try {
+			ResultSet rs = executeQuery();
+			while (rs.next()) {
+
+				mbs = new MobBaseStats(rs);
+			}
+
+		} catch (SQLException e) {
+			Logger.error(e.getErrorCode() + ' ' + e.getMessage(), e);
+		} finally {
+			closeCallable();
+		}
+		return mbs;
+
+	}
+
+	public ArrayList<RuneBase> LOAD_RUNES_FOR_MOBBASE(int mobBaseUUID) {
+
+		ArrayList<RuneBase> runes = new ArrayList<>();
+		prepareCallable("SELECT * FROM `static_npc_mobbase_runes` WHERE `mobbaseUUID` = ?");
+		setInt(1, mobBaseUUID);
+		try {
+			ResultSet rs = executeQuery();
+			while (rs.next()) {
+				int runeID = rs.getInt("runeID");
+				RuneBase rune = RuneBase.getRuneBase(runeID);
+				runes.add(rune);
+			}
+
+		} catch (SQLException e) {
+			Logger.error(e.getErrorCode() + ' ' + e.getMessage(), e);
+		} finally {
+			closeCallable();
+		}
+		return runes;
+
+	}
+
+	public boolean ADD_MOBBASE_EFFECT(int mobBaseUUID, int token, int rank, int reqLvl) {
+		prepareCallable("INSERT INTO `static_npc_mobbase_effects` (`mobbaseUUID`, `token`, `rank`, `reqLvl`) VALUES (?, ?, ?, ?);");
+		setInt(1, mobBaseUUID);
+		setInt(2, token);
+		setInt(3, rank);
+		setInt(4, reqLvl);
+		return (executeUpdate() > 0);
+	}
+
+	public boolean ADD_MOBBASE_POWER(int mobBaseUUID, int token, int rank) {
+		prepareCallable("INSERT INTO `static_npc_mobbase_powers` (`mobbaseUUID`, `token`, `rank`) VALUES (?, ?, ?);");
+		setInt(1, mobBaseUUID);
+		setInt(2, token);
+		setInt(3, rank);
+		return (executeUpdate() > 0);
+	}
+
+	public boolean UPDATE_SKILLS(int ID, int skillsID) {
+		prepareCallable("UPDATE `static_npc_mobbase` SET `baseSkills`=? WHERE `ID`=?;");
+		setInt(1, skillsID);
+		setInt(2, ID);
+		return (executeUpdate() > 0);
+	}
+
+	public boolean ADD_MOBBASE_RUNE(int mobBaseUUID, int runeID) {
+		prepareCallable("INSERT INTO `static_npc_mobbase_runes` (`mobbaseUUID`, `runeID`) VALUES (?, ?);");
+		setInt(1, mobBaseUUID);
+		setInt(2, runeID);
+		return (executeUpdate() > 0);
+	}
+
+	public MobBase COPY_MOBBASE(MobBase toAdd, String name) {
+		prepareCallable("INSERT INTO `static_npc_mobbase` (`loadID`, `lootTableID`, `name`, `level`, `health`, `atr`, `defense`, `minDmg`,`maxDmg`, `goldMod`, `seeInvis`, `flags`, `noaggro`, `spawntime`) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);");
+		setInt(1, toAdd.getLoadID());
+		setInt(2, toAdd.getLootTable());
+		setString(3, (name.length() > 0) ? name : toAdd.getFirstName());
+		setInt(4, toAdd.getLevel());
+		setFloat(5, toAdd.getHealthMax());
+		setInt(5, toAdd.getAtr());
+		setInt(6, toAdd.getDefense());
+		setFloat(7, toAdd.getMinDmg());
+		setFloat(8, toAdd.getMaxDmg());
+		setInt(9, toAdd.getGoldMod());
+		setInt(10, toAdd.getSeeInvis());
+		setLong(11, toAdd.getFlags().toLong());
+		setLong(12, toAdd.getNoAggro().toLong());
+		setInt(13, toAdd.getSpawnTime());
+		int objectUUID = insertGetUUID();
+		if (objectUUID > 0)
+			return GET_MOBBASE(objectUUID, true);
+		return null;
+	}
+
+	public boolean RENAME_MOBBASE(int ID, String newName) {
+		prepareCallable("UPDATE `static_npc_mobbase` SET `name`=? WHERE `ID`=?;");
+		setString(1, newName);
+		setInt(2, ID);
+		return (executeUpdate() > 0);
+	}
+
+
+	public void LOAD_ALL_MOBBASE_LOOT(int mobBaseID) {
+
+		if (mobBaseID == 0)
+			return;
+		ArrayList<MobLootBase> mobLootList = new ArrayList<>();
+		prepareCallable("SELECT * FROM `static_mob_loottable` WHERE `mobBaseID` = ?");
+		setInt(1,mobBaseID);
+
+		try {
+			ResultSet rs = executeQuery();
+
+			//shrines cached in rs for easy cache on creation.
+			while (rs.next()) {
+
+				MobLootBase mobLootBase = new MobLootBase(rs);
+				mobLootList.add(mobLootBase);
+
+			}
+
+			MobLootBase.MobLootSet.put(mobBaseID, mobLootList);
+
+		} catch (SQLException e) {
+			Logger.error( e.getErrorCode() + ' ' + e.getMessage(), e);
+		} finally {
+			closeCallable();
+		}
+
+	}
+
+	public void LOAD_ALL_MOBBASE_SPEEDS(MobBase mobBase) {
+
+		if (mobBase.getLoadID() == 0)
+			return;
+		ArrayList<MobLootBase> mobLootList = new ArrayList<>();
+		prepareCallable("SELECT * FROM `static_npc_mobbase_race` WHERE `mobbaseID` = ?");
+		setInt(1,mobBase.getLoadID());
+
+		try {
+			ResultSet rs = executeQuery();
+
+			//shrines cached in rs for easy cache on creation.
+			while (rs.next()) {
+				float walk = rs.getFloat("walkStandard");
+				float walkCombat = rs.getFloat("walkCombat");
+				float run = rs.getFloat("runStandard");
+				float runCombat = rs.getFloat("runCombat");
+				mobBase.updateSpeeds(walk, walkCombat, run, runCombat);
+			}
+
+
+		} catch (SQLException e) {
+			Logger.error(e.getErrorCode() + ' ' + e.getMessage(), e);
+		} finally {
+			closeCallable();
+		}
+
+	}
+
+	public HashMap<Integer, MobbaseGoldEntry> LOAD_GOLD_FOR_MOBBASE() {
+
+		HashMap<Integer, MobbaseGoldEntry> goldSets;
+		MobbaseGoldEntry goldSetEntry;
+		int	mobbaseID;
+
+		goldSets = new HashMap<>();
+		int recordsRead = 0;
+
+		prepareCallable("SELECT * FROM static_npc_mobbase_gold");
+
+		try {
+			ResultSet rs = executeQuery();
+
+			while (rs.next()) {
+
+				recordsRead++;
+
+				mobbaseID = rs.getInt("mobbaseID");
+				goldSetEntry = new MobbaseGoldEntry(rs);
+				goldSets.put(mobbaseID, goldSetEntry);
+
+			}
+
+			Logger.info("read: " + recordsRead + " cached: " + goldSets.size());
+
+		} catch (SQLException e) {
+			Logger.error(e.getErrorCode() + ' ' + e.getMessage(), e);
+		} finally {
+			closeCallable();
+		}
+		return goldSets;
+	}
+}
diff --git a/src/engine/db/handlers/dbMobHandler.java b/src/engine/db/handlers/dbMobHandler.java
new file mode 100644
index 00000000..c26312db
--- /dev/null
+++ b/src/engine/db/handlers/dbMobHandler.java
@@ -0,0 +1,316 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.db.handlers;
+
+import engine.ai.MobileFSM.STATE;
+import engine.math.Vector3fImmutable;
+import engine.objects.Mob;
+import engine.objects.PlayerCharacter;
+import engine.objects.Zone;
+import engine.server.MBServerStatics;
+import engine.server.world.WorldServer;
+import org.joda.time.DateTime;
+import org.pmw.tinylog.Logger;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.HashMap;
+
+public class dbMobHandler extends dbHandlerBase {
+
+	public dbMobHandler() {
+		this.localClass = Mob.class;
+		this.localObjectType = engine.Enum.GameObjectType.valueOf(this.localClass.getSimpleName());
+	}
+
+	public Mob ADD_MOB(Mob toAdd, boolean isMob)
+			 {
+		prepareCallable("CALL `mob_CREATE`(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);");
+		setLong(1, toAdd.getParentZoneID());
+		setInt(2, toAdd.getMobBaseID());
+		setInt(3, toAdd.getGuildUUID());
+		setFloat(4, toAdd.getSpawnX());
+		setFloat(5, toAdd.getSpawnY());
+		setFloat(6, toAdd.getSpawnZ());
+		setInt(7, 0);
+		setFloat(8, toAdd.getSpawnRadius());
+		setInt(9, toAdd.getTrueSpawnTime());
+		if (toAdd.getContract() != null)
+			setInt(10, toAdd.getContract().getContractID());
+		else
+			setInt(10, 0);
+		setInt(11, toAdd.getBuildingID());
+		setInt(12, toAdd.getLevel());
+		int objectUUID = (int) getUUID();
+		if (objectUUID > 0)
+			return GET_MOB(objectUUID);
+		return null;
+	}
+
+	public Mob ADD_SIEGE_MOB(Mob toAdd, boolean isMob)
+			 {
+		prepareCallable("CALL `mob_SIEGECREATE`(?, ?, ?, ?, ?, ?, ?, ?, ?, ?);");
+		setLong(1, toAdd.getParentZoneID());
+		setInt(2, toAdd.getMobBaseID());
+		setInt(3, toAdd.getGuildUUID());
+		setFloat(4, toAdd.getSpawnX());
+		setFloat(5, toAdd.getSpawnY());
+		setFloat(6, toAdd.getSpawnZ());
+		setInt(7,0);
+		setFloat(8, toAdd.getSpawnRadius());
+		setInt(9, toAdd.getTrueSpawnTime());
+		setInt(10, toAdd.getBuildingID());
+
+		int objectUUID = (int) getUUID();
+		if (objectUUID > 0)
+			return GET_MOB(objectUUID);
+		return null;
+	}
+
+	public boolean updateUpgradeTime(Mob mob, DateTime upgradeDateTime) {
+
+
+
+		try {
+
+			prepareCallable("UPDATE obj_mob SET upgradeDate=? "
+					+ "WHERE UID = ?");
+
+			if (upgradeDateTime == null)
+				setNULL(1, java.sql.Types.DATE);
+			else
+				setTimeStamp(1, upgradeDateTime.getMillis());
+
+			setInt(2, mob.getObjectUUID());
+			executeUpdate();
+		} catch (Exception e) {
+			Logger.error("Mob.updateUpgradeTime", "UUID: " + mob.getObjectUUID());
+			return false;
+		}
+		return true;
+	}
+
+	public int DELETE_MOB(final Mob mob) {
+		prepareCallable("DELETE FROM `object` WHERE `UID` = ?");
+		setLong(1, mob.getDBID());
+		return executeUpdate();
+	}
+
+	public void LOAD_PATROL_POINTS(Mob captain) {
+
+
+
+		prepareCallable("SELECT * FROM `dyn_guards` WHERE `captainUID` = ?");
+		setInt(1,captain.getObjectUUID());
+
+		try {
+			ResultSet rs = executeQuery();
+
+			//shrines cached in rs for easy cache on creation.
+			while (rs.next()) {
+				int mobBaseID = rs.getInt("mobBaseID");
+				String name = rs.getString("name");
+				Mob toCreate = captain.createGuardMob(mobBaseID, captain.getGuild(), captain.getParentZone(), captain.getBuilding().getLoc(), captain.getLevel(),name);
+				if (toCreate == null)
+					return;
+
+				//   toCreate.despawn();
+				if (toCreate != null) {
+					
+					toCreate.setTimeToSpawnSiege(System.currentTimeMillis() + MBServerStatics.FIFTEEN_MINUTES);
+					toCreate.setDeathTime(System.currentTimeMillis());
+					toCreate.setState(STATE.Respawn);
+
+				}
+			}
+
+
+		} catch (SQLException e) {
+			Logger.error( e.toString());
+		} finally {
+			closeCallable();
+		}
+
+
+
+	}
+
+	public boolean ADD_TO_GUARDS(final long captainUID, final int mobBaseID, final String name, final int slot) {
+		prepareCallable("INSERT INTO `dyn_guards` (`captainUID`, `mobBaseID`,`name`, `slot`) VALUES (?,?,?,?)");
+		setLong(1, captainUID);
+		setInt(2, mobBaseID);
+		setString(3, name);
+		setInt(4, slot);
+		return (executeUpdate() > 0);
+	}
+
+	public boolean REMOVE_FROM_GUARDS(final long captainUID, final int mobBaseID, final int slot) {
+		prepareCallable("DELETE FROM `dyn_guards` WHERE `captainUID`=? AND `mobBaseID`=? AND `slot` =?");
+		setLong(1, captainUID);
+		setInt(2, mobBaseID);
+		setInt(3,slot);
+		return (executeUpdate() > 0);
+	}
+
+
+	public ArrayList<Mob> GET_ALL_MOBS_FOR_ZONE(Zone zone) {
+		prepareCallable("SELECT `obj_mob`.*, `object`.`parent` FROM `object` INNER JOIN `obj_mob` ON `obj_mob`.`UID` = `object`.`UID` WHERE `object`.`parent` = ?;");
+		setLong(1, zone.getObjectUUID());
+		return getLargeObjectList();
+	}
+
+	public ArrayList<Mob> GET_ALL_MOBS_FOR_BUILDING(int buildingID) {
+		prepareCallable("SELECT * FROM `obj_mob` WHERE `mob_buildingID` = ?");
+		setInt(1, buildingID);
+		return getObjectList();
+	}
+
+	public ArrayList<Mob> GET_ALL_MOBS() {
+		prepareCallable("SELECT `obj_mob`.*, `object`.`parent` FROM `object` INNER JOIN `obj_mob` ON `obj_mob`.`UID` = `object`.`UID`;");
+		return getObjectList();
+	}
+
+	public Mob GET_MOB(final int objectUUID) {
+		prepareCallable("SELECT `obj_mob`.*, `object`.`parent` FROM `object` INNER JOIN `obj_mob` ON `obj_mob`.`UID` = `object`.`UID` WHERE `object`.`UID` = ?;");
+		setLong(1, objectUUID);
+		return (Mob) getObjectSingle(objectUUID);
+	}
+
+	public int MOVE_MOB(long mobID, long parentID, float locX, float locY, float locZ) {
+		prepareCallable("UPDATE `object` INNER JOIN `obj_mob` On `object`.`UID` = `obj_mob`.`UID` SET `object`.`parent`=?, `obj_mob`.`mob_spawnX`=?, `obj_mob`.`mob_spawnY`=?, `obj_mob`.`mob_spawnZ`=? WHERE `obj_mob`.`UID`=?;");
+		setLong(1, parentID);
+		setFloat(2, locX);
+		setFloat(3, locY);
+		setFloat(4, locZ);
+		setLong(5, mobID);
+		return executeUpdate();
+	}
+
+	public boolean UPDATE_MOB_BUILDING(int buildingID, int mobID) {
+		prepareCallable("UPDATE `object` INNER JOIN `obj_mob` On `object`.`UID` = `obj_mob`.`UID` SET  `obj_mob`.`mob_buildingID`=? WHERE `obj_mob`.`UID`=?;");
+		setInt(1, buildingID);
+		setInt(2, mobID);
+		return (executeUpdate() > 0);
+	}
+
+	public String SET_PROPERTY(final Mob m, String name, Object new_value) {
+		prepareCallable("CALL mob_SETPROP(?,?,?)");
+		setLong(1, m.getObjectUUID());
+		setString(2, name);
+		setString(3, String.valueOf(new_value));
+		return getResult();
+	}
+
+	public String SET_PROPERTY(final Mob m, String name, Object new_value, Object old_value) {
+		prepareCallable("CALL mob_GETSETPROP(?,?,?,?)");
+		setLong(1, m.getObjectUUID());
+		setString(2, name);
+		setString(3, String.valueOf(new_value));
+		setString(4, String.valueOf(old_value));
+		return getResult();
+	}
+
+
+	public static boolean COPY_ZONE_MOBILES(PlayerCharacter pc, Zone sourceZone, Zone targetZone) {
+
+		ArrayList<Mob> sourceMobList;
+		Vector3fImmutable worldDelta;
+		Mob newMobile;
+
+		// Sanity check.  Can't copy a non existent zone
+
+		if ((sourceZone == null) || (targetZone == null))
+			return false;
+
+		// Generate collections for all buildings in each zone
+
+
+		for (Mob mobile : sourceZone.zoneMobSet) {
+
+			// Calculate world coordinate offset between zones
+
+			worldDelta = new Vector3fImmutable(targetZone.getAbsX(), targetZone.getAbsY(), targetZone.getAbsZ());
+			worldDelta = worldDelta.subtract(new Vector3fImmutable(sourceZone.getAbsX(), sourceZone.getAbsY(), sourceZone.getAbsZ()));
+
+			newMobile = Mob.createMob(mobile.getLoadID(),
+					mobile.getLoc().add(worldDelta), null, true, targetZone, mobile.getBuilding(), 0);
+
+			if (newMobile != null) {
+				newMobile.updateDatabase();
+			}
+
+		}
+
+		return true;
+	}
+
+
+	public void LOAD_RUNES_FOR_FIDELITY_MOBS() {
+
+
+
+
+
+		prepareCallable("SELECT static_zone_npc.npcID,static_zone_npc.loadNum, static_zone_npc.classID, static_zone_npc.professionID, static_zone_npc.extraRune, static_zone_npc.extraRune2 FROM static_zone_npc ; ");
+
+		try {
+			ResultSet rs = executeQuery();
+
+			while (rs.next()) {
+
+
+				int loadNum = rs.getInt("loadNum");
+				int fidelityID = rs.getInt("npcID");
+				int classID = rs.getInt("classID");
+				int professionID = rs.getInt("professionID");
+				int extraRune = rs.getInt("extraRune");
+				int extraRune2 = rs.getInt("extraRune2");
+
+				if (WorldServer.ZoneFidelityMobRunes.get(loadNum) == null)
+					WorldServer.ZoneFidelityMobRunes.put(loadNum, new HashMap<>());
+				ArrayList<Integer> runeList;
+				if (WorldServer.ZoneFidelityMobRunes.get(loadNum).get(fidelityID) == null){
+					runeList = new ArrayList<>(4);
+				}else
+					runeList = WorldServer.ZoneFidelityMobRunes.get(loadNum).get(fidelityID);
+
+
+
+				if (classID != 0)
+					runeList.add(classID);
+				if (professionID != 0)
+					runeList.add(professionID);
+				if(extraRune != 0)
+					runeList.add(extraRune);
+
+				if (extraRune2 != 0)
+					runeList.add(extraRune2);
+
+				WorldServer.ZoneFidelityMobRunes.get(loadNum).put(fidelityID, runeList);
+
+
+			}
+
+			rs.close();
+
+
+
+		} catch (SQLException e) {
+			Logger.error( e.toString());
+		} finally {
+			closeCallable();
+
+		}
+
+	}
+
+
+}
diff --git a/src/engine/db/handlers/dbNPCHandler.java b/src/engine/db/handlers/dbNPCHandler.java
new file mode 100644
index 00000000..cc4623e9
--- /dev/null
+++ b/src/engine/db/handlers/dbNPCHandler.java
@@ -0,0 +1,397 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.db.handlers;
+
+import engine.Enum.ProfitType;
+import engine.objects.*;
+import org.joda.time.DateTime;
+import org.pmw.tinylog.Logger;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.HashMap;
+
+public class dbNPCHandler extends dbHandlerBase {
+
+	public dbNPCHandler() {
+		this.localClass = NPC.class;
+		this.localObjectType = engine.Enum.GameObjectType.valueOf(this.localClass.getSimpleName());
+	}
+
+	public NPC ADD_NPC(NPC toAdd, boolean isMob) {
+		prepareCallable("CALL `npc_CREATE`(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);");
+		setLong(1, toAdd.getParentZoneID());
+		setString(2, toAdd.getName());
+		setInt(3, toAdd.getContractID());
+		setInt(4, toAdd.getGuildUUID());
+		setFloat(5, toAdd.getSpawnX());
+		setFloat(6, toAdd.getSpawnY());
+		setFloat(7, toAdd.getSpawnZ());
+		setInt(8, toAdd.getLevel());
+		setFloat(9, toAdd.getBuyPercent());
+		setFloat(10, toAdd.getSellPercent());
+		if (toAdd.getBuilding() != null) {
+			setInt(11, toAdd.getBuilding().getObjectUUID());
+		} else {
+			setInt(11, 0);
+		}
+
+		int objectUUID = (int) getUUID();
+		if (objectUUID > 0) {
+			return GET_NPC(objectUUID);
+		}
+		return null;
+	}
+
+	public int DELETE_NPC(final NPC npc) {
+		if (npc.isStatic()) {
+			return DELETE_STATIC_NPC(npc);
+		}
+
+		npc.removeFromZone();
+		prepareCallable("DELETE FROM `object` WHERE `UID` = ?");
+		setLong(1, (long) npc.getDBID());
+		return executeUpdate();
+	}
+
+	private int DELETE_STATIC_NPC(final NPC npc) {
+		npc.removeFromZone();
+		prepareCallable("DELETE FROM `_init_npc` WHERE `ID` = ?");
+		setInt(1, npc.getDBID());
+		return executeUpdate();
+	}
+
+	public ArrayList<NPC> GET_ALL_NPCS_FOR_ZONE(Zone zone) {
+		prepareCallable("SELECT `obj_npc`.*, `object`.`parent` FROM `object` INNER JOIN `obj_npc` ON `obj_npc`.`UID` = `object`.`UID` WHERE `object`.`parent` = ?;");
+		setLong(1, (long) zone.getObjectUUID());
+		return getLargeObjectList();
+	}
+
+	public ArrayList<NPC> GET_ALL_NPCS() {
+		prepareCallable("SELECT `obj_npc`.*, `object`.`parent` FROM `object` INNER JOIN `obj_npc` ON `obj_npc`.`UID` = `object`.`UID`;");
+		
+		return getObjectList();
+	}
+
+	public ArrayList<NPC> GET_NPCS_BY_BUILDING(final int buildingID) {
+		prepareCallable("SELECT `obj_npc`.*, `object`.`parent` FROM `obj_npc` INNER JOIN `object` ON `obj_npc`.`UID` = `object`.`UID` WHERE `npc_buildingID` = ? LIMIT 3");
+		setInt(1, buildingID);
+		return getObjectList();
+	}
+
+	public NPC GET_NPC(final int objectUUID) {
+		prepareCallable("SELECT `obj_npc`.*, `object`.`parent` FROM `object` INNER JOIN `obj_npc` ON `obj_npc`.`UID` = `object`.`UID` WHERE `object`.`UID` = ?;");
+		setLong(1, (long) objectUUID);
+		return (NPC) getObjectSingle(objectUUID);
+	}
+
+	public int MOVE_NPC(long npcID, long parentID, float locX, float locY, float locZ) {
+		prepareCallable("UPDATE `object` INNER JOIN `obj_npc` On `object`.`UID` = `obj_npc`.`UID` SET `object`.`parent`=?, `obj_npc`.`npc_spawnX`=?, `obj_npc`.`npc_spawnY`=?, `obj_npc`.`npc_spawnZ`=? WHERE `obj_npc`.`UID`=?;");
+		setLong(1, parentID);
+		setFloat(2, locX);
+		setFloat(3, locY);
+		setFloat(4, locZ);
+		setLong(5, npcID);
+		return executeUpdate();
+	}
+
+
+	public String SET_PROPERTY(final NPC n, String name, Object new_value) {
+		prepareCallable("CALL npc_SETPROP(?,?,?)");
+		setLong(1, (long) n.getDBID());
+		setString(2, name);
+		setString(3, String.valueOf(new_value));
+		return getResult();
+	}
+
+	public String SET_PROPERTY(final NPC n, String name, Object new_value, Object old_value) {
+		prepareCallable("CALL npc_GETSETPROP(?,?,?,?)");
+		setLong(1, (long) n.getDBID());
+		setString(2, name);
+		setString(3, String.valueOf(new_value));
+		setString(4, String.valueOf(old_value));
+		return getResult();
+	}
+
+	public void updateDatabase(final NPC npc) {
+		prepareCallable("UPDATE obj_npc SET npc_name=?, npc_contractID=?, npc_typeID=?, npc_guildID=?,"
+				+ " npc_spawnX=?, npc_spawnY=?, npc_spawnZ=?, npc_level=? ,"
+				+ " npc_buyPercent=?, npc_sellPercent=?, npc_buildingID=? WHERE UID = ?");
+		setString(1, npc.getName());
+		setInt(2, (npc.getContract() != null) ? npc.getContract().getObjectUUID() : 0);
+		setInt(3, 0);
+		setInt(4, (npc.getGuild() != null) ? npc.getGuild().getObjectUUID() : 0);
+		setFloat(5, npc.getBindLoc().x);
+		setFloat(6, npc.getBindLoc().y);
+		setFloat(7, npc.getBindLoc().z);
+		setShort(8, npc.getLevel());
+		setFloat(9, npc.getBuyPercent());
+		setFloat(10, npc.getSellPercent());
+		setInt(11, (npc.getBuilding() != null) ? npc.getBuilding().getObjectUUID() : 0);
+		setInt(12, npc.getDBID());
+		executeUpdate();
+	}
+
+	public boolean updateUpgradeTime(NPC npc, DateTime upgradeDateTime) {
+
+
+
+		try {
+
+			prepareCallable("UPDATE obj_npc SET upgradeDate=? "
+					+ "WHERE UID = ?");
+
+			if (upgradeDateTime == null)
+				setNULL(1, java.sql.Types.DATE);
+			else
+				setTimeStamp(1, upgradeDateTime.getMillis());
+
+			setInt(2, npc.getObjectUUID());
+			executeUpdate();
+		} catch (Exception e) {
+			Logger.error("UUID: " + npc.getObjectUUID());
+			return false;
+		}
+		return true;
+	}
+
+	public boolean UPDATE_BUY_PROFIT(NPC npc,float percent) {
+		prepareCallable("UPDATE `obj_npc` SET `npc_buyPercent`=? WHERE `UID`=?");
+		setFloat(1, percent);
+		setLong(2, npc.getObjectUUID());
+		return (executeUpdate() > 0);
+	}
+
+	public boolean UPDATE_SELL_PROFIT(NPC npc,float percent) {
+		prepareCallable("UPDATE `obj_npc` SET `npc_sellPercent`=? WHERE `UID`=?");
+		setFloat(1, percent);
+		setLong(2, npc.getObjectUUID());
+		return (executeUpdate() > 0);
+	}
+
+	public boolean UPDATE_SLOT(NPC npc,int slot) {
+		prepareCallable("UPDATE `obj_npc` SET `npc_slot`=? WHERE `UID`=?");
+		setFloat(1, slot);
+		setLong(2, npc.getObjectUUID());
+		return (executeUpdate() > 0);
+	}
+
+	public boolean UPDATE_MOBBASE(NPC npc, int mobBaseID) {
+		prepareCallable("UPDATE `obj_npc` SET `npc_raceID`=? WHERE `UID`=?");
+		setLong(1, mobBaseID);
+		setLong(2, npc.getObjectUUID());
+		return (executeUpdate() > 0);
+	}
+
+	public boolean UPDATE_EQUIPSET(NPC npc, int equipSetID) {
+		prepareCallable("UPDATE `obj_npc` SET `equipsetID`=? WHERE `UID`=?");
+		setInt(1, equipSetID);
+		setLong(2, npc.getObjectUUID());
+		return (executeUpdate() > 0);
+	}
+	
+	public boolean UPDATE_NAME(NPC npc,String name) {
+		prepareCallable("UPDATE `obj_npc` SET `npc_name`=? WHERE `UID`=?");
+		setString(1, name);
+		setLong(2, npc.getObjectUUID());
+		return (executeUpdate() > 0);
+	}
+
+	public void LOAD_PIRATE_NAMES() {
+
+		String pirateName;
+		int mobBase;
+		int recordsRead = 0;
+
+		prepareCallable("SELECT * FROM static_piratenames");
+
+		try {
+			ResultSet rs = executeQuery();
+
+			while (rs.next()) {
+
+				recordsRead++;
+				mobBase = rs.getInt("mobbase");
+				pirateName = rs.getString("first_name");
+
+				// Handle new mobbbase entries
+
+				if (NPC._pirateNames.get(mobBase) == null) {
+					NPC._pirateNames.putIfAbsent(mobBase, new ArrayList<>());
+					}
+
+				// Insert name into proper arraylist
+
+				NPC._pirateNames.get(mobBase).add(pirateName);
+
+			}
+
+			Logger.info("names read: " + recordsRead + " for "
+			             + NPC._pirateNames.size() + " mobBases");
+
+		} catch (SQLException e) {
+			Logger.error(e.getErrorCode() + ' ' + e.getMessage(), e);
+		} finally {
+			closeCallable();
+		}
+	}
+	
+	public void LOAD_RUNES_FOR_FIDELITY_NPC(NPC npc) {
+
+
+
+
+
+		prepareCallable("SELECT static_zone_npc.npcID,static_zone_npc.loadNum, static_zone_npc.classID, static_zone_npc.professionID, static_zone_npc.extraRune, static_zone_npc.extraRune2 FROM static_zone_npc WHERE static_zone_npc.loadNum = ? AND static_zone_npc.npcID = ?");
+		setInt(1,npc.getParentZoneID());
+		setInt(2, npc.getFidalityID());
+		try {
+			ResultSet rs = executeQuery();
+
+			while (rs.next()) {
+
+
+				
+				int classID = rs.getInt("classID");
+				int professionID = rs.getInt("professionID");
+				int extraRune = rs.getInt("extraRune");
+				int extraRune2 = rs.getInt("extraRune2");
+				
+				npc.classID = classID;
+				npc.professionID = professionID;
+				npc.extraRune = extraRune;
+				npc.extraRune2 = extraRune2;
+
+			
+
+			}
+
+			rs.close();
+
+
+
+		} catch (SQLException e) {
+			Logger.error( e.toString());
+		} finally {
+			closeCallable();
+
+		}
+	}
+
+	public boolean ADD_TO_PRODUCTION_LIST(final long ID,final long npcUID, final long itemBaseID, DateTime dateTime, String prefix, String suffix, String name, boolean isRandom, int playerID) {
+		prepareCallable("INSERT INTO `dyn_npc_production` (`ID`,`npcUID`, `itemBaseID`,`dateToUpgrade`, `isRandom`, `prefix`, `suffix`, `name`,`playerID`) VALUES (?,?,?,?,?,?,?,?,?)");
+		setLong(1,ID);
+		setLong(2, npcUID);
+		setLong(3, itemBaseID);
+		setTimeStamp(4, dateTime.getMillis());
+		setBoolean(5, isRandom);
+		setString(6, prefix);
+		setString(7, suffix);
+		setString(8, name);
+		setInt(9,playerID);
+		return (executeUpdate() > 0);
+	}
+
+	public boolean REMOVE_FROM_PRODUCTION_LIST(final long ID,final long npcUID) {
+		prepareCallable("DELETE FROM `dyn_npc_production` WHERE `ID`=? AND `npcUID`=?;");
+		setLong(1,ID);
+		setLong(2, npcUID);
+		return (executeUpdate() > 0);
+	}
+
+	public boolean UPDATE_ITEM_TO_INVENTORY(final long ID,final long npcUID) {
+		prepareCallable("UPDATE `dyn_npc_production` SET `inForge`=? WHERE `ID`=? AND `npcUID`=?;");
+		setByte(1, (byte)0);
+		setLong(2, ID);
+		setLong(3, npcUID);
+		return (executeUpdate() > 0);
+	}
+
+	public boolean UPDATE_ITEM_PRICE(final long ID,final long npcUID, int value) {
+		prepareCallable("UPDATE `dyn_npc_production` SET `value`=? WHERE `ID`=? AND `npcUID`=?;");
+		setInt(1, value);
+		setLong(2, ID);
+		setLong(3, npcUID);
+
+		return (executeUpdate() > 0);
+	}
+
+	public boolean UPDATE_ITEM_ID(final long ID,final long npcUID,final long value) {
+		prepareCallable("UPDATE `dyn_npc_production` SET `ID`=? WHERE `ID`=? AND `npcUID`=? LIMIT 1;");
+		setLong(1, value);
+		setLong(2, ID);
+		setLong(3, npcUID);
+
+		return (executeUpdate() > 0);
+	}
+
+	public void LOAD_ALL_ITEMS_TO_PRODUCE(NPC npc) {
+
+		if (npc == null)
+			return;
+
+		prepareCallable("SELECT * FROM `dyn_npc_production` WHERE `npcUID` = ?");
+		setInt(1,npc.getObjectUUID());
+
+		try {
+			ResultSet rs = executeQuery();
+
+			//shrines cached in rs for easy cache on creation.
+			while (rs.next()) {
+				ProducedItem producedItem = new ProducedItem(rs);
+				npc.forgedItems.add(producedItem);
+			}
+
+		} catch (SQLException e) {
+			Logger.error(e.getErrorCode() + ' ' + e.getMessage(), e);
+		} finally {
+			closeCallable();
+		}
+	}
+	
+	public boolean UPDATE_PROFITS(NPC npc,ProfitType profitType, float value){
+		prepareCallable("UPDATE `dyn_npc_profits` SET `" + profitType.dbField + "` = ? WHERE `npcUID`=?");
+		setFloat(1, value);
+		setInt(2, npc.getObjectUUID());
+		return (executeUpdate() > 0);
+	}
+	
+	public void LOAD_NPC_PROFITS() {
+
+		HashMap<Integer, ArrayList<BuildingRegions>> regions;
+		NPCProfits npcProfit;
+
+
+		prepareCallable("SELECT * FROM dyn_npc_profits");
+
+		try {
+			ResultSet rs = executeQuery();
+
+			while (rs.next()) {
+
+				
+				npcProfit = new NPCProfits(rs);
+				NPCProfits.ProfitCache.put(npcProfit.npcUID, npcProfit);
+			}
+
+		} catch (SQLException e) {
+			Logger.error(": " + e.getErrorCode() + ' ' + e.getMessage(), e);
+		} finally {
+			closeCallable();
+		}
+	}
+	
+	public boolean CREATE_PROFITS(NPC npc){
+			prepareCallable("INSERT INTO `dyn_npc_profits` (`npcUID`) VALUES (?)");
+			setLong(1,npc.getObjectUUID());
+			return (executeUpdate() > 0);
+	}
+}
diff --git a/src/engine/db/handlers/dbPlayerCharacterHandler.java b/src/engine/db/handlers/dbPlayerCharacterHandler.java
new file mode 100644
index 00000000..1fb8e394
--- /dev/null
+++ b/src/engine/db/handlers/dbPlayerCharacterHandler.java
@@ -0,0 +1,402 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.db.handlers;
+
+import engine.Enum;
+import engine.gameManager.DbManager;
+import engine.objects.AbstractWorldObject;
+import engine.objects.Heraldry;
+import engine.objects.PlayerCharacter;
+import engine.objects.PlayerFriends;
+import engine.server.MBServerStatics;
+import org.pmw.tinylog.Logger;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class dbPlayerCharacterHandler extends dbHandlerBase {
+
+	public dbPlayerCharacterHandler() {
+		this.localClass = PlayerCharacter.class;
+		this.localObjectType = Enum.GameObjectType.valueOf(this.localClass.getSimpleName());
+	}
+
+	public PlayerCharacter ADD_PLAYER_CHARACTER(final PlayerCharacter toAdd) {
+		if (toAdd.getAccount() == null) {
+			return null;
+		}
+		prepareCallable("CALL `character_CREATE`(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);");
+		setLong(1, toAdd.getAccount().getObjectUUID());
+		setString(2, toAdd.getFirstName());
+		setString(3, toAdd.getLastName());
+		setInt(4, toAdd.getRace().getRaceRuneID());
+		setInt(5, toAdd.getBaseClass().getObjectUUID());
+		setInt(6, toAdd.getStrMod());
+		setInt(7, toAdd.getDexMod());
+		setInt(8, toAdd.getConMod());
+		setInt(9, toAdd.getIntMod());
+		setInt(10, toAdd.getSpiMod());
+		setInt(11, toAdd.getExp());
+		setInt(12, toAdd.getSkinColor());
+		setInt(13, toAdd.getHairColor());
+		setByte(14, toAdd.getHairStyle());
+		setInt(15, toAdd.getBeardColor());
+		setByte(16, toAdd.getBeardStyle());
+
+		int objectUUID = (int) getUUID();
+		if (objectUUID > 0) {
+			return GET_PLAYER_CHARACTER(objectUUID);
+		}
+		return null;
+	}
+
+	public boolean SET_IGNORE_LIST(int sourceID, int targetID, boolean toIgnore, String charName) {
+		if (toIgnore) {
+			//Add to ignore list
+			prepareCallable("INSERT INTO `dyn_character_ignore` (`accountUID`, `ignoringUID`, `characterName`) VALUES (?, ?, ?)");
+			setLong(1, (long) sourceID);
+			setLong(2, (long) targetID);
+			setString(3, charName);
+			return (executeUpdate() > 0);
+		} else {
+			//delete from ignore list
+			prepareCallable("DELETE FROM `dyn_character_ignore` WHERE `accountUID` = ? && `ignoringUID` = ?");
+			setLong(1, (long) sourceID);
+			setLong(2, (long) targetID);
+			return (executeUpdate() > 0);
+		}
+	}
+
+	public static boolean DELETE_CHARACTER_IGNORE(final PlayerCharacter pc, final ArrayList<Integer> toDelete) {
+
+		return false;
+	}
+
+	public ArrayList<PlayerCharacter> GET_ALL_PLAYERCHARACTERS() {
+		prepareCallable("SELECT * FROM `obj_character`");
+		return getObjectList();
+	}
+
+	public ArrayList<PlayerCharacter> GET_CHARACTERS_FOR_ACCOUNT(final int id, boolean forceFromDB) {
+		prepareCallable("SELECT `obj_character`.*, `object`.`parent` FROM `object` INNER JOIN `obj_character` ON `obj_character`.`UID` = `object`.`UID` WHERE `object`.`parent`=? && `obj_character`.`char_isActive`='1';");
+		setLong(1, (long) id);
+		return getObjectList(10, forceFromDB);
+	}
+
+	public ArrayList<PlayerCharacter> GET_CHARACTERS_FOR_ACCOUNT(final int id) {
+		prepareCallable("SELECT `obj_character`.*, `object`.`parent` FROM `object` INNER JOIN `obj_character` ON `obj_character`.`UID` = `object`.`UID` WHERE `object`.`parent`=? && `obj_character`.`char_isActive`='1';");
+		setLong(1, (long) id);
+		return getObjectList();
+	}
+
+	public ArrayList<PlayerCharacter> GET_ALL_CHARACTERS() {
+		prepareCallable("SELECT `obj_character`.*, `object`.`parent` FROM `object` INNER JOIN `obj_character` ON `obj_character`.`UID` = `object`.`UID` WHERE `obj_character`.`char_isActive`='1';");
+		return getObjectList();
+	}
+
+	/**
+	 *
+	 * <code>getFirstName</code> looks up the first name of a PlayerCharacter by
+	 * first checking the GOM cache and then querying the database.
+	 * PlayerCharacter objects that are not already cached won't be instantiated
+	 * and cached.
+	 *
+	 */
+	public String GET_FIRST_NAME(final int objectUUID) {
+		prepareCallable("SELECT `char_firstname` from `obj_character` WHERE `UID` = ? LIMIT 1");
+		setLong(1, (long) objectUUID);
+		String firstName = "";
+		try {
+			ResultSet rs = executeQuery();
+			if (rs.next()) {
+				firstName = rs.getString("char_firstname");
+			}
+		} catch (SQLException e) {
+			Logger.error( e);
+		} finally {
+			closeCallable();
+		}
+		return firstName;
+	}
+
+	public ConcurrentHashMap<Integer, String> GET_IGNORE_LIST(final int objectUUID, final boolean skipActiveCheck) {
+		ConcurrentHashMap<Integer, String> out = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW);
+		prepareCallable("SELECT * FROM `dyn_character_ignore` WHERE `accountUID` = ?;");
+		setLong(1, (long) objectUUID);
+		try {
+			ResultSet rs = executeQuery();
+			while (rs.next()) {
+				int ignoreCharacterID = rs.getInt("ignoringUID");
+				if (ignoreCharacterID == 0) {
+					continue;
+				}
+				String name = rs.getString("characterName");
+				out.put(ignoreCharacterID, name);
+			}
+			rs.close();
+		} catch (SQLException e) {
+			Logger.error("SQL Error number: " + e.getErrorCode());
+			return out; // null to explicitly indicate a problem and prevent data loss
+		} finally {
+			closeCallable();
+		}
+		return out;
+	}
+
+	public PlayerCharacter GET_PLAYER_CHARACTER(final int objectUUID) {
+
+		if (objectUUID == 0)
+			return null;
+
+		PlayerCharacter pc = (PlayerCharacter) DbManager.getFromCache(Enum.GameObjectType.PlayerCharacter, objectUUID);
+		if (pc != null)
+			return pc;
+		prepareCallable("SELECT `obj_character`.*, `object`.`parent` FROM `object` INNER JOIN `obj_character` ON `obj_character`.`UID` = `object`.`UID` WHERE `object`.`UID` = ?");
+		setLong(1, (long) objectUUID);
+		return (PlayerCharacter) getObjectSingle(objectUUID);
+	}
+
+	public boolean INSERT_CHARACTER_IGNORE(final PlayerCharacter pc, final ArrayList<Integer> toAdd) {
+		boolean allWorked = true;
+		prepareCallable("INSERT INTO `dyn_character_ignore` (`characterUID`, `ignoringUID`) VALUES (?, ?)");
+		setLong(1, (long) pc.getObjectUUID());
+		for (int id : toAdd) {
+			setLong(2, (long) id);
+			if (executeUpdate(false) == 0) {
+				allWorked = false;
+			}
+		}
+		closeCallable();
+		return allWorked;
+	}
+
+	public boolean IS_CHARACTER_NAME_UNIQUE(final String firstName) {
+		boolean unique = true;
+		prepareCallable("SELECT `char_firstname` FROM `obj_character` WHERE `char_isActive`=1 && `char_firstname`=?");
+		setString(1, firstName);
+		try {
+			ResultSet rs = executeQuery();
+			if (rs.next()) {
+				unique = false;
+			}
+			rs.close();
+		} catch (SQLException e) {
+			Logger.error("SQL Error number: " + e.getMessage());
+			unique = false;
+		} finally {
+			closeCallable();
+		}
+		return unique;
+	}
+
+	public boolean UPDATE_NAME(String oldFirstName, String newFirstName, String newLastName) {
+		prepareCallable("UPDATE `obj_character` SET `char_firstname`=?, `char_lastname`=? WHERE `char_firstname`=? AND `char_isActive`='1'");
+		setString(1, newFirstName);
+		setString(2, newLastName);
+		setString(3, oldFirstName);
+		return (executeUpdate() != 0);
+	}
+
+	public boolean SET_DELETED(final PlayerCharacter pc) {
+		prepareCallable("UPDATE `obj_character` SET `char_isActive`=? WHERE `UID` = ?");
+		setBoolean(1, !pc.isDeleted());
+		setLong(2, (long) pc.getObjectUUID());
+		return (executeUpdate() != 0);
+	}
+	public boolean SET_ACTIVE(final PlayerCharacter pc, boolean status) {
+		prepareCallable("UPDATE `obj_character` SET `char_isActive`=? WHERE `UID` = ?");
+		setBoolean(1, status);
+		setLong(2, (long) pc.getObjectUUID());
+		return (executeUpdate() != 0);
+	}
+	public boolean SET_BIND_BUILDING(final PlayerCharacter pc, int bindBuildingID) {
+		prepareCallable("UPDATE `obj_character` SET `char_bindBuilding`=? WHERE `UID` = ?");
+		setInt(1, bindBuildingID);
+		setLong(2, (long) pc.getObjectUUID());
+		return (executeUpdate() != 0);
+	}
+
+	public boolean SET_ANNIVERSERY(final PlayerCharacter pc, boolean flag) {
+		prepareCallable("UPDATE `obj_character` SET `anniversery`=? WHERE `UID` = ?");
+		setBoolean(1, flag);
+		setLong(2, (long) pc.getObjectUUID());
+		return (executeUpdate() != 0);
+	}
+
+
+	public boolean UPDATE_CHARACTER_EXPERIENCE(final PlayerCharacter pc) {
+		prepareCallable("UPDATE `obj_character` SET `char_experience`=? WHERE `UID` = ?");
+		setInt(1, pc.getExp());
+		setLong(2, (long) pc.getObjectUUID());
+		return (executeUpdate() != 0);
+	}
+	
+	public boolean UPDATE_GUILD(final PlayerCharacter pc, int guildUUID) {
+		prepareCallable("UPDATE `obj_character` SET `guildUID`=? WHERE `UID` = ?");
+		setInt(1, guildUUID);
+		setLong(2, (long) pc.getObjectUUID());
+		return (executeUpdate() != 0);
+	}
+
+	public boolean UPDATE_CHARACTER_STAT(final PlayerCharacter pc, String stat, short amount) {
+		prepareCallable("UPDATE `obj_character` SET `" + stat + "`=? WHERE `UID`=?");
+		setInt(1, pc.getExp());
+		setLong(2, (long) pc.getObjectUUID());
+		return (executeUpdate() != 0);
+	}
+
+	public boolean UPDATE_CHARACTER_STATS(final PlayerCharacter pc) {
+		prepareCallable("UPDATE `obj_character` SET `char_strMod`=?, `char_dexMod`=?, `char_conMod`=?, `char_intMod`=?, `char_spiMod`=? WHERE `UID`=?");
+		setInt(1, pc.getStrMod());
+		setInt(2, pc.getDexMod());
+		setInt(3, pc.getConMod());
+		setInt(4, pc.getIntMod());
+		setInt(5, pc.getSpiMod());
+		setLong(6, (long) pc.getObjectUUID());
+		return (executeUpdate() != 0);
+	}
+
+	public String SET_PROPERTY(final PlayerCharacter c, String name, Object new_value) {
+		prepareCallable("CALL character_SETPROP(?,?,?)");
+		setLong(1, (long) c.getObjectUUID());
+		setString(2, name);
+		setString(3, String.valueOf(new_value));
+		return getResult();
+	}
+
+	public String SET_PROPERTY(final PlayerCharacter c, String name, Object new_value, Object old_value) {
+		prepareCallable("CALL character_GETSETPROP(?,?,?,?)");
+		setLong(1, (long) c.getObjectUUID());
+		setString(2, name);
+		setString(3, String.valueOf(new_value));
+		setString(4, String.valueOf(old_value));
+		return getResult();
+	}
+	
+	public boolean SET_PROMOTION_CLASS(PlayerCharacter player, int promotionClassID) {
+		prepareCallable("UPDATE `obj_character` SET `char_promotionClassID`=?  WHERE `UID`=?;");
+		setInt(1,promotionClassID);
+		setInt(2, player.getObjectUUID());
+		return (executeUpdate() != 0);
+	}
+	
+	public boolean SET_INNERCOUNCIL(PlayerCharacter player, boolean isInnerCouncil) {
+		prepareCallable("UPDATE `obj_character` SET `guild_isInnerCouncil`=?  WHERE `UID`=?;");
+		setBoolean(1,isInnerCouncil);
+		setInt(2, player.getObjectUUID());
+		return (executeUpdate() != 0);
+	}
+	
+	public boolean SET_FULL_MEMBER(PlayerCharacter player, boolean isFullMember) {
+		prepareCallable("UPDATE `obj_character` SET `guild_isFullMember`=?  WHERE `UID`=?;");
+		setBoolean(1,isFullMember);
+		setInt(2, player.getObjectUUID());
+		return (executeUpdate() != 0);
+	}
+	
+	public boolean SET_TAX_COLLECTOR(PlayerCharacter player, boolean isTaxCollector) {
+		prepareCallable("UPDATE `obj_character` SET `guild_isTaxCollector`=?  WHERE `UID`=?;");
+		setBoolean(1,isTaxCollector);
+		setInt(2, player.getObjectUUID());
+		return (executeUpdate() != 0);
+	}
+	
+	public boolean SET_RECRUITER(PlayerCharacter player, boolean isRecruiter) {
+		prepareCallable("UPDATE `obj_character` SET `guild_isRecruiter`=?  WHERE `UID`=?;");
+		setBoolean(1,isRecruiter);
+		setInt(2, player.getObjectUUID());
+		return (executeUpdate() != 0);
+	}
+	
+	public boolean SET_GUILD_TITLE(PlayerCharacter player, int title) {
+		prepareCallable("UPDATE `obj_character` SET `guild_title`=?  WHERE `UID`=?;");
+		setInt(1,title);
+		setInt(2, player.getObjectUUID());
+		return (executeUpdate() != 0);
+	}
+	
+	
+	
+	public boolean ADD_FRIEND(int source, long friend){
+		prepareCallable("INSERT INTO `dyn_character_friends` (`playerUID`, `friendUID`) VALUES (?, ?)");
+		setLong(1, (long) source);
+		setLong(2, (long)friend);
+		return (executeUpdate() != 0);
+	}
+	
+	public boolean REMOVE_FRIEND(int source, int friend){
+		prepareCallable("DELETE FROM `dyn_character_friends` WHERE (`playerUID`=?) AND (`friendUID`=?)");
+		setLong(1, (long) source);
+		setLong(2, (long)friend);
+		return (executeUpdate() != 0);
+	}
+	
+	public void LOAD_PLAYER_FRIENDS() {
+
+		PlayerFriends playerFriend;
+
+
+		prepareCallable("SELECT * FROM dyn_character_friends");
+
+		try {
+			ResultSet rs = executeQuery();
+
+			while (rs.next()) {
+				playerFriend = new PlayerFriends(rs);
+			}
+
+
+		} catch (SQLException e) {
+			Logger.error("LoadMeshBounds: " + e.getErrorCode() + ' ' + e.getMessage(), e);
+		} finally {
+			closeCallable();
+		}
+	}
+	
+	public boolean ADD_HERALDY(int source, AbstractWorldObject character){
+		prepareCallable("INSERT INTO `dyn_character_heraldy` (`playerUID`, `characterUID`,`characterType`) VALUES (?, ?,?)");
+		setLong(1, (long) source);
+		setLong(2, (long)character.getObjectUUID());
+		setInt(3, character.getObjectType().ordinal());
+		return (executeUpdate() != 0);
+	}
+	
+	public boolean REMOVE_HERALDY(int source, int characterUID){
+		prepareCallable("DELETE FROM `dyn_character_heraldy` WHERE (`playerUID`=?) AND (`characterUID`=?)");
+		setLong(1, (long) source);
+		setLong(2, (long)characterUID);
+		return (executeUpdate() != 0);
+	}
+	
+	public void LOAD_HERALDY() {
+
+		Heraldry heraldy;
+
+
+		prepareCallable("SELECT * FROM dyn_character_heraldy");
+
+		try {
+			ResultSet rs = executeQuery();
+
+			while (rs.next()) {
+				heraldy = new Heraldry(rs);
+			}
+
+
+		} catch (SQLException e) {
+			Logger.error("LoadHeraldy: " + e.getErrorCode() + ' ' + e.getMessage(), e);
+		} finally {
+			closeCallable();
+		}
+	}
+	
+}
diff --git a/src/engine/db/handlers/dbPromotionClassHandler.java b/src/engine/db/handlers/dbPromotionClassHandler.java
new file mode 100644
index 00000000..230d3f15
--- /dev/null
+++ b/src/engine/db/handlers/dbPromotionClassHandler.java
@@ -0,0 +1,54 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.db.handlers;
+
+import engine.objects.PromotionClass;
+import org.pmw.tinylog.Logger;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+
+public class dbPromotionClassHandler extends dbHandlerBase {
+
+    public dbPromotionClassHandler() {
+        this.localClass = PromotionClass.class;
+        this.localObjectType = engine.Enum.GameObjectType.valueOf(this.localClass.getSimpleName());
+    }
+
+    public ArrayList<Integer> GET_ALLOWED_RUNES(final PromotionClass pc) {
+        ArrayList<Integer> runes = new ArrayList<>();
+        prepareCallable("SELECT * FROM `static_rune_promotionrunereq` WHERE `promoID`=?");
+        setInt(1, pc.getObjectUUID());
+        try {
+            ResultSet rs = executeQuery();
+            while (rs.next()) {
+                runes.add(rs.getInt("runereqID"));
+            }
+        } catch (SQLException e) {
+            Logger.error("Failed to retrieve Allowed Runes for PromotionClass " + pc.getObjectUUID() + ". Error number: " + e.getErrorCode(), e);
+            return null;
+        } finally {
+            closeCallable();
+        }
+        return runes;
+    }
+
+    public PromotionClass GET_PROMOTION_CLASS(final int objectUUID) {
+        prepareCallable("SELECT * FROM `static_rune_promotion` WHERE `ID` = ?");
+        setInt(1, objectUUID);
+        return (PromotionClass) getObjectSingle(objectUUID);
+    }
+    
+    public ArrayList<PromotionClass> GET_ALL_PROMOTIONS() {
+		prepareCallable("SELECT * FROM `static_rune_promotion`");
+		return getObjectList();
+	}
+}
diff --git a/src/engine/db/handlers/dbRaceHandler.java b/src/engine/db/handlers/dbRaceHandler.java
new file mode 100644
index 00000000..df83d381
--- /dev/null
+++ b/src/engine/db/handlers/dbRaceHandler.java
@@ -0,0 +1,85 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.db.handlers;
+
+import engine.objects.Race;
+import org.pmw.tinylog.Logger;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.HashSet;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class dbRaceHandler extends dbHandlerBase {
+
+    public dbRaceHandler() {
+    }
+
+    public HashSet<Integer> BEARD_COLORS_FOR_RACE(final int id) {
+        prepareCallable("SELECT `color` FROM `static_rune_racebeardcolor` WHERE `RaceID` = ?");
+        setInt(1, id);
+        return getIntegerList(1);
+    }
+
+    public HashSet<Integer> BEARD_STYLES_FOR_RACE(final int id) {
+        prepareCallable("SELECT `beardStyle` FROM `static_rune_racebeardstyle` WHERE `RaceID` = ?");
+        setInt(1, id);
+        return getIntegerList(1);
+    }
+
+    public HashSet<Integer> HAIR_COLORS_FOR_RACE(final int id) {
+        prepareCallable("SELECT `color` FROM `static_rune_racehaircolor` WHERE `RaceID` = ?");
+        setInt(1, id);
+        return getIntegerList(1);
+    }
+
+    public HashSet<Integer> HAIR_STYLES_FOR_RACE(final int id) {
+        prepareCallable("SELECT `hairStyle` FROM `static_rune_racehairstyle` WHERE `RaceID` = ?");
+        setInt(1, id);
+        return getIntegerList(1);
+    }
+
+    public HashSet<Integer> SKIN_COLOR_FOR_RACE(final int id) {
+        prepareCallable("SELECT `color` FROM `static_rune_raceskincolor` WHERE `RaceID` = ?");
+        setInt(1, id);
+        return getIntegerList(1);
+    }
+
+    public ConcurrentHashMap<Integer, Race> LOAD_ALL_RACES() {
+
+        ConcurrentHashMap<Integer, Race> races;
+        Race thisRace;
+
+        races = new ConcurrentHashMap<>();
+        int recordsRead = 0;
+
+        prepareCallable("SELECT * FROM static_rune_race");
+
+        try {
+            ResultSet rs = executeQuery();
+
+            while (rs.next()) {
+
+                recordsRead++;
+                thisRace = new Race(rs);
+
+                races.put(thisRace.getRaceRuneID(), thisRace);
+            }
+
+            Logger.info("read: " + recordsRead + " cached: " + races.size());
+
+        } catch (SQLException e) {
+            Logger.error( e.toString());
+        } finally {
+            closeCallable();
+        }
+        return races;
+    }
+}
diff --git a/src/engine/db/handlers/dbRealmHandler.java b/src/engine/db/handlers/dbRealmHandler.java
new file mode 100644
index 00000000..932d6425
--- /dev/null
+++ b/src/engine/db/handlers/dbRealmHandler.java
@@ -0,0 +1,73 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.db.handlers;
+
+import engine.objects.Realm;
+import org.pmw.tinylog.Logger;
+
+import java.net.UnknownHostException;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.logging.Level;
+
+public class dbRealmHandler extends dbHandlerBase {
+
+    public dbRealmHandler() {
+
+    }
+
+        public ConcurrentHashMap<Integer, Realm> LOAD_ALL_REALMS() {
+        
+            ConcurrentHashMap<Integer, Realm> realmList;
+            Realm thisRealm;
+                    
+            realmList = new ConcurrentHashMap<>();
+            int recordsRead = 0;
+            
+		prepareCallable("SELECT * FROM obj_realm");
+
+		try {
+			ResultSet rs = executeQuery();
+                        
+			while (rs.next()) {
+                            
+                          recordsRead++;
+                          thisRealm = new Realm(rs);
+                          realmList.put(thisRealm.getRealmID(), thisRealm);
+			}
+                        
+                        Logger.info( "read: " + recordsRead + " cached: " + realmList.size());
+                                
+		} catch (SQLException e) {
+			Logger.error(e.getErrorCode() + ' ' + e.getMessage(), e);
+		} catch (UnknownHostException ex) {
+            java.util.logging.Logger.getLogger(dbRealmHandler.class.getName()).log(Level.SEVERE, null, ex);
+        } finally {
+			closeCallable();
+		}
+		return realmList;
+	}
+        
+    public void REALM_UPDATE(Realm realm) {
+
+            prepareCallable("CALL realm_UPDATE(?,?,?,?)");
+            
+            setInt(1, realm.getRealmID());
+            setInt(2, (realm.getRulingCity() == null) ? 0 : realm.getRulingCity().getObjectUUID());
+            setInt(3, realm.getCharterType());
+        if (realm.ruledSince != null)
+            setLocalDateTime(4, realm.ruledSince);
+            else
+                setNULL(4, java.sql.Types.DATE);
+            
+            executeUpdate();
+    }
+}
diff --git a/src/engine/db/handlers/dbResistHandler.java b/src/engine/db/handlers/dbResistHandler.java
new file mode 100644
index 00000000..0eccf2e2
--- /dev/null
+++ b/src/engine/db/handlers/dbResistHandler.java
@@ -0,0 +1,37 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.db.handlers;
+
+import engine.objects.Resists;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+public class dbResistHandler extends dbHandlerBase {
+
+    public dbResistHandler() {
+
+    }
+
+    public Resists GET_RESISTS_FOR_MOB(int resistID) {
+        prepareCallable("SELECT * FROM `static_npc_mob_resists` WHERE `ID` = ?;");
+        setInt(1, resistID);
+        try {
+            ResultSet rs = executeQuery();
+            if (rs.next()) {
+                return new Resists(rs);
+            }
+        } catch (SQLException e) {
+        } finally {
+            closeCallable();
+        }
+        return null;
+    }
+}
diff --git a/src/engine/db/handlers/dbRuneBaseAttributeHandler.java b/src/engine/db/handlers/dbRuneBaseAttributeHandler.java
new file mode 100644
index 00000000..ce880b6a
--- /dev/null
+++ b/src/engine/db/handlers/dbRuneBaseAttributeHandler.java
@@ -0,0 +1,35 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.db.handlers;
+
+import engine.objects.RuneBaseAttribute;
+
+import java.util.ArrayList;
+
+public class dbRuneBaseAttributeHandler extends dbHandlerBase {
+
+	public dbRuneBaseAttributeHandler() {
+		this.localClass = RuneBaseAttribute.class;
+		this.localObjectType = engine.Enum.GameObjectType.valueOf(this.localClass.getSimpleName());
+	}
+
+	public ArrayList<RuneBaseAttribute> GET_ATTRIBUTES_FOR_RUNEBASE(int id) {
+		prepareCallable("SELECT * FROM `static_rune_runebaseattribute` WHERE `RuneBaseID`=?");
+		setInt(1, id);
+		return getObjectList();
+	}
+
+	public ArrayList<RuneBaseAttribute> GET_ATTRIBUTES_FOR_RUNEBASE() {
+		prepareCallable("SELECT * FROM `static_rune_runebaseattribute`");
+		return getObjectList();
+	}
+
+
+}
diff --git a/src/engine/db/handlers/dbRuneBaseEffectHandler.java b/src/engine/db/handlers/dbRuneBaseEffectHandler.java
new file mode 100644
index 00000000..0e84c8d0
--- /dev/null
+++ b/src/engine/db/handlers/dbRuneBaseEffectHandler.java
@@ -0,0 +1,73 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.db.handlers;
+
+import engine.Enum.GameObjectType;
+import engine.gameManager.DbManager;
+import engine.objects.AbstractGameObject;
+import engine.objects.RuneBaseEffect;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+public class dbRuneBaseEffectHandler extends dbHandlerBase {
+
+	public dbRuneBaseEffectHandler() {
+		this.localClass = RuneBaseEffect.class;
+		this.localObjectType = engine.Enum.GameObjectType.valueOf(this.localClass.getSimpleName());
+	}
+
+	public ArrayList<RuneBaseEffect> GET_EFFECTS_FOR_RUNEBASE(int id) {
+		prepareCallable("SELECT * FROM `static_rune_baseeffect` WHERE `runeID`=?");
+		setInt(1, id);
+		return getObjectList();
+	}
+
+	public RuneBaseEffect GET_RUNEBASE_EFFECT(int id) {
+
+		if (id == 0)
+			return null;
+		RuneBaseEffect runeBaseEffect = (RuneBaseEffect) DbManager.getFromCache(GameObjectType.RuneBaseEffect, id);
+		if (runeBaseEffect != null)
+			return runeBaseEffect;
+		prepareCallable("SELECT * FROM `static_rune_baseeffect` WHERE `ID` = ?");
+		setInt(1, id);
+		return (RuneBaseEffect) getObjectSingle(id);
+	}
+
+	public ArrayList<RuneBaseEffect> GET_ALL_RUNEBASE_EFFECTS(){
+		prepareCallable("SELECT * FROM `static_rune_baseeffect`;");
+		return  getObjectList();
+	}
+
+	//This calls from cache only. Call this AFTER caching all runebase effects;
+	public HashMap<Integer, ArrayList<RuneBaseEffect>> LOAD_BASEEFFECTS_FOR_RUNEBASE() {
+		HashMap<Integer, ArrayList<RuneBaseEffect>> runeBaseEffectSet;
+		runeBaseEffectSet = new HashMap<>();
+
+
+		for (AbstractGameObject runeBaseEffect:DbManager.getList(GameObjectType.RuneBaseEffect)){
+
+			int runeBaseID = ((RuneBaseEffect)runeBaseEffect).getRuneBaseID();
+			if (runeBaseEffectSet.get(runeBaseID) == null){
+				ArrayList<RuneBaseEffect> runeBaseEffectList = new ArrayList<>();
+				runeBaseEffectList.add((RuneBaseEffect)runeBaseEffect);
+				runeBaseEffectSet.put(runeBaseID, runeBaseEffectList);
+			}
+			else{
+				ArrayList<RuneBaseEffect>runeBaseEffectList = runeBaseEffectSet.get(runeBaseID);
+				runeBaseEffectList.add((RuneBaseEffect)runeBaseEffect);
+				runeBaseEffectSet.put(runeBaseID, runeBaseEffectList);
+			}
+		}
+		return runeBaseEffectSet;
+	}
+
+}
diff --git a/src/engine/db/handlers/dbRuneBaseHandler.java b/src/engine/db/handlers/dbRuneBaseHandler.java
new file mode 100644
index 00000000..44e58179
--- /dev/null
+++ b/src/engine/db/handlers/dbRuneBaseHandler.java
@@ -0,0 +1,173 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.db.handlers;
+
+import engine.objects.RuneBase;
+import org.pmw.tinylog.Logger;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+
+public class dbRuneBaseHandler extends dbHandlerBase {
+
+	public dbRuneBaseHandler() {
+		this.localClass = RuneBase.class;
+		this.localObjectType = engine.Enum.GameObjectType.valueOf(this.localClass.getSimpleName());
+	}
+
+	public void GET_RUNE_REQS(final RuneBase rb) {
+		prepareCallable("SELECT * FROM `static_rune_runereq` WHERE `runeID` = ?");
+		setInt(1, rb.getObjectUUID());
+		try {
+
+			ResultSet rs = executeQuery();
+
+			while (rs.next()) {
+				int type = rs.getInt("type");
+
+				switch (type) {
+				case 1:
+					rb.getRace().put(rs.getInt("requiredRuneID"), rs.getBoolean("isAllowed"));
+					break;
+				case 2:
+					rb.getBaseClass().put(rs.getInt("requiredRuneID"), rs.getBoolean("isAllowed"));
+					break;
+				case 3:
+					rb.getPromotionClass().put(rs.getInt("requiredRuneID"), rs.getBoolean("isAllowed"));
+					break;
+				case 4:
+					rb.getDiscipline().put(rs.getInt("requiredRuneID"), rs.getBoolean("isAllowed"));
+					break;
+				case 5:
+					rb.getOverwrite().add(rs.getInt("requiredRuneID"));
+					break;
+				case 6:
+					rb.setLevelRequired(rs.getInt("requiredRuneID"));
+					break;
+				}
+			}
+			rs.close();
+		} catch (SQLException e) {
+			Logger.error("SQL Error number: " + e.getErrorCode());
+		} finally {
+			closeCallable();
+		}
+	}
+
+	public RuneBase GET_RUNEBASE(final int id) {
+		prepareCallable("SELECT * FROM `static_rune_runebase` WHERE `ID` = ?");
+		setInt(1, id);
+		return (RuneBase) getObjectSingle(id);
+	}
+
+	public ArrayList<RuneBase> LOAD_ALL_RUNEBASES() {
+		prepareCallable("SELECT * FROM `static_rune_runebase`;");
+		return  getObjectList();
+	}
+
+	public HashMap<Integer, ArrayList<Integer>> LOAD_ALLOWED_STARTING_RUNES_FOR_BASECLASS() {
+
+		HashMap<Integer, ArrayList<Integer>> runeSets;
+
+		runeSets = new HashMap<>();
+		int recordsRead = 0;
+
+		prepareCallable("SELECT * FROM static_rune_baseclassrune");
+
+		try {
+			ResultSet rs = executeQuery();
+
+			while (rs.next()) {
+
+				recordsRead++;
+
+				int baseClassID = rs.getInt("BaseClassesID");
+				int runeBaseID = rs.getInt("RuneBaseID");
+
+				if (runeSets.get(baseClassID) == null){
+					ArrayList<Integer> runeList = new ArrayList<>();
+					runeList.add(runeBaseID);
+					runeSets.put(baseClassID, runeList);
+				}
+				else{
+					ArrayList<Integer>runeList = runeSets.get(baseClassID);
+					runeList.add(runeBaseID);
+					runeSets.put(baseClassID, runeList);
+				}
+			}
+
+			Logger.info("read: " + recordsRead + " cached: " + runeSets.size());
+
+		} catch (SQLException e) {
+			Logger.error(e.getErrorCode() + ' ' + e.getMessage(), e);
+		} finally {
+			closeCallable();
+		}
+		return runeSets;
+	}
+
+	public HashMap<Integer, ArrayList<Integer>> LOAD_ALLOWED_STARTING_RUNES_FOR_RACE() {
+
+		HashMap<Integer, ArrayList<Integer>> runeSets;
+
+		runeSets = new HashMap<>();
+		int recordsRead = 0;
+
+		prepareCallable("SELECT * FROM static_rune_racerune");
+
+		try {
+			ResultSet rs = executeQuery();
+
+			while (rs.next()) {
+
+				recordsRead++;
+
+				int raceID = rs.getInt("RaceID");
+				int runeBaseID = rs.getInt("RuneBaseID");
+
+				if (runeSets.get(raceID) == null){
+					ArrayList<Integer> runeList = new ArrayList<>();
+					runeList.add(runeBaseID);
+					runeSets.put(raceID, runeList);
+				}
+				else{
+					ArrayList<Integer>runeList = runeSets.get(raceID);
+					runeList.add(runeBaseID);
+					runeSets.put(raceID, runeList);
+				}
+			}
+
+			Logger.info( "read: " + recordsRead + " cached: " + runeSets.size());
+
+		} catch (SQLException e) {
+			Logger.error(e.getErrorCode() + ' ' + e.getMessage(), e);
+		} finally {
+			closeCallable();
+		}
+		return runeSets;
+	}
+
+	public ArrayList<RuneBase> GET_RUNEBASE_FOR_BASECLASS(final int id) {
+		prepareCallable("SELECT rb.* FROM static_rune_baseclassrune bcr, static_rune_runebase rb WHERE bcr.RuneBaseID = rb.ID "
+				+ "&& ( bcr.BaseClassesID = 111111 || bcr.BaseClassesID = ? )");
+		setInt(1, id);
+		return getObjectList();
+	}
+
+	public HashSet<RuneBase> GET_RUNEBASE_FOR_RACE(final int id) {
+		prepareCallable("SELECT rb.* FROM static_rune_racerune rr, static_rune_runebase rb"
+				+ " WHERE rr.RuneBaseID = rb.ID && ( rr.RaceID = 111111 || rr.RaceID = ?)");
+		setInt(1, id);
+		return new HashSet<>(getObjectList());
+	}
+}
diff --git a/src/engine/db/handlers/dbShrineHandler.java b/src/engine/db/handlers/dbShrineHandler.java
new file mode 100644
index 00000000..3d5eb30b
--- /dev/null
+++ b/src/engine/db/handlers/dbShrineHandler.java
@@ -0,0 +1,147 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.db.handlers;
+
+import engine.Enum.ProtectionState;
+import engine.gameManager.DbManager;
+import engine.math.Vector3fImmutable;
+import engine.objects.AbstractGameObject;
+import engine.objects.Building;
+import engine.objects.Shrine;
+import org.joda.time.DateTime;
+import org.pmw.tinylog.Logger;
+
+import java.net.UnknownHostException;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+
+public class dbShrineHandler extends dbHandlerBase {
+
+    public dbShrineHandler() {
+        this.localClass = Shrine.class;
+        this.localObjectType = engine.Enum.GameObjectType.valueOf(this.localClass.getSimpleName());
+    }
+
+    public ArrayList<AbstractGameObject> CREATE_SHRINE( int parentZoneID, int OwnerUUID, String name, int meshUUID,
+            Vector3fImmutable location, float meshScale, int currentHP,
+            ProtectionState protectionState, int currentGold, int rank,
+            DateTime upgradeDate, int blueprintUUID, float w, float rotY, String shrineType) {
+
+        prepareCallable("CALL `shrine_CREATE`(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? ,? ,? ,?, ?,?);");
+
+      
+        setInt(1, parentZoneID);
+        setInt(2, OwnerUUID);
+        setString(3, name);
+        setInt(4, meshUUID);
+        setFloat(5, location.x);
+        setFloat(6, location.y);
+        setFloat(7, location.z);
+        setFloat(8, meshScale);
+        setInt(9, currentHP);
+        setString(10, protectionState.name());
+        setInt(11, currentGold);
+        setInt(12, rank);
+
+        if (upgradeDate != null) {
+            setTimeStamp(13, upgradeDate.getMillis());
+        } else {
+            setNULL(13, java.sql.Types.DATE);
+        }
+
+        setInt(14, blueprintUUID);
+        setFloat(15, w);
+        setFloat(16, rotY);
+        setString(17, shrineType);
+
+        ArrayList<AbstractGameObject> list = new ArrayList<>();
+        //System.out.println(this.cs.get().toString());
+        try {
+            boolean work = execute();
+            if (work) {
+                ResultSet rs = this.cs.get().getResultSet();
+                while (rs.next()) {
+                    addObject(list, rs);
+                }
+                rs.close();
+            } else {
+                Logger.info("Shrine Creation Failed: " + this.cs.get().toString());
+                return list; //city creation failure
+            }
+            while (this.cs.get().getMoreResults()) {
+                ResultSet rs = this.cs.get().getResultSet();
+                while (rs.next()) {
+                    addObject(list, rs);
+                }
+                rs.close();
+            }
+        } catch (SQLException e) {
+            Logger.info("Shrine Creation Failed, SQLException: " + this.cs.get().toString() + e.toString());
+            return list; //city creation failure
+        } catch (UnknownHostException e) {
+            Logger.info("Shrine Creation Failed, UnknownHostException: " + this.cs.get().toString());
+            return list; //city creation failure
+        } finally {
+            closeCallable();
+        }
+        return list;
+
+    }
+
+    public boolean updateFavors(Shrine shrine, int amount, int oldAmount) {
+
+        prepareCallable("UPDATE `obj_shrine` SET `shrine_favors`=? WHERE `UID` = ? AND `shrine_favors` = ?");
+        setInt(1, amount);
+        setLong(2, (long) shrine.getObjectUUID());
+        setInt(3, oldAmount);
+        return (executeUpdate() != 0);
+    }
+
+    public static void addObject(ArrayList<AbstractGameObject> list, ResultSet rs) throws SQLException, UnknownHostException {
+        String type = rs.getString("type");
+        switch (type) {
+            case "building":
+                Building building = new Building(rs);
+                DbManager.addToCache(building);
+                list.add(building);
+                break;
+            case "shrine":
+                Shrine shrine = new Shrine(rs);
+                DbManager.addToCache(shrine);
+                list.add(shrine);
+                break;
+        }
+    }
+
+    public void LOAD_ALL_SHRINES() {
+
+        Shrine thisShrine;
+
+        prepareCallable("SELECT `obj_shrine`.*, `object`.`parent`, `object`.`type` FROM `object` LEFT JOIN `obj_shrine` ON `object`.`UID` = `obj_shrine`.`UID` WHERE `object`.`type` = 'shrine';");
+
+        try {
+            ResultSet rs = executeQuery();
+
+            //shrines cached in rs for easy cache on creation.
+            while (rs.next()) {
+                thisShrine = new Shrine(rs);
+                thisShrine.getShrineType().addShrineToServerList(thisShrine);
+            }
+
+        } catch (SQLException e) {
+            Logger.error( e.getErrorCode() + ' ' + e.getMessage(), e);
+        } finally {
+            closeCallable();
+        }
+
+    }
+
+}
diff --git a/src/engine/db/handlers/dbSkillBaseHandler.java b/src/engine/db/handlers/dbSkillBaseHandler.java
new file mode 100644
index 00000000..f35b7be4
--- /dev/null
+++ b/src/engine/db/handlers/dbSkillBaseHandler.java
@@ -0,0 +1,143 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.db.handlers;
+
+import engine.Enum;
+import engine.Enum.GameObjectType;
+import engine.gameManager.DbManager;
+import engine.objects.AbstractGameObject;
+import engine.objects.MaxSkills;
+import engine.objects.SkillsBase;
+import org.pmw.tinylog.Logger;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.HashMap;
+
+public class dbSkillBaseHandler extends dbHandlerBase {
+
+	public dbSkillBaseHandler() {
+		this.localClass = SkillsBase.class;
+		this.localObjectType = Enum.GameObjectType.valueOf(this.localClass.getSimpleName());
+	}
+
+	public SkillsBase GET_BASE(final int objectUUID) {
+
+		SkillsBase skillsBase = (SkillsBase) DbManager.getFromCache(GameObjectType.SkillsBase, objectUUID);
+		if (skillsBase != null)
+			return skillsBase;
+		prepareCallable("SELECT * FROM static_skill_skillsbase WHERE ID = ?");
+		setInt(1, objectUUID);
+		SkillsBase sb;
+		sb = (SkillsBase) getObjectSingle(objectUUID);
+		SkillsBase.putInCache(sb);
+		return sb;
+	}
+
+	public SkillsBase GET_BASE_BY_NAME(String name) {
+		SkillsBase sb = SkillsBase.getFromCache(name);
+		if (sb != null) {
+			return sb;
+		}
+		prepareCallable("SELECT * FROM static_skill_skillsbase WHERE name = ?");
+		setString(1, name);
+		ArrayList<AbstractGameObject> result = getObjectList();
+		if (result.size() > 0) {
+			sb = (SkillsBase) result.get(0);
+			SkillsBase.putInCache(sb);
+			return sb;
+		} else {
+			return null;
+		}
+	}
+
+	public SkillsBase GET_BASE_BY_TOKEN(final int token) {
+		SkillsBase sb = SkillsBase.getFromCache(token);
+		if (sb != null) {
+			return sb;
+		}
+
+		prepareCallable("SELECT * FROM static_skill_skillsbase WHERE token = ?");
+		setInt(1, token);
+		ArrayList<AbstractGameObject> result = getObjectList();
+		if (result.size() > 0) {
+			sb = (SkillsBase) result.get(0);
+			SkillsBase.putInCache(sb);
+			return sb;
+		} else {
+			return null;
+		}
+	}
+
+	public void LOAD_ALL_MAX_SKILLS_FOR_CONTRACT() {
+
+		prepareCallable("SELECT * FROM `static_rune_maxskills`");
+
+
+		try {
+			ResultSet rs = executeQuery();
+
+			//shrines cached in rs for easy cache on creation.
+			while (rs.next()) {
+
+				MaxSkills maxSKills = new MaxSkills(rs);
+				if (MaxSkills.MaxSkillsSet.get(maxSKills.getRuneID()) == null){
+					ArrayList<MaxSkills> newMaxSkillsList = new ArrayList<>();
+					newMaxSkillsList.add(maxSKills);
+					MaxSkills.MaxSkillsSet.put(maxSKills.getRuneID(), newMaxSkillsList);
+				}else
+					MaxSkills.MaxSkillsSet.get(maxSKills.getRuneID()).add(maxSKills);
+
+			}
+
+
+
+		} catch (SQLException e) {
+			Logger.error( e.getErrorCode() + ' ' + e.getMessage(), e);
+		} finally {
+			closeCallable();
+		}
+
+	}
+	
+	public void LOAD_ALL_RUNE_SKILLS() {
+
+		prepareCallable("SELECT * FROM `static_skill_skillsgranted`");
+
+
+		try {
+			ResultSet rs = executeQuery();
+
+			//shrines cached in rs for easy cache on creation.
+			while (rs.next()) {
+
+				int runeID = rs.getInt("runeID");
+				int token = rs.getInt("token");
+				int amount = rs.getInt("amount");
+				
+				if (SkillsBase.runeSkillsCache.get(runeID) == null)
+					SkillsBase.runeSkillsCache.put(runeID, new HashMap<>());
+				
+				SkillsBase.runeSkillsCache.get(runeID).put(token, amount);
+			}
+
+
+
+		} catch (SQLException e) {
+			Logger.error( e.getErrorCode() + ' ' + e.getMessage(), e);
+		} finally {
+			closeCallable();
+		}
+
+	}
+
+
+}
diff --git a/src/engine/db/handlers/dbSkillReqHandler.java b/src/engine/db/handlers/dbSkillReqHandler.java
new file mode 100644
index 00000000..b0e16f90
--- /dev/null
+++ b/src/engine/db/handlers/dbSkillReqHandler.java
@@ -0,0 +1,35 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.db.handlers;
+
+import engine.objects.SkillReq;
+
+import java.util.ArrayList;
+
+public class dbSkillReqHandler extends dbHandlerBase {
+
+    public dbSkillReqHandler() {
+        this.localClass = SkillReq.class;
+        this.localObjectType = engine.Enum.GameObjectType.valueOf(this.localClass.getSimpleName());
+    }
+
+    public ArrayList<SkillReq> GET_REQS_FOR_RUNE(final int objectUUID) {
+        prepareCallable("SELECT * FROM `static_skill_skillreq` WHERE `runeID`=?");
+        setInt(1, objectUUID);
+        return getObjectList();
+    }
+    
+    public SkillReq GET_REQS_BY_SKILLID(int skillID) {
+        prepareCallable("SELECT * FROM `static_skill_skillreq` WHERE `skillID` = ?");
+        setInt(1,skillID);
+        int objectUUID = (int) getUUID();
+        return (SkillReq) this.getObjectSingle(objectUUID);
+    }
+}
diff --git a/src/engine/db/handlers/dbSpecialLootHandler.java b/src/engine/db/handlers/dbSpecialLootHandler.java
new file mode 100644
index 00000000..76aa68dd
--- /dev/null
+++ b/src/engine/db/handlers/dbSpecialLootHandler.java
@@ -0,0 +1,75 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.db.handlers;
+
+import engine.objects.SpecialLoot;
+import org.pmw.tinylog.Logger;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.HashMap;
+
+public class dbSpecialLootHandler extends dbHandlerBase {
+
+	public dbSpecialLootHandler() {
+		this.localClass = SpecialLoot.class;
+		this.localObjectType = engine.Enum.GameObjectType.valueOf(this.localClass.getSimpleName());
+	}
+
+	public ArrayList<SpecialLoot> GET_SPECIALLOOT(int mobbaseID) {
+
+		prepareCallable("SELECT * FROM `static_npc_mob_specialloot` WHERE `mobbaseID`=?");
+		setInt(1, mobbaseID);
+		return getObjectList();
+	}
+
+	public void GenerateSpecialLoot(){
+		HashMap<Integer, ArrayList<SpecialLoot>> lootSets;
+		SpecialLoot lootSetEntry;
+		int	lootSetID;
+
+		lootSets = new HashMap<>();
+		int recordsRead = 0;
+
+		prepareCallable("SELECT * FROM static_zone_npc_specialloot");
+
+		try {
+			ResultSet rs = executeQuery();
+
+			while (rs.next()) {
+
+				recordsRead++;
+
+				lootSetID = rs.getInt("lootSet");
+				lootSetEntry = new SpecialLoot(rs,true);
+
+				if (lootSets.get(lootSetID) == null){
+					ArrayList<SpecialLoot> lootList = new ArrayList<>();
+					lootList.add(lootSetEntry);
+					lootSets.put(lootSetID, lootList);
+				}
+				else{
+					ArrayList<SpecialLoot>lootList = lootSets.get(lootSetID);
+					lootList.add(lootSetEntry);
+					lootSets.put(lootSetID, lootList);
+				}
+			}
+
+			Logger.info( "read: " + recordsRead + " cached: " + lootSets.size());
+
+		} catch (SQLException e) {
+			Logger.error(e.getErrorCode() + ' ' + e.getMessage(), e);
+		} finally {
+			closeCallable();
+		}
+		SpecialLoot.LootMap = lootSets;
+	}
+}
diff --git a/src/engine/db/handlers/dbVendorDialogHandler.java b/src/engine/db/handlers/dbVendorDialogHandler.java
new file mode 100644
index 00000000..def139f2
--- /dev/null
+++ b/src/engine/db/handlers/dbVendorDialogHandler.java
@@ -0,0 +1,31 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.db.handlers;
+
+import engine.Enum;
+import engine.gameManager.DbManager;
+import engine.objects.VendorDialog;
+
+public class dbVendorDialogHandler extends dbHandlerBase {
+
+	public dbVendorDialogHandler() {
+		this.localClass = VendorDialog.class;
+		this.localObjectType = Enum.GameObjectType.valueOf(this.localClass.getSimpleName());
+	}
+
+	public VendorDialog GET_VENDORDIALOG(final int objectUUID) {
+		VendorDialog vd = (VendorDialog) DbManager.getFromCache(Enum.GameObjectType.VendorDialog, objectUUID);
+		if (vd != null)
+			return vd;
+		prepareCallable("SELECT * FROM `static_npc_vendordialog` WHERE `ID`=?");
+		setInt(1, objectUUID);
+		return (VendorDialog) getObjectSingle(objectUUID);
+	}
+}
diff --git a/src/engine/db/handlers/dbWarehouseHandler.java b/src/engine/db/handlers/dbWarehouseHandler.java
new file mode 100644
index 00000000..f859cfaa
--- /dev/null
+++ b/src/engine/db/handlers/dbWarehouseHandler.java
@@ -0,0 +1,449 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.db.handlers;
+
+import engine.Enum.GameObjectType;
+import engine.Enum.ProtectionState;
+import engine.Enum.TransactionType;
+import engine.gameManager.DbManager;
+import engine.math.Vector3fImmutable;
+import engine.objects.*;
+import engine.server.MBServerStatics;
+import org.joda.time.DateTime;
+import org.pmw.tinylog.Logger;
+
+import java.net.UnknownHostException;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class dbWarehouseHandler extends dbHandlerBase {
+
+	private static final ConcurrentHashMap<Integer, String> columns = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW);
+
+	public dbWarehouseHandler() {
+		this.localClass = Warehouse.class;
+		this.localObjectType = engine.Enum.GameObjectType.valueOf(this.localClass.getSimpleName());
+
+		if (columns.isEmpty()) {
+			createColumns();
+		}
+	}
+
+	public Warehouse CREATE_WAREHOUSE(Warehouse wh) {
+		try {
+			wh = this.addWarehouse(wh);
+		} catch (Exception e) {
+			Logger.error(e);
+			wh = null;
+			
+		}
+		return wh;
+	}
+
+	public ArrayList<AbstractGameObject> CREATE_WAREHOUSE( int parentZoneID, int OwnerUUID, String name, int meshUUID,
+			Vector3fImmutable location, float meshScale, int currentHP,
+			ProtectionState protectionState, int currentGold, int rank,
+			DateTime upgradeDate, int blueprintUUID, float w, float rotY) {
+
+		prepareCallable("CALL `WAREHOUSE_CREATE`(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? ,? ,? ,?, ?);");
+
+		setInt(1, parentZoneID);
+		setInt(2, OwnerUUID);
+		setString(3, name);
+		setInt(4, meshUUID);
+		setFloat(5, location.x);
+		setFloat(6, location.y);
+		setFloat(7, location.z);
+		setFloat(8, meshScale);
+		setInt(9, currentHP);
+		setString(10, protectionState.name());
+		setInt(11, currentGold);
+		setInt(12, rank);
+
+		if (upgradeDate != null) {
+			setTimeStamp(13, upgradeDate.getMillis());
+		} else {
+			setNULL(13, java.sql.Types.DATE);
+		}
+
+		setInt(14, blueprintUUID);
+		setFloat(15, w);
+		setFloat(16, rotY);
+
+		ArrayList<AbstractGameObject> list = new ArrayList<>();
+		//System.out.println(this.cs.get().toString());
+		try {
+			boolean work = execute();
+			if (work) {
+				ResultSet rs = this.cs.get().getResultSet();
+				while (rs.next()) {
+					addObject(list, rs);
+				}
+				rs.close();
+			} else {
+				Logger.info("Warehouse Creation Failed: " + this.cs.get().toString());
+				return list; //city creation failure
+			}
+			while (this.cs.get().getMoreResults()) {
+				ResultSet rs = this.cs.get().getResultSet();
+				while (rs.next()) {
+					addObject(list, rs);
+				}
+				rs.close();
+			}
+		} catch (SQLException e) {
+			Logger.info("Warehouse Creation Failed, SQLException: " + this.cs.get().toString() + e.toString());
+			return list; //city creation failure
+		} catch (UnknownHostException e) {
+			Logger.info("Warehouse Creation Failed, UnknownHostException: " + this.cs.get().toString());
+			return list; //city creation failure
+		} finally {
+			closeCallable();
+		}
+		return list;
+
+	}
+
+	//Don't call yet, not ready in DB. -
+	public boolean WAREHOUSE_ADD(Item item, Warehouse warehouse, ItemBase ib, int amount) {
+		if (item == null || warehouse == null || ib == null || !(dbWarehouseHandler.columns.containsKey(ib.getUUID()))) {
+			return false;
+		}
+		if ((item.getNumOfItems() - amount) < 0) {
+			return false;
+		}
+		if (!warehouse.getResources().containsKey(ib)) {
+			return false;
+		}
+
+		prepareCallable("CALL `warehouse_ADD`(?,?,?,?,?,?,?);");
+		setLong(1, (long) warehouse.getObjectUUID());
+		setInt(2, warehouse.getResources().get(ib));
+		setLong(3, (long) item.getObjectUUID());
+		setInt(4, item.getNumOfItems());
+		setInt(5, amount);
+		setString(6, dbWarehouseHandler.columns.get(ib.getUUID()));
+		setInt(7, ib.getUUID());
+		String result = getResult();
+
+		return (result != null && result.equals("success"));
+	}
+
+	private Warehouse addWarehouse(Warehouse toAdd) {
+		prepareCallable("CALL `warehouse_CREATE`(?);");
+		setInt(1, toAdd.getUID());
+		int objectUUID = (int) getUUID();
+		if (objectUUID > 0) {
+			return GET_WAREHOUSE(objectUUID);
+		}
+		return null;
+	}
+
+	public Warehouse GET_WAREHOUSE(int objectUUID) {
+		Warehouse warehouse = (Warehouse) DbManager.getFromCache(GameObjectType.Warehouse, objectUUID);
+		if (warehouse != null)
+			return warehouse;
+		prepareCallable("SELECT * FROM `obj_warehouse` WHERE `UID` = ?");
+		setInt(1, objectUUID);
+		return (Warehouse) getObjectSingle(objectUUID);
+	}
+
+	public boolean updateLocks(final Warehouse wh, long locks) {
+		prepareCallable("UPDATE `obj_warehouse` SET `warehouse_locks`=? WHERE `UID` = ?");
+		setLong(1, locks);
+		setInt(2, wh.getUID());
+		return (executeUpdate() != 0);
+	}
+
+	public boolean updateGold(final Warehouse wh, int amount ) {
+		prepareCallable("UPDATE `obj_warehouse` SET `warehouse_gold`=? WHERE `UID` = ?");
+		setInt(1, amount);
+		setInt(2, wh.getUID());
+		return (executeUpdate() != 0);
+	}
+
+	public boolean updateStone(final Warehouse wh, int amount) {
+		prepareCallable("UPDATE `obj_warehouse` SET `warehouse_stone`=? WHERE `UID` = ?");
+		setInt(1, amount);
+		setInt(2, wh.getUID());
+
+		return (executeUpdate() != 0);
+	}
+
+	public boolean updateTruesteel(final Warehouse wh, int amount) {
+		prepareCallable("UPDATE `obj_warehouse` SET `warehouse_truesteel`=? WHERE `UID` = ?");
+		setInt(1, amount);
+		setInt(2, wh.getUID());
+
+		return (executeUpdate() != 0);
+	}
+
+	public boolean updateIron(final Warehouse wh, int amount) {
+		prepareCallable("UPDATE `obj_warehouse` SET `warehouse_iron`=? WHERE `UID` = ?");
+		setInt(1, amount);
+		setInt(2, wh.getUID());
+
+		return (executeUpdate() != 0);
+	}
+
+	public boolean updateAdamant(final Warehouse wh, int amount) {
+		prepareCallable("UPDATE `obj_warehouse` SET `warehouse_adamant`=? WHERE `UID` = ?");
+		setInt(1, amount);
+		setInt(2, wh.getUID());
+
+		return (executeUpdate() != 0);
+	}
+
+	public boolean updateLumber(final Warehouse wh, int amount) {
+		prepareCallable("UPDATE `obj_warehouse` SET `warehouse_lumber`=? WHERE `UID` = ?");
+		setInt(1, amount);
+		setInt(2, wh.getUID());
+
+		return (executeUpdate() != 0);
+	}
+
+	public boolean updateOak(final Warehouse wh, int amount) {
+		prepareCallable("UPDATE `obj_warehouse` SET `warehouse_oak`=? WHERE `UID` = ?");
+		setInt(1, amount);
+		setInt(2, wh.getUID());
+
+		return (executeUpdate() != 0);
+	}
+
+	public boolean updateBronzewood(final Warehouse wh, int amount) {
+		prepareCallable("UPDATE `obj_warehouse` SET `warehouse_bronzewood`=? WHERE `UID` = ?");
+		setInt(1, amount);
+		setInt(2, wh.getUID());
+
+		return (executeUpdate() != 0);
+	}
+
+	public boolean updateMandrake(final Warehouse wh, int amount) {
+		prepareCallable("UPDATE `obj_warehouse` SET `warehouse_mandrake`=? WHERE `UID` = ?");
+		setInt(1, amount);
+		setInt(2, wh.getUID());
+
+		return (executeUpdate() != 0);
+	}
+
+	public boolean updateCoal(final Warehouse wh, int amount) {
+		prepareCallable("UPDATE `obj_warehouse` SET `warehouse_coal`=? WHERE `UID` = ?");
+		setInt(1, amount);
+		setInt(2, wh.getUID());
+
+		return (executeUpdate() != 0);
+	}
+
+	public boolean updateAgate(final Warehouse wh, int amount) {
+		prepareCallable("UPDATE `obj_warehouse` SET `warehouse_agate`=? WHERE `UID` = ?");
+		setInt(1, amount);
+		setInt(2, wh.getUID());
+
+		return (executeUpdate() != 0);
+	}
+
+	public boolean updateDiamond(final Warehouse wh, int amount) {
+		prepareCallable("UPDATE `obj_warehouse` SET `warehouse_diamond`=? WHERE `UID` = ?");
+		setInt(1, amount);
+		setInt(2, wh.getUID());
+
+		return (executeUpdate() != 0);
+	}
+
+	public boolean updateOnyx(final Warehouse wh, int amount) {
+		prepareCallable("UPDATE `obj_warehouse` SET `warehouse_onyx`=? WHERE `UID` = ?");
+		setInt(1, amount);
+		setInt(2, wh.getUID());
+
+		return (executeUpdate() != 0);
+	}
+
+	public boolean updateAzoth(final Warehouse wh, int amount) {
+		prepareCallable("UPDATE `obj_warehouse` SET `warehouse_azoth`=? WHERE `UID` = ?");
+		setInt(1, amount);
+		setInt(2, wh.getUID());
+
+		return (executeUpdate() != 0);
+	}
+
+	public boolean updateOrichalk(final Warehouse wh, int amount) {
+		prepareCallable("UPDATE `obj_warehouse` SET `warehouse_orichalk`=? WHERE `UID` = ?");
+		setInt(1, amount);
+		setInt(2, wh.getUID());
+
+		return (executeUpdate() != 0);
+	}
+
+	public boolean updateAntimony(final Warehouse wh, int amount) {
+		prepareCallable("UPDATE `obj_warehouse` SET `warehouse_antimony`=? WHERE `UID` = ?");
+		setInt(1, amount);
+		setInt(2, wh.getUID());
+
+		return (executeUpdate() != 0);
+	}
+
+	public boolean updateSulfur(final Warehouse wh, int amount) {
+		prepareCallable("UPDATE `obj_warehouse` SET `warehouse_sulfur`=? WHERE `UID` = ?");
+		setInt(1, amount);
+		setInt(2, wh.getUID());
+
+		return (executeUpdate() != 0);
+	}
+
+	public boolean updateQuicksilver(final Warehouse wh, int amount) {
+		prepareCallable("UPDATE `obj_warehouse` SET `warehouse_quicksilver`=? WHERE `UID` = ?");
+		setInt(1, amount);
+		setInt(2, wh.getUID());
+
+		return (executeUpdate() != 0);
+	}
+
+	public boolean updateGalvor(final Warehouse wh, int amount) {
+		prepareCallable("UPDATE `obj_warehouse` SET `warehouse_galvor`=? WHERE `UID` = ?");
+		setInt(1, amount);
+		setInt(2, wh.getUID());
+
+		return (executeUpdate() != 0);
+	}
+
+	public boolean updateWormwood(final Warehouse wh, int amount ) {
+		prepareCallable("UPDATE `obj_warehouse` SET `warehouse_wormwood`=? WHERE `UID` = ?");
+		setInt(1, amount);
+		setInt(2, wh.getUID());
+
+		return (executeUpdate() != 0);
+	}
+
+	public boolean updateObsidian(final Warehouse wh, int amount) {
+		prepareCallable("UPDATE `obj_warehouse` SET `warehouse_obsidian`=? WHERE `UID` = ?");
+		setInt(1, amount);
+		setInt(2, wh.getUID());
+
+		return (executeUpdate() != 0);
+	}
+
+	public boolean updateBloodstone(final Warehouse wh, int amount) {
+		prepareCallable("UPDATE `obj_warehouse` SET `warehouse_bloodstone`=? WHERE `UID` = ?");
+		setInt(1, amount);
+		setInt(2, wh.getUID());
+
+		return (executeUpdate() != 0);
+	}
+
+	public boolean updateMithril(final Warehouse wh, int amount) {
+		prepareCallable("UPDATE `obj_warehouse` SET `warehouse_mithril`=? WHERE `UID` = ?");
+		setInt(1, amount);
+		setInt(2, wh.getUID());
+
+		return (executeUpdate() != 0);
+	}
+
+	private static void createColumns() {
+		columns.put(1580000, "warehouse_stone");
+		columns.put(1580001, "warehouse_truesteel");
+		columns.put(1580002, "warehouse_iron");
+		columns.put(1580003, "warehouse_adamant");
+		columns.put(1580004, "warehouse_lumber");
+		columns.put(1580005, "warehouse_oak");
+		columns.put(1580006, "warehouse_bronzewood");
+		columns.put(1580007, "warehouse_mandrake");
+		columns.put(1580008, "warehouse_coal");
+		columns.put(1580009, "warehouse_agate");
+		columns.put(1580010, "warehouse_diamond");
+		columns.put(1580011, "warehouse_onyx");
+		columns.put(1580012, "warehouse_azoth");
+		columns.put(1580013, "warehouse_orichalk");
+		columns.put(1580014, "warehouse_antimony");
+		columns.put(1580015, "warehouse_sulfur");
+		columns.put(1580016, "warehouse_quicksilver");
+		columns.put(1580017, "warehouse_galvor");
+		columns.put(1580018, "warehouse_wormwood");
+		columns.put(1580019, "warehouse_obsidian");
+		columns.put(1580020, "warehouse_bloodstone");
+		columns.put(1580021, "warehouse_mithril");
+		columns.put(7, "warehouse_gold");
+	}
+
+	public boolean CREATE_TRANSACTION(int warehouseBuildingID, GameObjectType targetType, int targetUUID, TransactionType transactionType,Resource resource, int amount,DateTime date){
+		Transaction transactions = null;
+		prepareCallable("INSERT INTO `dyn_warehouse_transactions` (`warehouseUID`, `targetType`,`targetUID`, `type`,`resource`,`amount`,`date` ) VALUES (?,?,?,?,?,?,?)");
+		setLong(1, warehouseBuildingID);
+		setString(2, targetType.name());
+		setLong(3, targetUUID);
+		setString(4, transactionType.name());
+		setString(5, resource.name());
+		setInt(6,amount);
+		setTimeStamp(7,date.getMillis());
+		return (executeUpdate() != 0);
+	}
+
+
+
+	public static void addObject(ArrayList<AbstractGameObject> list, ResultSet rs) throws SQLException, UnknownHostException {
+		String type = rs.getString("type");
+		switch (type) {
+		case "building":
+			Building building = new Building(rs);
+			DbManager.addToCache(building);
+			list.add(building);
+			break;
+		case "warehouse":
+			Warehouse warehouse = new Warehouse(rs);
+			DbManager.addToCache(warehouse);
+			list.add(warehouse);
+			break;
+		}
+	}
+
+	public ArrayList<Transaction> GET_TRANSACTIONS_FOR_WAREHOUSE(final int warehouseUUID) {
+		ArrayList<Transaction> transactionsList = new ArrayList<>();
+		prepareCallable("SELECT * FROM dyn_warehouse_transactions WHERE `warehouseUID` = ?;");
+		setInt(1, warehouseUUID);
+		try {
+			ResultSet rs = executeQuery();
+
+			//shrines cached in rs for easy cache on creation.
+			while (rs.next()) {
+				Transaction transactions = new Transaction(rs);
+				transactionsList.add(transactions);
+			}
+
+		} catch (SQLException e) {
+			Logger.error( e.getErrorCode() + ' ' + e.getMessage(), e);
+		} finally {
+			closeCallable();
+		}
+		return transactionsList;
+	}
+
+	public void LOAD_ALL_WAREHOUSES() {
+
+		Warehouse thisWarehouse;
+
+		prepareCallable("SELECT `obj_warehouse`.*, `object`.`parent`, `object`.`type` FROM `object` LEFT JOIN `obj_warehouse` ON `object`.`UID` = `obj_warehouse`.`UID` WHERE `object`.`type` = 'warehouse';");
+
+		try {
+			ResultSet rs = executeQuery();
+			while (rs.next()) {
+				thisWarehouse = new Warehouse(rs);
+				thisWarehouse.runAfterLoad();
+				thisWarehouse.loadAllTransactions();
+			}
+
+		} catch (SQLException e) {
+			Logger.error(e.getErrorCode() + ' ' + e.getMessage(), e);
+		} finally {
+			closeCallable();
+		}
+
+	}
+}
diff --git a/src/engine/db/handlers/dbZoneHandler.java b/src/engine/db/handlers/dbZoneHandler.java
new file mode 100644
index 00000000..6a6990ae
--- /dev/null
+++ b/src/engine/db/handlers/dbZoneHandler.java
@@ -0,0 +1,93 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.db.handlers;
+
+import engine.Enum;
+import engine.gameManager.DbManager;
+import engine.objects.Zone;
+
+import java.sql.ResultSet;
+import java.util.ArrayList;
+
+public class dbZoneHandler extends dbHandlerBase {
+
+	public dbZoneHandler() {
+		this.localClass = Zone.class;
+		this.localObjectType = Enum.GameObjectType.valueOf(this.localClass.getSimpleName());
+	}
+
+	public ArrayList<Zone> GET_ALL_NODES(Zone zone) {
+		ArrayList<Zone> wsmList = new ArrayList<>();
+		wsmList.addAll(zone.getNodes());
+		if (zone.absX == 0.0f) {
+			zone.absX = zone.getXCoord();
+		}
+		if (zone.absY == 0.0f) {
+			zone.absY = zone.getYCoord();
+		}
+		if (zone.absZ == 0.0f) {
+			zone.absZ = zone.getZCoord();
+		}
+		for (Zone child : zone.getNodes()) {
+			child.absX = child.getXCoord() + zone.absX;
+			child.absY = child.getYCoord() + zone.absY;
+			child.absZ = child.getZCoord() + zone.absZ;
+			wsmList.addAll(this.GET_ALL_NODES(child));
+		}
+		return wsmList;
+	}
+
+	public Zone GET_BY_UID(long ID) {
+
+		Zone zone = (Zone) DbManager.getFromCache(Enum.GameObjectType.Zone, (int)ID);
+		if (zone != null)
+			return zone;
+		prepareCallable("SELECT `obj_zone`.*, `object`.`parent` FROM `object` INNER JOIN `obj_zone` ON `obj_zone`.`UID` = `object`.`UID` WHERE `object`.`UID` = ?;");
+		setLong(1, ID);
+		return (Zone) getObjectSingle((int) ID);
+	}
+
+	public ArrayList<Zone> GET_MAP_NODES(final int objectUUID) {
+		prepareCallable("SELECT `obj_zone`.*, `object`.`parent` FROM `object` INNER JOIN `obj_zone` ON `obj_zone`.`UID` = `object`.`UID` WHERE `object`.`parent` = ?;");
+		setLong(1, (long) objectUUID);
+		return getObjectList();
+	}
+
+	public ResultSet GET_ZONE_EXTENTS(final int loadNum) {
+		prepareCallable("SELECT * FROM `static_zone_size` WHERE `loadNum`=?;");
+		setInt(1, loadNum);
+		return executeQuery();
+	}
+
+	public String SET_PROPERTY(final Zone z, String name, Object new_value) {
+		prepareCallable("CALL zone_SETPROP(?,?,?)");
+		setLong(1, (long) z.getObjectUUID());
+		setString(2, name);
+		setString(3, String.valueOf(new_value));
+		return getResult();
+	}
+
+	public String SET_PROPERTY(final Zone z, String name, Object new_value, Object old_value) {
+		prepareCallable("CALL zone_GETSETPROP(?,?,?,?)");
+		setLong(1, (long) z.getObjectUUID());
+		setString(2, name);
+		setString(3, String.valueOf(new_value));
+		setString(4, String.valueOf(old_value));
+		return getResult();
+	}
+
+	public boolean DELETE_ZONE(final Zone zone) {
+
+		prepareCallable("DELETE FROM `object` WHERE `UID` = ? AND `type` = 'zone'");
+		setInt(1, zone.getObjectUUID());
+		return (executeUpdate() != 0);
+	}
+
+}
diff --git a/src/engine/devcmd/AbstractDevCmd.java b/src/engine/devcmd/AbstractDevCmd.java
new file mode 100644
index 00000000..75a54683
--- /dev/null
+++ b/src/engine/devcmd/AbstractDevCmd.java
@@ -0,0 +1,181 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.devcmd;
+
+import engine.Enum.GameObjectType;
+import engine.gameManager.ChatManager;
+import engine.gameManager.DbManager;
+import engine.objects.*;
+
+import java.util.ArrayList;
+
+public abstract class AbstractDevCmd {
+
+    protected final ArrayList<String> cmdStrings;
+    private AbstractGameObject tr;
+    private String rsult;
+
+    public AbstractDevCmd(String cmdString) {
+        super();
+        this.cmdStrings = new ArrayList<>();
+        this.addCmdString(cmdString);
+        this.rsult = "";
+    }
+
+    /**
+     * This function is called by the DevCmdManager. Method splits argString
+     * into a String array and then calls the subclass specific _doCmd method.
+     */
+
+    public void doCmd(PlayerCharacter pcSender, String argString,
+            AbstractGameObject target) {
+        String[] args = argString.split(" ");
+
+        if (pcSender == null) {
+            return;
+        }
+
+        if (args.length > 0 && args[0].equalsIgnoreCase("?")) {
+            this.sendHelp(pcSender);
+            this.sendUsage(pcSender);
+        } else {
+            this.tr = target;
+            this._doCmd(pcSender, args, target);
+        }
+    }
+
+    protected abstract void _doCmd(PlayerCharacter pcSender, String[] args,
+            AbstractGameObject target);
+
+    /**
+     * Returns the string sent to the client that displays how to use this
+     * command.
+     */
+
+    public final String getUsageString() {
+        return "Usage: " + this._getUsageString();
+    }
+
+    protected abstract String _getUsageString();
+
+    /**
+     * Returns the string sent to the client that displays what this command
+     * does.
+     */
+
+    public final String getHelpString() {
+        return this.getMainCmdString() + ": " + this._getHelpString();
+    }
+
+    protected abstract String _getHelpString();
+
+    public final ArrayList<String> getCmdStrings() {
+        return cmdStrings;
+    }
+
+    public final String getMainCmdString() {
+        return this.cmdStrings.get(0);
+    }
+
+    protected void addCmdString(String cmdString) {
+        String lowercase = cmdString.toLowerCase();
+        this.cmdStrings.add(lowercase);
+    }
+
+    public void setTarget(AbstractGameObject ago) {
+        this.tr = ago;
+    }
+
+    public AbstractGameObject getTarget() {
+        return this.tr;
+    }
+
+    public void setResult(String result) {
+        this.rsult = result;
+    }
+
+    public String getResult() {
+        return this.rsult;
+    }
+
+    /*
+     * Helper functions
+     */
+    protected void sendUsage(PlayerCharacter pc) {
+        this.throwbackError(pc, this.getUsageString());
+    }
+
+    protected void sendHelp(PlayerCharacter pc) {
+        this.throwbackError(pc, this.getHelpString());
+    }
+
+    protected void throwbackError(PlayerCharacter pc, String msgText) {
+        ChatManager.chatSystemError(pc, msgText);
+    }
+
+    protected void throwbackInfo(PlayerCharacter pc, String msgText) {
+        ChatManager.chatSystemInfo(pc, msgText);
+    }
+
+    /*
+     * Misc tools/helpers
+     */
+    protected static Building getTargetAsBuilding(PlayerCharacter pc) {
+        int targetType = pc.getLastTargetType().ordinal();
+        int targetID = pc.getLastTargetID();
+        if (targetType == GameObjectType.Building.ordinal()) {
+            Building b = (Building) DbManager
+                    .getFromCache(GameObjectType.Building, targetID);
+            if (b == null) {
+                ChatManager.chatSystemError(
+                        pc,
+                        "Command Failed. Could not find building of ID "
+                        + targetID);
+                return null;
+            }
+            return b;
+        } else {
+            return null;
+        }
+    }
+
+    protected static Mob getTargetAsMob(PlayerCharacter pc) {
+        int targetType = pc.getLastTargetType().ordinal();
+        int targetID = pc.getLastTargetID();
+        if (targetType == GameObjectType.Mob.ordinal()) {
+            Mob b = Mob.getMob(targetID);
+            if (b == null) {
+                ChatManager.chatSystemError(pc,
+                        "Command Failed. Could not find Mob of ID " + targetID);
+                return null;
+            }
+            return b;
+        } else {
+            return null;
+        }
+    }
+
+    protected static NPC getTargetAsNPC(PlayerCharacter pc) {
+        int targetType = pc.getLastTargetType().ordinal();
+        int targetID = pc.getLastTargetID();
+        if (targetType == GameObjectType.NPC.ordinal()) {
+            NPC b = NPC.getFromCache(targetID);
+            if (b == null) {
+                ChatManager.chatSystemError(pc,
+                        "Command Failed. Could not find NPC of ID " + targetID);
+                return null;
+            }
+            return b;
+        } else {
+            return null;
+        }
+    }
+
+}
diff --git a/src/engine/devcmd/cmds/AddBuildingCmd.java b/src/engine/devcmd/cmds/AddBuildingCmd.java
new file mode 100644
index 00000000..50e371ff
--- /dev/null
+++ b/src/engine/devcmd/cmds/AddBuildingCmd.java
@@ -0,0 +1,127 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.devcmd.cmds;
+
+import engine.Enum.ProtectionState;
+import engine.InterestManagement.WorldGrid;
+import engine.devcmd.AbstractDevCmd;
+import engine.gameManager.ChatManager;
+import engine.gameManager.DbManager;
+import engine.gameManager.ZoneManager;
+import engine.math.Vector3f;
+import engine.math.Vector3fImmutable;
+import engine.objects.*;
+import engine.server.MBServerStatics;
+
+public class AddBuildingCmd extends AbstractDevCmd {
+
+	public AddBuildingCmd() {
+        super("addbuilding");
+//		super("addbuilding", MBServerStatics.ACCESS_GROUP_DESIGNER_UP, 0, false, true);
+        this.addCmdString("building");
+    }
+
+	@Override
+	protected void _doCmd(PlayerCharacter pc, String[] words,
+			AbstractGameObject target) {
+
+		// Arg Count Check
+		if (words.length != 2) {
+			this.sendUsage(pc);
+			return;
+		}
+
+		int ID;
+		int rank;
+                Blueprint blueprint;
+                
+		try {
+			ID = Integer.parseInt(words[0]);
+			rank = Integer.parseInt(words[1]);
+		} catch (Exception e) {
+			throwbackError(pc, "Invalid addBuilding Command. Need Building ID and rank.");
+			return; // NaN
+		}
+		if (ID < 1) {
+			throwbackError(pc,
+					"Invalid addBuilding Command. Invalid Building ID.");
+			return;
+		}
+		Vector3f rot = new Vector3f(0.0f, 0.0f, 0.0f);
+		float w = 1f;
+		Zone zone = ZoneManager.findSmallestZone(pc.getLoc());
+
+		if (zone == null) {
+			throwbackError(pc, "Failed to find zone to place building in.");
+			return;
+		}
+
+               blueprint = Blueprint.getBlueprint(ID);
+               
+               if ((blueprint != null) && (rank > blueprint.getMaxRank())) {
+                   throwbackError(pc, rank + " is not a valid rank for this building");
+			return;
+               }
+		
+		Building likeBuilding = DbManager.BuildingQueries.GET_BUILDING_BY_MESH(ID);
+                
+		if (likeBuilding != null) {
+			rot = likeBuilding.getRot();
+			w = likeBuilding.getw();
+		}
+                
+                String buildingName = "";
+                int blueprintUUID = 0;
+                
+                Vector3fImmutable localLoc = ZoneManager.worldToLocal(pc.getLoc(), zone);
+                
+                if (localLoc == null)
+                	return;
+                
+                if (blueprint != null) {
+                 buildingName = blueprint.getName();
+                 blueprintUUID = blueprint.getBlueprintUUID();
+                 }
+                 
+                Building building = DbManager.BuildingQueries.
+                        CREATE_BUILDING(
+                                   zone.getObjectUUID(), 0, buildingName, ID,
+                                   localLoc, 1.0f, 0, ProtectionState.PROTECTED, 0, rank,
+                                   null, blueprintUUID, w, rot.y);
+         
+
+		if (building == null) {
+			throwbackError(pc, "Failed to add building.");
+			return;
+		}
+
+		building.setRot(rot);
+		building.setw(w);
+
+		building.setObjectTypeMask(MBServerStatics.MASK_BUILDING);
+	        WorldGrid.addObject(building, pc);
+		ChatManager.chatSayInfo(pc,
+				"Building with ID " + building.getObjectUUID() + " added");
+
+		this.setResult(String.valueOf(building.getObjectUUID()));
+
+	}
+
+	@Override
+	protected String _getHelpString() {
+		return "Creates a building of type 'buildingID' at the location your character is standing.";
+	}
+
+	@Override
+	protected String _getUsageString() {
+		return "' /addbuilding buildingID rank' || ' /building buildingID rank'";
+	}
+
+}
diff --git a/src/engine/devcmd/cmds/AddGoldCmd.java b/src/engine/devcmd/cmds/AddGoldCmd.java
new file mode 100644
index 00000000..2d339821
--- /dev/null
+++ b/src/engine/devcmd/cmds/AddGoldCmd.java
@@ -0,0 +1,77 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.devcmd.cmds;
+
+import engine.devcmd.AbstractDevCmd;
+import engine.gameManager.ChatManager;
+import engine.objects.AbstractGameObject;
+import engine.objects.Item;
+import engine.objects.PlayerCharacter;
+
+/**
+ * @author Eighty
+ *
+ */
+public class AddGoldCmd extends AbstractDevCmd {
+
+	public AddGoldCmd() {
+        super("addgold");
+    }
+
+	@Override
+	protected void _doCmd(PlayerCharacter pc, String[] words,
+			AbstractGameObject target) {
+		if (words.length != 1) {
+			this.sendUsage(pc);
+			return;
+		}
+
+		Item gold = pc.getCharItemManager().getGoldInventory();
+		int curAmt;
+		if (gold == null)
+			curAmt = 0;
+		else
+			curAmt = gold.getNumOfItems();
+
+		int amt;
+		try {
+			amt = Integer.parseInt(words[0]);
+		} catch (NumberFormatException e) {
+			throwbackError(pc, "Quantity must be a number, " + words[0] + " is invalid");
+			return;
+		}
+		if (amt < 1 || amt > 10000000) {
+			throwbackError(pc, "Quantity must be between 1 and 10000000 (10 million)");
+			return;
+		} else if ((curAmt + amt) > 10000000) {
+			throwbackError(pc, "This would place your inventory over 10,000,000 gold.");
+			return;
+		}
+
+		if (!pc.getCharItemManager().addGoldToInventory(amt, true)) {
+			throwbackError(pc, "Failed to add gold to inventory");
+			return;
+		}
+
+		ChatManager.chatSayInfo(pc, amt + " gold added to inventory");
+		pc.getCharItemManager().updateInventory();
+	}
+
+	@Override
+	protected String _getHelpString() {
+        return "adds gold to inventory";
+	}
+
+	@Override
+	protected String _getUsageString() {
+        return "' /addGold quantity'";
+	}
+
+}
diff --git a/src/engine/devcmd/cmds/AddMobCmd.java b/src/engine/devcmd/cmds/AddMobCmd.java
new file mode 100644
index 00000000..edbceaf7
--- /dev/null
+++ b/src/engine/devcmd/cmds/AddMobCmd.java
@@ -0,0 +1,111 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.devcmd.cmds;
+
+import engine.Enum.GameObjectType;
+import engine.devcmd.AbstractDevCmd;
+import engine.gameManager.ChatManager;
+import engine.gameManager.DbManager;
+import engine.gameManager.ZoneManager;
+import engine.math.Vector3fImmutable;
+import engine.objects.*;
+import org.pmw.tinylog.Logger;
+
+/**
+ * @author Eighty
+ *
+ */
+public class AddMobCmd extends AbstractDevCmd {
+
+	public AddMobCmd() {
+        super("mob");
+    }
+
+	@Override
+	protected void _doCmd(PlayerCharacter pc, String[] words,
+			AbstractGameObject target) {
+		if (words.length != 1) {
+			this.sendUsage(pc);
+			return;
+		}
+
+		Zone zone = ZoneManager.findSmallestZone(pc.getLoc());
+
+		if (words[0].equals("all")){
+
+			for (AbstractGameObject mobbaseAGO: DbManager.getList(GameObjectType.MobBase)){
+				MobBase mb = (MobBase)mobbaseAGO;
+				int loadID = mb.getObjectUUID();
+				Mob mob = Mob.createMob( loadID, Vector3fImmutable.getRandomPointInCircle(pc.getLoc(), 100),
+						null, true, zone, null,0);
+				if (mob != null) {
+					mob.updateDatabase();
+					this.setResult(String.valueOf(mob.getDBID()));
+				} else {
+					throwbackError(pc, "Failed to create mob of type " + loadID);
+					Logger.error( "Failed to create mob of type "
+							+ loadID);
+				}
+			}
+			return;
+		}
+
+
+		int loadID;
+		try {
+			loadID = Integer.parseInt(words[0]);
+		} catch (NumberFormatException e) {
+			throwbackError(pc, "Supplied type " + words[0]
+					+ " failed to parse to an Integer");
+			return;
+		} catch (Exception e) {
+			throwbackError(pc,
+					"An unknown exception occurred when trying to use mob command for type "
+							+ words[0]);
+			return; // NaN
+		}
+
+
+		if (zone == null) {
+			throwbackError(pc, "Failed to find zone to place mob in.");
+			return;
+		}
+
+		if (zone.isPlayerCity()) {
+			throwbackError(pc, "Cannot use ./mob on Player cities. Try ./servermob instead.");
+			return;
+		}
+
+
+		Mob mob = Mob.createMob( loadID, pc.getLoc(),
+				null, true, zone, null,0);
+		if (mob != null) {
+			mob.updateDatabase();
+			ChatManager.chatSayInfo(pc,
+					"Mob with ID " + mob.getDBID() + " added");
+			this.setResult(String.valueOf(mob.getDBID()));
+		} else {
+			throwbackError(pc, "Failed to create mob of type " + loadID);
+			Logger.error("Failed to create mob of type "
+					+ loadID);
+		}
+	}
+
+	@Override
+	protected String _getHelpString() {
+        return "Creates a Mob of type 'mobID' at the location your character is standing";
+	}
+
+	@Override
+	protected String _getUsageString() {
+        return "' /mob mobID'";
+	}
+
+}
diff --git a/src/engine/devcmd/cmds/AddMobPowerCmd.java b/src/engine/devcmd/cmds/AddMobPowerCmd.java
new file mode 100644
index 00000000..2b553b42
--- /dev/null
+++ b/src/engine/devcmd/cmds/AddMobPowerCmd.java
@@ -0,0 +1,98 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+
+
+
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.devcmd.cmds;
+
+import engine.Enum.GameObjectType;
+import engine.devcmd.AbstractDevCmd;
+import engine.gameManager.DbManager;
+import engine.gameManager.PowersManager;
+import engine.objects.AbstractGameObject;
+import engine.objects.Mob;
+import engine.objects.PlayerCharacter;
+import engine.powers.PowersBase;
+
+/**
+ * 
+ * @author Eighty
+ * 
+ */
+public class AddMobPowerCmd extends AbstractDevCmd {
+
+	public AddMobPowerCmd() {
+        super("addmobpower");
+    }
+
+	@Override
+	protected void _doCmd(PlayerCharacter pcSender, String[] args,
+			AbstractGameObject target) {
+		
+	
+		if(args.length != 2){
+			this.sendUsage(pcSender);
+			return;
+		}
+		
+	if (target.getObjectType() != GameObjectType.Mob){
+		this.throwbackError(pcSender, "Target is not a valid Mob.");
+		return;
+	}
+	Mob mobTarget = (Mob)target;
+
+		
+		int rank = 0;
+		String idString = args[0];
+		
+		try{
+			rank = Integer.valueOf(args[1]);
+		}catch(Exception e){
+			this.throwbackInfo(pcSender, "Failed to Parse an Integer.");
+			return;
+		}
+		
+		PowersBase pb = PowersManager.getPowerByIDString(idString);
+		if (pb == null){
+			this.throwbackError(pcSender, "not a valid Effect. IDString is Case Sensitive.");
+			return;
+		}
+		
+		if (!DbManager.MobBaseQueries.ADD_MOBBASE_POWER(mobTarget.getMobBaseID(), pb.getToken(), rank)){
+			this.throwbackError(pcSender, "Failed to update Database");
+		}
+		
+		mobTarget.getMobBase().updatePowers();
+		
+		this.throwbackInfo(pcSender, "Successfuly added Power " + pb.getIDString() + " to Mobbase with UID " + mobTarget.getMobBaseID());
+		
+		
+	}
+
+	@Override
+	protected String _getUsageString() {
+        return "' /addmobpower poweridstring rank";
+	}
+
+	@Override
+	protected String _getHelpString() {
+        return "Temporarily add visual effects to Character";
+	}
+
+}
diff --git a/src/engine/devcmd/cmds/AddMobRuneCmd.java b/src/engine/devcmd/cmds/AddMobRuneCmd.java
new file mode 100644
index 00000000..54a4ceef
--- /dev/null
+++ b/src/engine/devcmd/cmds/AddMobRuneCmd.java
@@ -0,0 +1,98 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+
+
+
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.devcmd.cmds;
+
+import engine.Enum.GameObjectType;
+import engine.devcmd.AbstractDevCmd;
+import engine.gameManager.DbManager;
+import engine.objects.AbstractGameObject;
+import engine.objects.Mob;
+import engine.objects.PlayerCharacter;
+import engine.objects.RuneBase;
+
+/**
+ * 
+ * @author Eighty
+ * 
+ */
+public class AddMobRuneCmd extends AbstractDevCmd {
+
+	public AddMobRuneCmd() {
+        super("addmobrune");
+    }
+
+	@Override
+	protected void _doCmd(PlayerCharacter pcSender, String[] args,
+			AbstractGameObject target) {
+		
+		
+		if(args.length != 1){
+			this.sendUsage(pcSender);
+			return;
+		}
+		
+	if (target.getObjectType() != GameObjectType.Mob){
+		this.throwbackError(pcSender, "Target is not a valid Mob.");
+		return;
+	}
+	Mob mobTarget = (Mob)target;
+
+		
+		int runeID = 0;
+		try{
+			runeID = Integer.valueOf(args[0]);
+		}catch(Exception e){
+			this.throwbackInfo(pcSender, "Failed to Parse an Integer.");
+			return;
+		}
+		
+		RuneBase rune = RuneBase.getRuneBase(runeID);
+		if (rune == null){
+			this.throwbackError(pcSender, "Invalid Rune ID");
+			return;
+		}
+		
+		
+		if (!DbManager.MobBaseQueries.ADD_MOBBASE_RUNE(mobTarget.getMobBaseID(), runeID)){
+			this.throwbackError(pcSender, "Failed to update Database");
+			return;
+		}
+		
+		mobTarget.getMobBase().updateRunes();
+		
+		this.throwbackInfo(pcSender, "Successfuly added rune  " + rune.getName() + " to Mobbase with UID " + mobTarget.getMobBaseID());
+		
+		
+		
+	}
+
+	@Override
+	protected String _getUsageString() {
+        return "' /visualeffect visualeffectID";
+	}
+
+	@Override
+	protected String _getHelpString() {
+        return "Temporarily add visual effects to Character";
+	}
+
+}
diff --git a/src/engine/devcmd/cmds/AddNPCCmd.java b/src/engine/devcmd/cmds/AddNPCCmd.java
new file mode 100644
index 00000000..4d3f5716
--- /dev/null
+++ b/src/engine/devcmd/cmds/AddNPCCmd.java
@@ -0,0 +1,111 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.devcmd.cmds;
+
+import engine.Enum.GameObjectType;
+import engine.InterestManagement.WorldGrid;
+import engine.devcmd.AbstractDevCmd;
+import engine.gameManager.BuildingManager;
+import engine.gameManager.ChatManager;
+import engine.gameManager.DbManager;
+import engine.gameManager.ZoneManager;
+import engine.objects.*;
+import org.pmw.tinylog.Logger;
+
+/**
+ * @author Eighty
+ *
+ */
+public class AddNPCCmd extends AbstractDevCmd {
+
+	public AddNPCCmd() {
+        super("npc");
+    }
+
+	@Override
+	protected void _doCmd(PlayerCharacter pc, String[] words,
+			AbstractGameObject target) {
+		int contractID;
+		String name = "";
+		int level = 0;
+
+		if (words.length < 2) {
+			this.sendUsage(pc);
+			return;
+		}
+		try {
+			contractID = Integer.parseInt(words[0]);
+			level = Integer.parseInt(words[1]);
+
+			for (int i = 2; i < words.length; i++) {
+				name += words[i];
+				if (i + 1 < words.length)
+					name += "";
+			}
+
+		} catch (NumberFormatException e) {
+			throwbackError(pc,
+					"Failed to parse supplied contractID or level to an Integer.");
+			return; // NaN
+		}
+
+		Contract contract = DbManager.ContractQueries.GET_CONTRACT(contractID);
+
+		if (contract == null || level < 1 || level > 75) {
+			throwbackError(pc,
+					"Invalid addNPC Command. Need contract ID, and level");
+			return; // NaN
+		}
+
+		// Pick a random name
+		if (name.isEmpty())
+			name = NPC.getPirateName(contract.getMobbaseID());
+
+		Zone zone = ZoneManager.findSmallestZone(pc.getLoc());
+
+		if (zone == null) {
+			throwbackError(pc, "Failed to find zone to place npc in.");
+			return;
+		}
+
+		if (target != null)
+			if (target.getObjectType() == GameObjectType.Building){
+				Building parentBuilding = (Building)target;
+				BuildingManager.addHirelingForWorld(parentBuilding, pc, parentBuilding.getLoc(), parentBuilding.getParentZone(), contract, level);
+				return;
+			}
+
+		NPC npc = NPC.createNPC(name, contractID,
+				pc.getLoc(), null, true, zone, (short)level, true, null);
+
+		if (npc != null) {
+			WorldGrid.addObject(npc, pc);
+			ChatManager.chatSayInfo(pc,
+					"NPC with ID " + npc.getDBID() + " added");
+			this.setResult(String.valueOf(npc.getDBID()));
+		} else {
+			throwbackError(pc, "Failed to create npc of contract type "
+					+ contractID);
+			Logger.error(
+					"Failed to create npc of contract type " + contractID);
+		}
+	}
+
+	@Override
+	protected String _getHelpString() {
+		return "Creates an NPC of type 'npcID' at the location your character is standing";
+	}
+
+	@Override
+	protected String _getUsageString() {
+		return "' /npc npcID level name'";
+	}
+
+}
diff --git a/src/engine/devcmd/cmds/ApplyBonusCmd.java b/src/engine/devcmd/cmds/ApplyBonusCmd.java
new file mode 100644
index 00000000..76234e1c
--- /dev/null
+++ b/src/engine/devcmd/cmds/ApplyBonusCmd.java
@@ -0,0 +1,158 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.devcmd.cmds;
+
+import engine.Enum.PowerActionType;
+import engine.devcmd.AbstractDevCmd;
+import engine.gameManager.ChatManager;
+import engine.gameManager.PowersManager;
+import engine.objects.AbstractGameObject;
+import engine.objects.PlayerCharacter;
+import engine.powers.ActionsBase;
+import engine.powers.PowersBase;
+import engine.powers.effectmodifiers.AbstractEffectModifier;
+import engine.util.ThreadUtils;
+import org.pmw.tinylog.Logger;
+
+import java.util.HashMap;
+import java.util.HashSet;
+
+public class ApplyBonusCmd extends AbstractDevCmd {
+
+	public ApplyBonusCmd() {
+		super("applybonus");
+	}
+
+	@Override
+	protected void _doCmd(PlayerCharacter pcSender, String[] words,
+			AbstractGameObject target) {
+		
+		String action = words[0];
+		
+		PowerActionType actionType = null;
+		
+		HashMap<String,HashSet<String>> appliedMods = new HashMap<>();
+		
+		try{
+			
+			if (action.equals("all") == false)
+		for (PowerActionType powerActionType : PowerActionType.values()){
+			if (powerActionType.name().equalsIgnoreCase(action) == false)
+				continue;
+			actionType = powerActionType;
+			break;
+		}
+			
+		}catch(Exception e){
+			this.throwbackError(pcSender, "Invalid power Action type for " + action);
+			this.throwbackInfo(pcSender, "Valid Types : " + this.getActionTypes());
+			return;
+		}
+		if (action.equals("all") == false)
+		if (actionType == null){
+			this.throwbackError(pcSender, "Invalid power Action type for " + action);
+			this.throwbackInfo(pcSender, "Valid Types : " + this.getActionTypes());
+			return;
+		}
+		
+		
+		for (PowersBase pb : PowersManager.powersBaseByIDString.values()){
+			if (pb.getActions() == null || pb.getActions().isEmpty())
+				continue;
+			
+			for (ActionsBase ab: pb.getActions()){
+				if (ab.getPowerAction() == null)
+					continue;
+				if (action.equals("all") == false)
+				if (ab.getPowerAction().getType().equalsIgnoreCase(action) == false)
+					continue;
+				String effect1 = "";
+				String effect2 = "";
+				ChatManager.chatSystemInfo(pcSender,"Applying Power " + pb.getName() + " : " +pb.getDescription());
+				if (ab.getPowerAction().getEffectsBase() == null){
+					
+					try {
+						PowersManager.runPowerAction(pcSender, pcSender, pcSender.getLoc(), ab, 1, pb);
+					} catch (Exception e) {
+						// TODO Auto-generated catch block
+						e.printStackTrace();
+					}
+					ThreadUtils.sleep(500);
+					continue;
+				}
+
+			
+					if (ab.getPowerAction().getEffectsBase().getModifiers() == null || ab.getPowerAction().getEffectsBase().getModifiers().isEmpty()){
+						try {
+							PowersManager.runPowerAction(pcSender, pcSender, pcSender.getLoc(), ab, 1, pb);
+						} catch (Exception e) {
+							// TODO Auto-generated catch block
+							e.printStackTrace();
+						}
+						continue;
+					}
+						
+					boolean run = true;
+					for (AbstractEffectModifier mod : ab.getPowerAction().getEffectsBase().getModifiers()){
+						if (appliedMods.containsKey(mod.modType.name()) == false){
+							appliedMods.put(mod.modType.name(), new HashSet<>());
+						}
+						
+//						if (appliedMods.get(mod.modType.name()).contains(mod.sourceType.name())){
+//							continue;
+//						}
+						
+						appliedMods.get(mod.modType.name()).add(mod.sourceType.name());
+						try{
+							try {
+								PowersManager.runPowerAction(pcSender, pcSender, pcSender.getLoc(), ab, 1, pb);
+							} catch (Exception e) {
+								// TODO Auto-generated catch block
+								e.printStackTrace();
+							}
+	
+						}catch(Exception e){
+							Logger.error(e);
+						}
+						break;
+						
+							
+					
+					
+				}
+			
+			}
+		}	
+	}
+
+
+	@Override
+	protected String _getUsageString() {
+		return "' /bounds'";
+	}
+
+	@Override
+	protected String _getHelpString() {
+		return "Audits all the mobs in a zone.";
+
+	}
+	
+	private String getActionTypes(){
+		String output = "";
+		
+		for (PowerActionType actionType : PowerActionType.values()){
+			output += actionType.name() + " | ";
+			
+		}
+		
+		return output.substring(0, output.length() -3);
+	}
+
+}
diff --git a/src/engine/devcmd/cmds/ApplyStatModCmd.java b/src/engine/devcmd/cmds/ApplyStatModCmd.java
new file mode 100644
index 00000000..21bf52ab
--- /dev/null
+++ b/src/engine/devcmd/cmds/ApplyStatModCmd.java
@@ -0,0 +1,149 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.devcmd.cmds;
+
+import engine.devcmd.AbstractDevCmd;
+import engine.gameManager.ChatManager;
+import engine.gameManager.PowersManager;
+import engine.math.Vector3fImmutable;
+import engine.objects.AbstractCharacter;
+import engine.objects.AbstractGameObject;
+import engine.objects.PlayerCharacter;
+import engine.powers.PowersBase;
+
+import java.util.ArrayList;
+
+/**
+ *
+ * @author Eighty
+ *
+ */
+public class ApplyStatModCmd extends AbstractDevCmd {
+
+	public ApplyStatModCmd() {
+        super("applystatmod");
+    }
+
+	private static int cnt = 0;
+
+	@Override
+	protected void _doCmd(PlayerCharacter pcSender, String[] args,
+			AbstractGameObject target) {
+		if(args.length < 1) {
+			//		if(args.length < 2) {
+			this.sendUsage(pcSender);
+			return;
+		}
+
+		if(!(target instanceof AbstractCharacter)) {
+			this.sendHelp(pcSender);
+			return;
+		}
+
+        this.setTarget(pcSender); //for logging
+
+		int spellID;
+		int powerAction = 0;
+		if (args[0].toLowerCase().contains("all")){
+
+			int amount = 0;
+			if (args.length == 1) {
+				amount = ApplyStatModCmd.cnt;
+				ApplyStatModCmd.cnt++;
+			} else {
+				amount = Integer.valueOf(args[1]);
+				ApplyStatModCmd.cnt = amount+1;
+			}
+
+
+			ArrayList<PowersBase> pbList = new ArrayList<>();
+			pbList.add(PowersManager.getPowerByToken(429047968));
+			pbList.add(PowersManager.getPowerByToken(429768864));
+			pbList.add(PowersManager.getPowerByToken(428458144));
+			pbList.add(PowersManager.getPowerByToken(428677994));
+			pbList.add(PowersManager.getPowerByToken(431874079));
+			pbList.add(PowersManager.getPowerByToken(431081336));
+
+
+
+
+
+			for (PowersBase pb:pbList){
+				if(amount <= 0) {
+					if (pb.getToken() ==428677994)
+						powerAction = 1;
+					PowersManager.removeEffect(pcSender, pb.getActions().get(powerAction), false, false);
+					continue;
+				} else if(amount > 9999 || amount < 21) {
+					ChatManager.chatSystemInfo(pcSender, "Amount must be between 21 and 9999 inclusive.");
+					return;
+				}
+
+				if (pb.getToken() ==428677994){
+					PowersManager.removeEffect(pcSender, pb.getActions().get(powerAction), false, false);
+					PowersManager.runPowerAction(pcSender, pcSender, Vector3fImmutable.ZERO, pb.getActions().get(powerAction), amount - 20, pb);
+				}
+				if (pb.getToken() ==428677994)
+					powerAction = 1;
+				PowersManager.removeEffect(pcSender, pb.getActions().get(powerAction), false, false);
+				PowersManager.runPowerAction(pcSender, pcSender, Vector3fImmutable.ZERO, pb.getActions().get(powerAction), amount - 20, pb);
+			}
+			return;
+		}
+		if(args[0].toLowerCase().contains("con")) {
+			spellID = 429047968;	//Blessing of Health
+		} else if(args[0].toLowerCase().contains("str")) {
+			spellID = 429768864;	//Blessing of Might
+		} else if(args[0].toLowerCase().contains("dex")) {
+			spellID = 428458144;	//Blessing of Dexterity
+		} else if(args[0].toLowerCase().contains("int")) {
+			spellID = 428677994;	//Bard Spi - TODO
+			powerAction = 1;
+		} else if(args[0].toLowerCase().contains("spi")) {
+			spellID = 428677994;	//Bard Spi
+		} else{
+			ChatManager.chatSystemInfo(pcSender, "No valid stat found.");
+			return;
+		}
+
+		PowersBase pb = PowersManager.getPowerByToken(spellID);
+
+		int amount = 0;
+		if (args.length == 1) {
+			amount = ApplyStatModCmd.cnt;
+			ApplyStatModCmd.cnt++;
+		} else {
+			amount = Integer.valueOf(args[1]);
+			ApplyStatModCmd.cnt = amount+1;
+		}
+		//		int amount = Integer.valueOf(args[1]);
+		if(amount <= 0) {
+			PowersManager.removeEffect(pcSender, pb.getActions().get(powerAction), false, false);
+			return;
+		} else if(amount > 9999 || amount < 21) {
+			ChatManager.chatSystemInfo(pcSender, "Amount must be between 21 and 9999 inclusive.");
+			return;
+		}
+
+		PowersManager.removeEffect(pcSender, pb.getActions().get(powerAction), false, false);
+		PowersManager.runPowerAction(pcSender, pcSender, Vector3fImmutable.ZERO, pb.getActions().get(powerAction), amount - 20, pb);
+	}
+
+	@Override
+	protected String _getUsageString() {
+        return "' /applystatmod <stat> [trains]'";
+	}
+
+	@Override
+	protected String _getHelpString() {
+        return "You must be targeting a player!";
+	}
+
+}
diff --git a/src/engine/devcmd/cmds/AuditFailedItemsCmd.java b/src/engine/devcmd/cmds/AuditFailedItemsCmd.java
new file mode 100644
index 00000000..216e05fb
--- /dev/null
+++ b/src/engine/devcmd/cmds/AuditFailedItemsCmd.java
@@ -0,0 +1,130 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.devcmd.cmds;
+
+import engine.Enum.ModType;
+import engine.devcmd.AbstractDevCmd;
+import engine.gameManager.PowersManager;
+import engine.net.ItemProductionManager;
+import engine.objects.*;
+import engine.powers.effectmodifiers.AbstractEffectModifier;
+import engine.powers.poweractions.AbstractPowerAction;
+import org.pmw.tinylog.Logger;
+
+public class AuditFailedItemsCmd extends AbstractDevCmd {
+
+	public AuditFailedItemsCmd() {
+		super("faileditems");
+	}
+
+	@Override
+	protected void _doCmd(PlayerCharacter pcSender, String[] words,
+			AbstractGameObject target) {
+	
+		
+      	   
+      	   if (ItemProductionManager.FailedItems.isEmpty())
+      		   return;
+      	   
+      	   Logger.info("Auditing Item production Failed Items");
+      	   
+      	   String newLine = "\r\n";
+  		   String auditFailedItem = "Failed Item Name | Prefix | Suffix | NPC | Contract  | Player | ";
+  		   
+      	   for (ProducedItem failedItem: ItemProductionManager.FailedItems){
+      		   
+      		   String npcName = "";
+      		   String playerName ="";
+      		   String contractName = "";
+      		   
+      		   String prefix = "";
+      		   String suffix = "";
+      		   String itemName = "";
+      		   NPC npc = NPC.getFromCache(failedItem.getNpcUID());
+      		   
+      		   if (npc == null){
+      			   npcName = "null";
+      			   contractName = "null";
+      		   }else{
+      			   npcName = npc.getName();
+      			   if (npc.getContract() != null)
+      				   contractName = npc.getContract().getName();
+      		   }
+      		   
+      		   PlayerCharacter roller = PlayerCharacter.getFromCache(failedItem.getPlayerID());
+      		   
+      		   if (roller == null)
+      			   playerName = "null";
+      		   else
+      			   playerName = roller.getName();
+      		   
+      		   ItemBase ib = ItemBase.getItemBase(failedItem.getItemBaseID());
+      		   
+      		   if (ib != null){
+      			   itemName = ib.getName();
+      		   }
+      		   
+      		   if (failedItem.isRandom() == false){
+      			   if (failedItem.getPrefix().isEmpty() == false){
+       				  AbstractPowerAction pa = PowersManager.getPowerActionByIDString(failedItem.getPrefix());
+       				  if (pa != null){
+       					  for (AbstractEffectModifier aem : pa.getEffectsBase().getModifiers()){
+       						  if (aem.modType.equals(ModType.ItemName)){
+       							  prefix = aem.getString1();
+       							  break;
+       						  }
+       					  }
+       				  }
+       				  
+       			   }
+      			   
+      			   if (failedItem.getSuffix().isEmpty() == false){
+       				  AbstractPowerAction pa = PowersManager.getPowerActionByIDString(failedItem.getSuffix());
+       				  if (pa != null){
+       					  for (AbstractEffectModifier aem : pa.getEffectsBase().getModifiers()){
+       						  if (aem.modType.equals(ModType.ItemName)){
+       							  suffix = aem.getString1();
+       							  break;
+       						  }
+       					  }
+       				  }
+       				  
+       			   }
+      			   
+      		   }else{
+      			   prefix = "random";
+      		   }
+      		   
+      			   
+      		   
+      		   
+      		   auditFailedItem += newLine;
+      		   auditFailedItem += itemName + " | "+prefix + " | "+suffix + " | "+ failedItem.getNpcUID() + ":" +npcName + " | "+contractName + " | "+failedItem.getPlayerID() + ":" +playerName;
+
+      	   }
+      	   Logger.info(auditFailedItem);
+      	 ItemProductionManager.FailedItems.clear();
+         
+
+	}
+
+
+	@Override
+	protected String _getUsageString() {
+		return "' /bounds'";
+	}
+
+	@Override
+	protected String _getHelpString() {
+		return "Audits all the mobs in a zone.";
+
+	}
+
+}
diff --git a/src/engine/devcmd/cmds/AuditHeightMapCmd.java b/src/engine/devcmd/cmds/AuditHeightMapCmd.java
new file mode 100644
index 00000000..74e9005a
--- /dev/null
+++ b/src/engine/devcmd/cmds/AuditHeightMapCmd.java
@@ -0,0 +1,71 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.devcmd.cmds;
+
+import engine.InterestManagement.HeightMap;
+import engine.devcmd.AbstractDevCmd;
+import engine.gameManager.ZoneManager;
+import engine.math.Vector2f;
+import engine.math.Vector3fImmutable;
+import engine.objects.AbstractGameObject;
+import engine.objects.PlayerCharacter;
+import engine.objects.Zone;
+
+public class AuditHeightMapCmd extends AbstractDevCmd {
+
+	public AuditHeightMapCmd() {
+        super("auditheightmap");
+    }
+
+	@Override
+	protected void _doCmd(PlayerCharacter pcSender, String[] words,
+			AbstractGameObject target) {
+
+		int count = Integer.parseInt(words[0]);
+		long start = System.currentTimeMillis();
+		for (int i = 0; i<count;i++){
+
+
+			Zone currentZone = ZoneManager.findSmallestZone(pcSender.getLoc());
+
+
+
+			Vector3fImmutable currentLoc = Vector3fImmutable.getRandomPointInCircle(currentZone.getLoc(), currentZone.getBounds().getHalfExtents().x < currentZone.getBounds().getHalfExtents().y ? currentZone.getBounds().getHalfExtents().x : currentZone.getBounds().getHalfExtents().y );
+
+			Vector2f zoneLoc = ZoneManager.worldToZoneSpace(currentLoc, currentZone);
+
+			if (currentZone != null && currentZone.getHeightMap() != null){
+				float altitude = currentZone.getHeightMap().getInterpolatedTerrainHeight(zoneLoc);
+				float outsetAltitude = HeightMap.getOutsetHeight(altitude, currentZone, pcSender.getLoc());
+			}
+
+		}
+		long end = System.currentTimeMillis();
+
+		long delta = end - start;
+
+		this.throwbackInfo(pcSender, "Audit Heightmap took " + delta + " ms to run " + count + " times!");
+
+
+	}
+
+
+	@Override
+	protected String _getUsageString() {
+		return "' /auditmobs [zone.UUID]'";
+	}
+
+	@Override
+	protected String _getHelpString() {
+		return "Audits all the mobs in a zone.";
+
+	}
+
+}
diff --git a/src/engine/devcmd/cmds/AuditMobsCmd.java b/src/engine/devcmd/cmds/AuditMobsCmd.java
new file mode 100644
index 00000000..7f2ce04d
--- /dev/null
+++ b/src/engine/devcmd/cmds/AuditMobsCmd.java
@@ -0,0 +1,106 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.devcmd.cmds;
+
+import engine.devcmd.AbstractDevCmd;
+import engine.gameManager.ZoneManager;
+import engine.objects.AbstractGameObject;
+import engine.objects.PlayerCharacter;
+import engine.objects.Zone;
+
+public class AuditMobsCmd extends AbstractDevCmd {
+
+	public AuditMobsCmd() {
+        super("auditmobs");
+    }
+
+	@Override
+	protected void _doCmd(PlayerCharacter pcSender, String[] words,
+			AbstractGameObject target) {
+		if (pcSender == null) return;
+
+		//get Zone to check mobs against
+
+		Zone zone;
+
+		if (words.length == 2){
+			if (words[0].equals("all")){
+				int plusplus = 0;
+				int count = Integer.parseInt(words[1]);
+				for (Zone zoneMicro: ZoneManager.getAllZones()){
+					int size = zoneMicro.zoneMobSet.size();
+
+					if (size >= count){
+						plusplus++;
+						throwbackInfo(pcSender, zoneMicro.getName() + " at location  " + zoneMicro.getLoc().toString()  + " has " + size + " mobs. ");
+						System.out.println(zoneMicro.getName() + " at location  " + zoneMicro.getLoc().toString()  + " has " + size + " mobs. ");
+					}
+
+
+				}
+				throwbackInfo(pcSender," there are " +plusplus + " zones with at least " + count + " mobs in each.");
+			}
+			return;
+		}
+		if (words.length > 1) {
+			this.sendUsage(pcSender);
+			return;
+		} else if (words.length == 1) {
+			int uuid;
+			try {
+				uuid = Integer.parseInt(words[0]);
+				zone = ZoneManager.getZoneByUUID(uuid);
+			} catch (NumberFormatException e) {
+				zone = ZoneManager.findSmallestZone(pcSender.getLoc());
+			}
+		} else
+			zone = ZoneManager.findSmallestZone(pcSender.getLoc());
+
+		if (zone == null) {
+			throwbackError(pcSender, "Unable to find the zone");
+			return;
+		}
+
+		//get list of mobs for zone
+
+		if (zone.zoneMobSet.isEmpty()) {
+			throwbackError(pcSender, "No mobs found for this zone.");
+			return;
+		}
+
+		//	ConcurrentHashMap<Integer, Mob> spawnMap = Mob.getSpawnMap();
+		//ConcurrentHashMap<Mob, Long> respawnMap = Mob.getRespawnMap();
+		//		ConcurrentHashMap<Mob, Long> despawnMap = Mob.getDespawnMap();
+
+		throwbackInfo(pcSender, zone.getName() + ", numMobs: " + zone.zoneMobSet.size());
+		throwbackInfo(pcSender, "UUID, dbID, inRespawnMap, isAlive, activeAI, Loc");
+
+
+
+		//mob not found in spawn map, check respawn
+		boolean inRespawn = false;
+
+
+
+	}
+
+
+	@Override
+	protected String _getUsageString() {
+		return "' /auditmobs [zone.UUID]'";
+	}
+
+	@Override
+	protected String _getHelpString() {
+		return "Audits all the mobs in a zone.";
+
+	}
+
+}
diff --git a/src/engine/devcmd/cmds/BoundsCmd.java b/src/engine/devcmd/cmds/BoundsCmd.java
new file mode 100644
index 00000000..dc5e6835
--- /dev/null
+++ b/src/engine/devcmd/cmds/BoundsCmd.java
@@ -0,0 +1,77 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.devcmd.cmds;
+
+import engine.Enum.GameObjectType;
+import engine.devcmd.AbstractDevCmd;
+import engine.objects.AbstractGameObject;
+import engine.objects.Building;
+import engine.objects.PlayerCharacter;
+
+public class BoundsCmd extends AbstractDevCmd {
+
+	public BoundsCmd() {
+        super("bounds");
+    }
+
+	@Override
+	protected void _doCmd(PlayerCharacter pcSender, String[] words,
+			AbstractGameObject target) {
+		if (target == null || !target.getObjectType().equals(GameObjectType.Building)){
+			this.throwbackError(pcSender, "No Building Selected");
+			return;
+		}
+		
+		Building building = (Building)target;
+		
+		if (building.getBounds() == null){
+			this.throwbackInfo(pcSender, "No valid Bounds for building UUID " + building.getObjectUUID());
+			return;
+		}
+		float topLeftX = building.getLoc().x - building.getBounds().getHalfExtents().x;
+		float topLeftY = building.getLoc().z - building.getBounds().getHalfExtents().y;
+		
+		float topRightX = building.getLoc().x + building.getBounds().getHalfExtents().x;
+		float topRightY = building.getLoc().z - building.getBounds().getHalfExtents().y;
+		
+		float bottomLeftX = building.getLoc().x - building.getBounds().getHalfExtents().x;
+		float bottomLeftY = building.getLoc().z + building.getBounds().getHalfExtents().y;
+		
+		float bottomRightX = building.getLoc().x + building.getBounds().getHalfExtents().x;
+		float bottomRightY = building.getLoc().z + building.getBounds().getHalfExtents().y;
+		
+		String newLine = "\r\n ";
+		
+		String output = "Bounds Information for Building UUID " + building.getObjectUUID();
+		output += newLine;
+		
+		output+= "Top Left : " + topLeftX + " , " + topLeftY + newLine;
+		output+= "Top Right : " + topRightX + " , " + topRightY + newLine;
+		output+= "Bottom Left : " + bottomLeftX + " , " + bottomLeftY + newLine;
+		output+= "Bottom Right : " + bottomRightX + " , " + bottomRightY + newLine;
+		
+		this.throwbackInfo(pcSender, output);
+		
+
+	}
+
+
+	@Override
+	protected String _getUsageString() {
+		return "' /bounds'";
+	}
+
+	@Override
+	protected String _getHelpString() {
+		return "Audits all the mobs in a zone.";
+
+	}
+
+}
diff --git a/src/engine/devcmd/cmds/ChangeNameCmd.java b/src/engine/devcmd/cmds/ChangeNameCmd.java
new file mode 100644
index 00000000..3ef3e095
--- /dev/null
+++ b/src/engine/devcmd/cmds/ChangeNameCmd.java
@@ -0,0 +1,114 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.devcmd.cmds;
+
+import engine.devcmd.AbstractDevCmd;
+import engine.gameManager.DbManager;
+import engine.gameManager.SessionManager;
+import engine.math.Vector3fImmutable;
+import engine.objects.AbstractGameObject;
+import engine.objects.PlayerCharacter;
+import engine.util.MiscUtils;
+
+public class ChangeNameCmd extends AbstractDevCmd {
+
+	public ChangeNameCmd() {
+		super("changename");
+	}
+
+	@Override
+	protected void _doCmd(PlayerCharacter pc, String[] words,
+			AbstractGameObject target) {
+		Vector3fImmutable loc = null;
+
+		// Arg Count Check
+		if (words.length < 2) {
+			this.sendUsage(pc);
+			return;
+		}
+
+		String oldFirst = words[0];
+		String newFirst = words[1];
+		String newLast = "";
+		if (words.length > 2) {
+			newLast = words[2];
+			for (int i=3; i<words.length; i++)
+				newLast += ' ' + words[i];
+		}
+
+		//verify new name length
+		if (newFirst.length() < 3) {
+			this.throwbackError(pc, "Error: First name is incorrect length. Must be between 3 and 15 characters");
+			return;
+		}
+
+		//verify old name length
+		if (newLast.length() > 50) {
+			this.throwbackError(pc, "Error: Last name is incorrect length. Must be no more than 50 characters");
+			return;
+		}
+
+		// Check if firstname is valid
+		if (MiscUtils.checkIfFirstNameInvalid(newFirst)) {
+			this.throwbackError(pc, "Error: First name is not allowed");
+			return;
+		}
+
+
+		//get the world ID we're modifying for
+
+		//test if first name is unique, unless new and old first name are equal.
+		if (!(oldFirst.equals(newFirst))) {
+			if (!DbManager.PlayerCharacterQueries.IS_CHARACTER_NAME_UNIQUE(newFirst)) {
+				this.throwbackError(pc, "Error: First name is not unique.");
+				return;
+			}
+		}
+
+		//tests passed, update name in database
+		if (!DbManager.PlayerCharacterQueries.UPDATE_NAME(oldFirst, newFirst, newLast)) {
+			this.throwbackError(pc, "Error: Database failed to update the name.");
+			return;
+		}
+
+		//Finally update player ingame
+		PlayerCharacter pcTar = null;
+		try {
+			pcTar = SessionManager
+					.getPlayerCharacterByLowerCaseName(words[0]);
+			pcTar.setFirstName(newFirst);
+			pcTar.setLastName(newLast);
+			this.setTarget(pcTar); //for logging
+
+			//specify if last name is ascii characters only
+			String lastAscii =  newLast.replaceAll("[^\\p{ASCII}]", "");
+			pcTar.setAsciiLastName(lastAscii.equals(newLast));
+		} catch (Exception e) {
+			this.throwbackError(pc, "Database was updated but ingame character failed to update.");
+			return;
+		}
+
+		String out = oldFirst + " was changed to " + newFirst + (newLast.isEmpty() ? "." : (' ' + newLast + '.'));
+		this.throwbackInfo(pc, out);
+
+
+	}
+
+	@Override
+	protected String _getHelpString() {
+		return "Changes the name of a player";
+	}
+
+	@Override
+	protected String _getUsageString() {
+		return "'./changename oldFirstName newFirstName newLastName'";
+	}
+
+}
diff --git a/src/engine/devcmd/cmds/CombatMessageCmd.java b/src/engine/devcmd/cmds/CombatMessageCmd.java
new file mode 100644
index 00000000..f1847562
--- /dev/null
+++ b/src/engine/devcmd/cmds/CombatMessageCmd.java
@@ -0,0 +1,70 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+
+
+
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.devcmd.cmds;
+
+import engine.devcmd.AbstractDevCmd;
+import engine.net.client.msg.TargetedActionMsg;
+import engine.objects.AbstractGameObject;
+import engine.objects.PlayerCharacter;
+
+/**
+ * 
+ * @author Eighty
+ *
+ */
+public class CombatMessageCmd extends AbstractDevCmd {
+
+	public CombatMessageCmd() {
+        super("cm");
+    }
+
+	@Override
+	protected void _doCmd(PlayerCharacter pcSender, String[] args,
+			AbstractGameObject target) {
+		if (pcSender == null)
+			return;
+		if (args.length != 1) {
+			this.sendUsage(pcSender);
+			return;
+		}
+		int num = 0;
+		try {
+			num = Integer.parseInt(args[0]);
+		} catch (NumberFormatException e) {
+			throwbackError(pcSender, "Supplied message number " + args[0] + " failed to parse to an Integer");
+			return;
+		}
+		TargetedActionMsg.un2cnt = num;
+		throwbackInfo(pcSender, "CombatMessage set to " + num);
+	}
+
+	@Override
+	protected String _getUsageString() {
+        return "' /cm [cmNumber]'";
+	}
+
+	@Override
+	protected String _getHelpString() {
+        return "Sets the combat message to the supplied integer value";
+	}
+
+}
diff --git a/src/engine/devcmd/cmds/CopyMobCmd.java b/src/engine/devcmd/cmds/CopyMobCmd.java
new file mode 100644
index 00000000..fa68b709
--- /dev/null
+++ b/src/engine/devcmd/cmds/CopyMobCmd.java
@@ -0,0 +1,84 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.devcmd.cmds;
+
+import engine.devcmd.AbstractDevCmd;
+import engine.gameManager.ChatManager;
+import engine.objects.AbstractGameObject;
+import engine.objects.MobBase;
+import engine.objects.PlayerCharacter;
+
+/**
+ * @author Eighty
+ *
+ */
+public class CopyMobCmd extends AbstractDevCmd {
+
+	public CopyMobCmd() {
+        super("copymob");
+    }
+
+	@Override
+	protected void _doCmd(PlayerCharacter pc, String[] words,
+			AbstractGameObject target) {
+		if (words.length < 1) {
+			this.sendUsage(pc);
+			return;
+		}
+
+		int loadID = 0;
+		String name = "";
+		try {
+			loadID = Integer.parseInt(words[0]);
+			if (words.length > 1) {
+				name = words[1];
+				for (int i=2; i<words.length;i++)
+					name += ' ' + words[i];
+			}
+		} catch (NumberFormatException e) {
+			throwbackError(pc, "Supplied type " + words[0]
+					+ " failed to parse to an Integer");
+			return;
+		} catch (Exception e) {
+			throwbackError(pc,
+					"Invalid copyMob Command. Need mob ID specified.");
+			return; // NaN
+		}
+		MobBase mob = MobBase.getMobBase(loadID);
+		if (mob == null) {
+			throwbackError(pc,
+					"Invalid copyMob Command. Mob ID specified is not valid.");
+			return;
+		}
+		MobBase mb = null;
+		try {
+			mb = MobBase.copyMobBase(mob, name);
+		} catch (Exception e) {}
+		if (mb == null) {
+			throwbackError(pc, "copyMob SQL Error. Failed to create new mob.");
+			return;
+		}
+		ChatManager.chatSayInfo(
+				pc,
+				"MobBase created with ID " + mb.getObjectUUID() + " using name "
+						+ mb.getFirstName());
+	}
+
+	@Override
+	protected String _getHelpString() {
+        return "Copies a Mob of type 'mobID' with optional new name";
+	}
+
+	@Override
+	protected String _getUsageString() {
+        return "' /mob mobID [name]'";
+	}
+
+}
diff --git a/src/engine/devcmd/cmds/CreateItemCmd.java b/src/engine/devcmd/cmds/CreateItemCmd.java
new file mode 100644
index 00000000..2067a4dd
--- /dev/null
+++ b/src/engine/devcmd/cmds/CreateItemCmd.java
@@ -0,0 +1,65 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.devcmd.cmds;
+
+import engine.devcmd.AbstractDevCmd;
+import engine.objects.AbstractGameObject;
+import engine.objects.ItemBase;
+import engine.objects.ItemFactory;
+import engine.objects.PlayerCharacter;
+
+/**
+ * @author Eighty
+ *
+ */
+public class CreateItemCmd extends AbstractDevCmd {
+
+	public CreateItemCmd() {
+        super("createitem");
+    }
+
+	@Override
+	protected void _doCmd(PlayerCharacter pc, String[] words,
+			AbstractGameObject target) {
+		if (words.length < 2) {
+			this.sendUsage(pc);
+			return;
+		}
+		int id;
+		id = ItemBase.getIDByName(words[0]);
+
+		if (id == 0)
+			id = Integer.parseInt(words[0]);
+		if (id == 7){
+			this.throwbackInfo(pc, "use /addgold to add gold.....");
+			return;
+		}
+
+		int size = 1;
+
+		if(words.length < 3) {
+			size = Integer.parseInt(words[1]);
+		}
+
+		ItemFactory.fillInventory(pc, id, size);
+
+	}
+
+	@Override
+	protected String _getHelpString() {
+        return "Fill your inventory with items";
+	}
+
+	@Override
+	protected String _getUsageString() {
+        return "' /createitem <ItembaseID> <quantity>'";
+	}
+
+}
diff --git a/src/engine/devcmd/cmds/DebugCmd.java b/src/engine/devcmd/cmds/DebugCmd.java
new file mode 100644
index 00000000..3a3f0110
--- /dev/null
+++ b/src/engine/devcmd/cmds/DebugCmd.java
@@ -0,0 +1,123 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.devcmd.cmds;
+
+import engine.devcmd.AbstractDevCmd;
+import engine.jobs.DebugTimerJob;
+import engine.objects.AbstractGameObject;
+import engine.objects.PlayerCharacter;
+
+public class DebugCmd extends AbstractDevCmd {
+
+
+
+	public DebugCmd() {
+		super("debug");
+		//		super("debug", MBServerStatics.ACCESS_GROUP_ALL_TEAM, 0, false, false);
+	}
+
+	@Override
+	protected void _doCmd(PlayerCharacter pc, String[] words,
+						  AbstractGameObject target) {
+		if (words.length < 2) {
+			this.sendUsage(pc);
+			return;
+		}
+
+		if (pc == null)
+			return;
+
+		//pc.setDebug must use bit sizes: 1, 2, 4, 8, 16, 32
+
+		String command = words[0].toLowerCase();
+		boolean toggle = (words[1].toLowerCase().equals("on")) ? true : false;
+
+		switch (command) {
+			case "magictrek":
+				pc.RUN_MAGICTREK = toggle;
+
+				break;
+			case "combat":
+				pc.setDebug(64, toggle);
+
+				break;
+			case "health":
+				toggleDebugTimer(pc, "Debug_Health", 1, 1000, toggle);
+
+				break;
+			case "mana":
+				toggleDebugTimer(pc, "Debug_Mana", 2, 1000, toggle);
+
+				break;
+			case "stamina":
+				toggleDebugTimer(pc, "Debug_Stamina", 3, 500, toggle);
+
+				break;
+			case "spells":
+				pc.setDebug(16, toggle);
+
+				break;
+			case "damageabsorber":
+				pc.setDebug(2, toggle);
+
+				break;
+			case "recast":
+			case "recycle":
+				pc.setDebug(4, toggle);
+
+				break;
+			case "seeinvis":
+				pc.setDebug(8, toggle);
+
+				break;
+			case "movement":
+				pc.setDebug(1, toggle);
+
+				break;
+			case "noaggro":
+				pc.setDebug(32, toggle);
+
+				break;
+			//		case "loot":
+			//			MBServerStatics.debugLoot = toggle;
+			//			break;
+
+			default:
+				String output = "Debug for " + command + " not found.";
+				throwbackError(pc, output);
+				return;
+		}
+
+
+		setTarget(pc); //for logging
+
+		String output = "Debug for " + command + " turned " + ((toggle) ? "on." : "off.");
+		throwbackInfo(pc, output);
+	}
+
+	@Override
+	protected String _getHelpString() {
+		return "Runs debug commands";
+
+	}
+
+	@Override
+	protected String _getUsageString() {
+		return "'./Debug command on/off'";
+	}
+
+	private static void toggleDebugTimer(PlayerCharacter pc, String name, int num, int duration, boolean toggle) {
+		if (toggle) {
+			DebugTimerJob dtj = new DebugTimerJob(pc, name, num, duration);
+			pc.renewTimer(name, dtj, duration);
+		} else
+			pc.cancelTimer(name);
+	}
+}
diff --git a/src/engine/devcmd/cmds/DebugMeleeSyncCmd.java b/src/engine/devcmd/cmds/DebugMeleeSyncCmd.java
new file mode 100644
index 00000000..baa189dc
--- /dev/null
+++ b/src/engine/devcmd/cmds/DebugMeleeSyncCmd.java
@@ -0,0 +1,58 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+
+package engine.devcmd.cmds;
+
+import engine.devcmd.AbstractDevCmd;
+import engine.gameManager.ChatManager;
+import engine.objects.AbstractGameObject;
+import engine.objects.PlayerCharacter;
+
+public class DebugMeleeSyncCmd extends AbstractDevCmd {
+
+	public DebugMeleeSyncCmd() {
+        super("debugmeleesync");
+    }
+
+	@Override
+	protected void _doCmd(PlayerCharacter pc, String[] words,
+			AbstractGameObject target) {
+		// Arg Count Check
+		if (words.length != 1) {
+			this.sendUsage(pc);
+			return;
+		}
+
+		String s = words[0].toLowerCase();
+
+		if (s.equals("on")) {
+			pc.setDebug(64, true);
+			ChatManager.chatSayInfo(pc, "Melee Sync Debug ON");
+		} else if (s.equals("off")) {
+			pc.setDebug(64, false);
+			ChatManager.chatSayInfo(pc, "Melee Sync Debug OFF");
+		} else {
+			this.sendUsage(pc);
+		}
+	}
+
+	@Override
+	protected String _getHelpString() {
+		return  "Turns on/off melee sync debugging.";
+
+	}
+
+	@Override
+	protected String _getUsageString() {
+		return "'./debugmeleesync on|off'";
+
+	}
+
+}
diff --git a/src/engine/devcmd/cmds/DecachePlayerCmd.java b/src/engine/devcmd/cmds/DecachePlayerCmd.java
new file mode 100644
index 00000000..ef9caf4f
--- /dev/null
+++ b/src/engine/devcmd/cmds/DecachePlayerCmd.java
@@ -0,0 +1,54 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.devcmd.cmds;
+
+import engine.Enum;
+import engine.devcmd.AbstractDevCmd;
+import engine.gameManager.DbManager;
+import engine.objects.AbstractGameObject;
+import engine.objects.PlayerCharacter;
+
+/**
+ *
+ */
+public class DecachePlayerCmd extends AbstractDevCmd {
+
+	public DecachePlayerCmd() {
+        super("decacheplayer");
+    }
+
+	@Override
+	protected void _doCmd(PlayerCharacter pc, String[] words,
+			AbstractGameObject target) {
+		if(words.length < 1) {
+			this.sendUsage(pc);
+		}
+
+		int objectUUID = Integer.parseInt(words[0]);
+
+		if(DbManager.inCache(Enum.GameObjectType.PlayerCharacter, objectUUID)) {
+			this.setTarget(PlayerCharacter.getFromCache(objectUUID)); //for logging
+			PlayerCharacter.getFromCache(objectUUID).removeFromCache();
+		} else {
+			this.sendHelp(pc);
+		}
+	}
+
+	@Override
+	protected String _getHelpString() {
+        return "No player found. Please make sure the table ID is correct.";
+	}
+
+	@Override
+	protected String _getUsageString() {
+        return "' /decacheplayer <UUID>'";
+	}
+
+}
diff --git a/src/engine/devcmd/cmds/DespawnCmd.java b/src/engine/devcmd/cmds/DespawnCmd.java
new file mode 100644
index 00000000..18d54437
--- /dev/null
+++ b/src/engine/devcmd/cmds/DespawnCmd.java
@@ -0,0 +1,73 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+
+package engine.devcmd.cmds;
+
+import engine.Enum.GameObjectType;
+import engine.devcmd.AbstractDevCmd;
+import engine.objects.AbstractGameObject;
+import engine.objects.Mob;
+import engine.objects.PlayerCharacter;
+
+public class DespawnCmd extends AbstractDevCmd {
+
+	public DespawnCmd() {
+        super("debugmob");
+    }
+
+	@Override
+	protected void _doCmd(PlayerCharacter pc, String[] words,
+			AbstractGameObject target) {
+		
+		
+		
+		
+
+		if (pc == null) {
+			return;
+		}
+
+		
+		
+		
+		
+		Mob mob = null;
+		
+		if (target != null && target.getObjectType().equals(GameObjectType.Mob))
+			mob = (Mob)target;
+		
+		if (mob == null)
+			mob = Mob.getFromCache(Integer.parseInt(words[1]));
+		
+		if (mob == null)
+			return;
+		
+		if (words[0].equalsIgnoreCase("respawn")){
+			mob.respawn();
+			this.throwbackInfo(pc, "Mob with ID " + mob.getObjectUUID() + " Respawned"); 
+		}else if (words[0].equalsIgnoreCase("despawn")){
+			mob.despawn();
+			this.throwbackInfo(pc, "Mob with ID " + mob.getObjectUUID() + " Despawned");
+		}
+	}
+
+	@Override
+	protected String _getHelpString() {
+		return "Gets distance from a target.";
+
+	}
+
+	@Override
+	protected String _getUsageString() {
+		return  "' /distance'";
+
+	}
+
+}
diff --git a/src/engine/devcmd/cmds/DistanceCmd.java b/src/engine/devcmd/cmds/DistanceCmd.java
new file mode 100644
index 00000000..458531c2
--- /dev/null
+++ b/src/engine/devcmd/cmds/DistanceCmd.java
@@ -0,0 +1,68 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+
+package engine.devcmd.cmds;
+
+import engine.devcmd.AbstractDevCmd;
+import engine.math.Vector3fImmutable;
+import engine.objects.AbstractGameObject;
+import engine.objects.AbstractWorldObject;
+import engine.objects.PlayerCharacter;
+
+public class DistanceCmd extends AbstractDevCmd {
+
+	public DistanceCmd() {
+        super("distance");
+    }
+
+	@Override
+	protected void _doCmd(PlayerCharacter pc, String[] words,
+			AbstractGameObject target) {
+		
+		
+		
+		
+		// Arg Count Check
+		if (words.length != 1) {
+			this.sendUsage(pc);
+			return;
+		}
+		if (pc == null) {
+			return;
+		}
+
+		if (target == null || !(target instanceof AbstractWorldObject)) {
+			throwbackError(pc, "No target found.");
+			return;
+		}
+		
+		AbstractWorldObject awoTarget = (AbstractWorldObject)target;
+
+		Vector3fImmutable pcLoc = pc.getLoc();
+		Vector3fImmutable tarLoc = awoTarget.getLoc();
+		String out = "Distance: " + pcLoc.distance(tarLoc) +
+				"\r\nYour Loc: " + pcLoc.x + 'x' + pcLoc.y + 'x' + pcLoc.z +
+				"\r\nTarget Loc: " + tarLoc.x + 'x' + tarLoc.y + 'x' + tarLoc.z;
+		throwbackInfo(pc, out);
+	}
+
+	@Override
+	protected String _getHelpString() {
+		return "Gets distance from a target.";
+
+	}
+
+	@Override
+	protected String _getUsageString() {
+		return  "' /distance'";
+
+	}
+
+}
diff --git a/src/engine/devcmd/cmds/EffectCmd.java b/src/engine/devcmd/cmds/EffectCmd.java
new file mode 100644
index 00000000..74733945
--- /dev/null
+++ b/src/engine/devcmd/cmds/EffectCmd.java
@@ -0,0 +1,63 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.devcmd.cmds;
+
+import engine.devcmd.AbstractDevCmd;
+import engine.gameManager.ChatManager;
+import engine.gameManager.PowersManager;
+import engine.objects.AbstractGameObject;
+import engine.objects.PlayerCharacter;
+import engine.powers.EffectsBase;
+
+/**
+ * 
+ * @author Eighty
+ * 
+ */
+public class EffectCmd extends AbstractDevCmd {
+
+	public EffectCmd() {
+        super("effect");
+    }
+
+	@Override
+	protected void _doCmd(PlayerCharacter pcSender, String[] args,
+			AbstractGameObject target) {
+		int ID = 0;
+		int token = 0;
+
+		if (args.length != 2) {
+			this.sendUsage(pcSender);
+			return;
+		}
+		ID = Integer.parseInt(args[0]);
+		token = Integer.parseInt(args[1]);
+
+		EffectsBase eb = PowersManager.setEffectToken(ID, token);
+		if (eb == null) {
+			throwbackError(pcSender, "Unable to find EffectsBase " + ID
+					+ " to modify.");
+			return;
+		}
+		ChatManager.chatSayInfo(pcSender,
+				"EffectsBase with ID " + ID + " changed token to " + token);
+	}
+
+	@Override
+	protected String _getUsageString() {
+        return "' /effect EffectsBaseID Token'";
+	}
+
+	@Override
+	protected String _getHelpString() {
+        return "Temporarily places the effect token with the corresponding EffectsBase on the server";
+	}
+
+}
diff --git a/src/engine/devcmd/cmds/EnchantCmd.java b/src/engine/devcmd/cmds/EnchantCmd.java
new file mode 100644
index 00000000..b6f2247c
--- /dev/null
+++ b/src/engine/devcmd/cmds/EnchantCmd.java
@@ -0,0 +1,88 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.devcmd.cmds;
+
+import engine.devcmd.AbstractDevCmd;
+import engine.objects.*;
+
+public class EnchantCmd extends AbstractDevCmd {
+
+	public EnchantCmd() {
+        super("enchant");
+    }
+
+	@Override
+	protected void _doCmd(PlayerCharacter pc, String[] words,
+			AbstractGameObject target) {
+		int rank = 0;
+		if (words.length < 1) {
+			this.sendUsage(pc);
+			return;
+		}
+
+
+		try{
+			rank = Integer.parseInt(words[0]);
+		}catch(Exception e){
+
+		}
+
+
+
+		Item item;
+		if (target == null || target instanceof Item)
+			item = (Item) target;
+		else {
+			throwbackError(pc, "Must have an item targeted");
+			return;
+		}
+
+		CharacterItemManager cim = pc.getCharItemManager();
+		if (cim == null) {
+			throwbackError(pc, "Unable to find the character item manager for player " + pc.getFirstName() + '.');
+			return;
+		}
+
+		if (words[0].equals("clear")) {
+			item.clearEnchantments();
+			cim.updateInventory();
+			this.setResult(String.valueOf(item.getObjectUUID()));
+		} else {
+			int cnt = words.length;
+			for (int i=1;i<cnt;i++) {
+				String enchant = words[i];
+				boolean valid = true;
+				for (Effect eff: item.getEffects().values()){
+					if (eff.getEffectsBase().getIDString().equals(enchant)){
+						throwbackError(pc,"This item already has that enchantment");
+						return;
+					}
+				}
+				if (valid) {
+					item.addPermanentEnchantmentForDev(enchant, rank);
+					this.setResult(String.valueOf(item.getObjectUUID()));
+				} else
+					throwbackError(pc, "Invalid Enchantment. Enchantment must consist of SUF-001 to SUF-328 or PRE-001 to PRE-334. Sent " + enchant + '.');
+			}
+			cim.updateInventory();
+		}
+	}
+
+	@Override
+	protected String _getHelpString() {
+		return  "Enchants an item with a prefix and suffix";
+	}
+
+	@Override
+	protected String _getUsageString() {
+		return "' /enchant clear/Enchant1 Enchant2 Enchant3 ...'";
+	}
+
+}
diff --git a/src/engine/devcmd/cmds/FindBuildingsCmd.java b/src/engine/devcmd/cmds/FindBuildingsCmd.java
new file mode 100644
index 00000000..369052a3
--- /dev/null
+++ b/src/engine/devcmd/cmds/FindBuildingsCmd.java
@@ -0,0 +1,111 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.devcmd.cmds;
+
+import engine.InterestManagement.WorldGrid;
+import engine.devcmd.AbstractDevCmd;
+import engine.math.Vector3fImmutable;
+import engine.objects.AbstractGameObject;
+import engine.objects.AbstractWorldObject;
+import engine.objects.Building;
+import engine.objects.PlayerCharacter;
+import engine.server.MBServerStatics;
+
+import java.util.HashSet;
+
+public class FindBuildingsCmd extends AbstractDevCmd {
+
+	public FindBuildingsCmd() {
+        super("findBuildings");
+    }
+
+	@Override
+	protected void _doCmd(PlayerCharacter pc, String[] words,
+			AbstractGameObject target) {
+		try {
+			Vector3fImmutable searchPt = pc.getLoc();
+			float range = 50.0f;
+
+			// Arg Count Check
+			// Valid arg count is 0,1,2,3
+			if (words.length == 0) {
+				// Use Player's loc and default range
+
+			} else if (words.length == 1) {
+				// Use Player's loc and specified range
+				range = Float.valueOf(words[0]);
+
+			} else if (words.length == 2) {
+				// Use specified loc and default range
+				searchPt = new Vector3fImmutable(Float.valueOf(words[0]),
+						searchPt.y,
+						Float.valueOf(words[1]));
+
+			} else if (words.length == 3) {
+				// Use specified loc and specified range
+				searchPt = new Vector3fImmutable(Float.valueOf(words[0]),
+						searchPt.y,
+						Float.valueOf(words[1]));
+				range = Float.valueOf(words[2]);
+
+			} else {
+				this.sendUsage(pc);
+				return;
+			}
+
+			String s = "";
+
+			HashSet<AbstractWorldObject> container = WorldGrid.getObjectsInRangePartial(
+					searchPt, range, MBServerStatics.MASK_BUILDING);
+
+			s += "Found " + container.size();
+			s += " buildings within " + range;
+			s += " units of [" + searchPt.toString() + ']';
+			throwbackInfo(pc, s);
+
+			int index = 0;
+			for (AbstractWorldObject awo : container) {
+				Building b = (Building) awo;
+
+				s = index + ")";
+				s += " ObjectID: " + awo.getObjectUUID() + ']';
+				s += " -> Name: " + b.getSimpleName();
+				if (b.getBlueprint() == null) {
+					s += " No Blueprint";
+				} else {
+					s += " Blueprint UUID: " + b.getBlueprint().getMeshForRank(0);
+				}
+				s += "[" + ((Building) awo).getBlueprintUUID() + ']';
+
+				throwbackInfo(pc, s);
+				++index;
+			}
+
+		} catch (NumberFormatException e) {
+			this.throwbackError(pc, "Supplied data: '" + words
+					+ "' failed to parse to a Float.");
+		} catch (Exception e) {
+			this.throwbackError(pc,
+					"An unknown exception occurred while attempting to findBuildings with data: '"
+							+ words + '\'');
+		}
+	}
+
+	@Override
+	protected String _getHelpString() {
+		return "Sets your character's Mana to 'amount'";
+	}
+
+	@Override
+	protected String _getUsageString() {
+		return "' /findBuildings [lat long] [range]'";
+	}
+
+}
diff --git a/src/engine/devcmd/cmds/FlashMsgCmd.java b/src/engine/devcmd/cmds/FlashMsgCmd.java
new file mode 100644
index 00000000..e500ca74
--- /dev/null
+++ b/src/engine/devcmd/cmds/FlashMsgCmd.java
@@ -0,0 +1,68 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.devcmd.cmds;
+
+import engine.devcmd.AbstractDevCmd;
+import engine.gameManager.ChatManager;
+import engine.objects.AbstractGameObject;
+import engine.objects.PlayerCharacter;
+
+/**
+ * 
+ * @author Eighty
+ * 
+ */
+public class FlashMsgCmd extends AbstractDevCmd {
+
+	public FlashMsgCmd() {
+        super("flash");
+    }
+
+	@Override
+	protected void _doCmd(PlayerCharacter pcSender, String[] args,
+			AbstractGameObject target) {
+		if (args.length != 1 || args[0].isEmpty()) {
+			this.sendUsage(pcSender);
+			return;
+		}
+		ChatManager.chatSystemFlash(args[0]);
+	}
+
+	/**
+	 * This function is called by the DevCmdManager. Override to avoid splitting
+	 * argString into String array, since flash displays full String as message,
+	 * then calls the subclass specific _doCmd method.
+	 *
+	 */
+
+	@Override
+	public void doCmd(PlayerCharacter pcSender, String argString,
+			AbstractGameObject target) {
+		String[] args = new String[1];
+		args[0] = argString;
+
+		if (pcSender == null) {
+			return;
+		}
+
+		this._doCmd(pcSender, args, target);
+	}
+
+	@Override
+	protected String _getUsageString() {
+        return "' /flash message'";
+	}
+
+	@Override
+	protected String _getHelpString() {
+        return "Flashes system message to all players";
+	}
+
+}
diff --git a/src/engine/devcmd/cmds/GateInfoCmd.java b/src/engine/devcmd/cmds/GateInfoCmd.java
new file mode 100644
index 00000000..44bfcf76
--- /dev/null
+++ b/src/engine/devcmd/cmds/GateInfoCmd.java
@@ -0,0 +1,87 @@
+package engine.devcmd.cmds;
+
+import engine.Enum.BuildingGroup;
+import engine.Enum.GameObjectType;
+import engine.Enum.RunegateType;
+import engine.devcmd.AbstractDevCmd;
+import engine.math.Vector3fImmutable;
+import engine.objects.*;
+
+public class GateInfoCmd extends AbstractDevCmd {
+      
+	public GateInfoCmd() {
+        super("gateinfo");
+    }
+
+        // AbstractDevCmd Overridden methods
+        
+	@Override
+	protected void _doCmd(PlayerCharacter player, String[] args,
+			AbstractGameObject target)  {
+       
+          Building targetBuilding;
+          String outString;
+          RunegateType runegateType;
+          Runegate runeGate;
+          Blueprint blueprint;
+          String newline = "\r\n ";
+          targetBuilding = (Building)target;
+         
+            if (targetBuilding.getObjectType() != GameObjectType.Building) {
+                throwbackInfo(player, "GateInfo: target must be a Building");
+                throwbackInfo(player, "Found" + targetBuilding.getObjectType().toString());
+                return;
+            }
+
+            blueprint = Blueprint._meshLookup.get(targetBuilding.getMeshUUID());
+
+             if (blueprint == null ||
+                (blueprint.getBuildingGroup() != BuildingGroup.RUNEGATE)){
+                throwbackInfo(player, "showgate: target must be a Runegate");
+                return;
+            }
+           
+           runegateType = RunegateType.getGateTypeFromUUID(targetBuilding.getObjectUUID());
+           runeGate = Runegate.getRunegates()[runegateType.ordinal()];
+               
+           outString = "RungateType: " + runegateType.name();
+           outString += newline;
+           
+           outString += "Portal State:";
+           outString += newline;
+           
+           for (Portal portal : runeGate.getPortals()) {
+               
+               outString += "Portal: " + portal.getPortalType().name();
+               outString += " Active: " + portal.isActive();
+               outString += " Dest: " + portal.getDestinationGateType().name();
+               outString += newline;
+               outString += " Origin: " + portal.getPortalLocation().x + 'x';
+               outString += " " + portal.getPortalLocation().y + 'y';
+               outString += newline;
+               
+               Vector3fImmutable offset = portal.getPortalLocation().subtract(targetBuilding.getLoc());
+               outString += " Offset: " + offset.x + "x " + offset.z + 'y';
+               outString += newline;
+               outString += newline;
+             
+           }
+           outString += newline;
+           throwbackInfo(player, outString);
+        }
+
+	@Override
+	protected String _getHelpString() {
+        return "Displays a runegate's gate status";
+	}
+
+	@Override
+	protected String _getUsageString() {
+
+
+        return "/gateinfo <target runegate> \n";
+	}
+
+ 
+        
+}
diff --git a/src/engine/devcmd/cmds/GetBankCmd.java b/src/engine/devcmd/cmds/GetBankCmd.java
new file mode 100644
index 00000000..55e1e871
--- /dev/null
+++ b/src/engine/devcmd/cmds/GetBankCmd.java
@@ -0,0 +1,52 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.devcmd.cmds;
+
+import engine.devcmd.AbstractDevCmd;
+import engine.gameManager.SessionManager;
+import engine.net.client.ClientConnection;
+import engine.net.client.msg.VendorDialogMsg;
+import engine.objects.AbstractGameObject;
+import engine.objects.PlayerCharacter;
+
+/**
+ *
+ * @author Eighty
+ *
+ */
+public class GetBankCmd extends AbstractDevCmd {
+
+	public GetBankCmd() {
+        super("getbank");
+    }
+
+	@Override
+    protected void _doCmd(PlayerCharacter pcSender, String[] args,
+			AbstractGameObject target) {
+		if (pcSender == null) return;
+
+		ClientConnection cc = SessionManager.getClientConnection(pcSender);
+		if (cc == null) return;
+
+		VendorDialogMsg.getBank(pcSender, null, cc);
+		this.setTarget(pcSender); //for logging
+	}
+
+	@Override
+	protected String _getUsageString() {
+        return "' /getbank'";
+	}
+
+	@Override
+	protected String _getHelpString() {
+        return "Opens bank window";
+	}
+
+}
diff --git a/src/engine/devcmd/cmds/GetCacheCountCmd.java b/src/engine/devcmd/cmds/GetCacheCountCmd.java
new file mode 100644
index 00000000..29840336
--- /dev/null
+++ b/src/engine/devcmd/cmds/GetCacheCountCmd.java
@@ -0,0 +1,40 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.devcmd.cmds;
+
+import engine.devcmd.AbstractDevCmd;
+import engine.gameManager.DbManager;
+import engine.objects.AbstractGameObject;
+import engine.objects.PlayerCharacter;
+
+public class GetCacheCountCmd extends AbstractDevCmd {
+
+	public GetCacheCountCmd() {
+        super("getcachecount");
+        this.addCmdString("getcachecount");
+    }
+
+	@Override
+	protected void _doCmd(PlayerCharacter pcSender, String[] args,
+			AbstractGameObject target) {
+		DbManager.printCacheCount(pcSender);
+	}
+
+	@Override
+	protected String _getUsageString() {
+        return "' /getcachecount'";
+	}
+
+	@Override
+	protected String _getHelpString() {
+        return "Get a count of the objects in the cache";
+	}
+
+}
diff --git a/src/engine/devcmd/cmds/GetDisciplineLocCmd.java b/src/engine/devcmd/cmds/GetDisciplineLocCmd.java
new file mode 100644
index 00000000..c8d428c1
--- /dev/null
+++ b/src/engine/devcmd/cmds/GetDisciplineLocCmd.java
@@ -0,0 +1,64 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.devcmd.cmds;
+
+import engine.devcmd.AbstractDevCmd;
+import engine.gameManager.ZoneManager;
+import engine.objects.*;
+
+import java.util.ArrayList;
+
+/**
+ * @author
+ *
+ */
+public class GetDisciplineLocCmd extends AbstractDevCmd {
+
+	public GetDisciplineLocCmd() {
+        super("getdiscloc");
+    }
+
+	@Override
+	protected void _doCmd(PlayerCharacter pc, String[] words,
+			AbstractGameObject target) {
+
+		System.out.println("MOB UUID , MOB NAME  , MACRO ZONE NAME   , MOB LOCATION, DROPPED ITEM, DROP CHANCE");
+
+		for (Zone zone: ZoneManager.getAllZones()){
+			for (Mob mob: zone.zoneMobSet){
+
+				if (mob.getLevel() >= 80)
+					continue;
+
+				ArrayList<SpecialLoot> specialLootList = SpecialLoot.LootMap.get(mob.getLootSet());
+
+
+				if (specialLootList != null)
+					for (SpecialLoot specialLoot: specialLootList){
+
+
+						ItemBase itemBase = ItemBase.getItemBase(specialLoot.getItemID());
+						System.out.println(mob.getObjectUUID() + " : " + mob.getName() + " :  " + (mob.getParentZone().isMacroZone() ? mob.getParentZone().getName() : mob.getParentZone().getParent().getName()) + " , "   + mob.getLoc().toString2D() + " , " + itemBase.getName() + " , " + specialLoot.getDropChance() + '%');
+					}
+			}
+		}
+	}
+
+	@Override
+	protected String _getHelpString() {
+		return "Enchants an item with a prefix and suffix";
+	}
+
+	@Override
+	protected String _getUsageString() {
+		return "' /enchant clear/Enchant1 Enchant2 Enchant3 ...'";
+	}
+
+}
diff --git a/src/engine/devcmd/cmds/GetHeightCmd.java b/src/engine/devcmd/cmds/GetHeightCmd.java
new file mode 100644
index 00000000..6f94f457
--- /dev/null
+++ b/src/engine/devcmd/cmds/GetHeightCmd.java
@@ -0,0 +1,233 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+
+package engine.devcmd.cmds;
+
+import engine.InterestManagement.HeightMap;
+import engine.devcmd.AbstractDevCmd;
+import engine.gameManager.ZoneManager;
+import engine.math.Vector2f;
+import engine.math.Vector3fImmutable;
+import engine.objects.AbstractGameObject;
+import engine.objects.PlayerCharacter;
+import engine.objects.Zone;
+
+public class GetHeightCmd extends AbstractDevCmd {
+
+    public GetHeightCmd() {
+        super("getHeight");
+        this.addCmdString("height");
+    }
+
+    @Override
+    protected void _doCmd(PlayerCharacter pc, String[] words,
+                          AbstractGameObject target) {
+
+        boolean end = true;
+
+        float height = HeightMap.getWorldHeight(pc);
+
+        this.throwbackInfo(pc, "Altitude : " + height);
+
+        this.throwbackInfo(pc, "Character Height: " + pc.getCharacterHeight());
+        this.throwbackInfo(pc, "Character Height to start swimming: " + pc.centerHeight);
+
+        Zone zone = ZoneManager.findSmallestZone(pc.getLoc());
+        this.throwbackInfo(pc, "Water Level : " + zone.getSeaLevel());
+        this.throwbackInfo(pc, "Character Water Level Above : " + (pc.getCharacterHeight() + height - zone.getSeaLevel()) );
+
+        if (end)
+            return;
+
+        Vector2f gridSquare;
+        Vector2f gridOffset;
+        Vector2f parentGrid;
+        Vector2f parentLoc = new Vector2f(-1, -1);
+
+        Zone currentZone = ZoneManager.findSmallestZone(pc.getLoc());
+
+        if (currentZone == null)
+            return;
+
+        Zone parentZone = currentZone.getParent();
+
+        HeightMap heightMap = currentZone.getHeightMap();
+
+
+        //find the next parents heightmap if the currentzone heightmap is null.
+        while (heightMap == null){
+
+            if (currentZone == ZoneManager.getSeaFloor()){
+                this.throwbackInfo(pc, "Could not find a heightmap to get height.");
+                break;
+            }
+
+            this.throwbackError(pc, "Heightmap does not exist for " + currentZone.getName());
+            this.throwbackInfo(pc, "Using parent zone instead: ");
+            currentZone = currentZone.getParent();
+            heightMap = currentZone.getHeightMap();
+        }
+
+
+        if ( (heightMap == null) || (currentZone == ZoneManager.getSeaFloor()) ) {
+            this.throwbackInfo(pc, currentZone.getName() + " has no heightmap " );
+            this.throwbackInfo(pc, "Current altitude: " + currentZone.absY );
+            return;
+        }
+
+        Vector2f zoneLoc = ZoneManager.worldToZoneSpace(pc.getLoc(), currentZone);
+
+        Vector3fImmutable seaFloorLocalLoc = ZoneManager.worldToLocal(pc.getLoc(), ZoneManager.getSeaFloor());
+        this.throwbackInfo(pc, "SeaFloor Local : " + seaFloorLocalLoc.x + " , " + seaFloorLocalLoc.y);
+
+
+
+
+        this.throwbackInfo(pc, "Local Zone Location : " + zoneLoc.x + " , " + zoneLoc.y);
+        Vector3fImmutable localLocFromCenter = ZoneManager.worldToLocal(pc.getLoc(), currentZone);
+        Vector3fImmutable parentLocFromCenter = ZoneManager.worldToLocal(pc.getLoc(), currentZone.getParent());
+        this.throwbackInfo(pc, "Local Zone Location from center : " + localLocFromCenter);
+        this.throwbackInfo(pc, "parent Zone Location from center : " + parentLocFromCenter);
+
+        Vector2f parentZoneLoc = ZoneManager.worldToZoneSpace(pc.getLoc(), currentZone.getParent());
+        this.throwbackInfo(pc, "Parent Zone Location from Bottom Left : " + parentZoneLoc);
+
+        if ((parentZone != null ) && (parentZone.getHeightMap() != null)) {
+            parentLoc = ZoneManager.worldToZoneSpace(pc.getLoc(), parentZone);
+            parentGrid = parentZone.getHeightMap().getGridSquare( parentLoc);
+        } else parentGrid = new Vector2f(-1,-1);
+
+        gridSquare = heightMap.getGridSquare(zoneLoc);
+        gridOffset = HeightMap.getGridOffset(gridSquare);
+
+        float interaltitude = currentZone.getHeightMap().getInterpolatedTerrainHeight(zoneLoc);
+
+        this.throwbackInfo(pc, currentZone.getName());
+        this.throwbackInfo(pc, "Current Grid Square: " + gridSquare.x + " , " + gridSquare.y );
+        this.throwbackInfo(pc, "Grid Offset: " + gridOffset.x + " , " + gridOffset.y);
+        this.throwbackInfo(pc, "Parent Grid: " + parentGrid.x + " , " + parentGrid.y);
+
+        if (parentGrid.x != -1) {
+            float parentAltitude = parentZone.getHeightMap().getInterpolatedTerrainHeight(parentLoc);
+            this.throwbackInfo(pc, "Parent ALTITUDE: " + (parentAltitude));
+            this.throwbackInfo(pc, "Parent Interpolation: " + (parentAltitude + parentZone.getWorldAltitude()));
+        }
+        this.throwbackInfo(pc, "interpolated height: " + interaltitude);
+
+        this.throwbackInfo(pc, "interpolated height with World: " + (interaltitude + currentZone.getWorldAltitude()));
+
+        float realWorldAltitude = interaltitude + currentZone.getWorldAltitude();
+
+        //OUTSET
+        if (parentZone != null){
+            float parentXRadius = currentZone.getBounds().getHalfExtents().x;
+            float parentZRadius = currentZone.getBounds().getHalfExtents().y;
+
+            float offsetX = Math.abs((localLocFromCenter.x / parentXRadius));
+            float offsetZ = Math.abs((localLocFromCenter.z / parentZRadius));
+
+            float bucketScaleX = 100/parentXRadius;
+            float bucketScaleZ = 200/parentZRadius;
+
+            float outsideGridSizeX = 1 - bucketScaleX; //32/256
+            float outsideGridSizeZ = 1 - bucketScaleZ;
+            float weight;
+
+            double scale;
+
+
+            if (offsetX > outsideGridSizeX && offsetX > offsetZ){
+                weight = (offsetX - outsideGridSizeX) / bucketScaleX;
+                scale = Math.atan2((.5 - weight) * 3.1415927, 1);
+
+                float scaleChild = (float) ((scale + 1) * .5);
+                float scaleParent = 1 - scaleChild;
+
+
+                float parentAltitude = parentZone.getHeightMap().getInterpolatedTerrainHeight(parentLoc);
+                float parentCenterAltitude = parentZone.getHeightMap().getInterpolatedTerrainHeight(ZoneManager.worldToZoneSpace(currentZone.getLoc(), parentZone));
+
+                parentCenterAltitude += currentZone.getYCoord();
+                parentCenterAltitude += interaltitude;
+
+                float firstScale = parentAltitude * scaleParent;
+                float secondScale = parentCenterAltitude * scaleChild;
+                float outsetALt = firstScale + secondScale;
+
+                outsetALt += currentZone.getParent().getAbsY();
+                realWorldAltitude = outsetALt;
+
+            }else if (offsetZ > outsideGridSizeZ){
+
+                weight = (offsetZ - outsideGridSizeZ) / bucketScaleZ;
+                scale = Math.atan2((.5 - weight) * 3.1415927, 1);
+
+                float scaleChild = (float) ((scale + 1) * .5);
+                float scaleParent = 1 - scaleChild;
+                float parentAltitude = parentZone.getHeightMap().getInterpolatedTerrainHeight(parentLoc);
+                float parentCenterAltitude = parentZone.getHeightMap().getInterpolatedTerrainHeight(ZoneManager.worldToZoneSpace(currentZone.getLoc(), parentZone));
+
+                parentCenterAltitude += currentZone.getYCoord();
+                parentCenterAltitude += interaltitude;
+                float firstScale = parentAltitude * scaleParent;
+                float secondScale = parentCenterAltitude * scaleChild;
+                float outsetALt = firstScale + secondScale;
+
+                outsetALt += currentZone.getParent().getAbsY();
+                realWorldAltitude = outsetALt;
+
+
+
+
+            }
+        }
+
+        float strMod = pc.statStrBase - 40;
+
+        strMod *= .00999999998f;
+
+        strMod += 1f;
+
+        float radius = 0;
+        switch (pc.getRaceID()){
+            case 2017:
+                radius = 3.1415927f;
+            case 2000:
+
+
+        }
+        strMod *= 1.5707964f;
+
+        strMod += 3.1415927f;
+
+        strMod -= .5f;
+
+
+
+        realWorldAltitude += strMod;
+
+        this.throwbackInfo(pc, "interpolated height with World: " + realWorldAltitude);
+
+
+
+
+    }
+
+    @Override
+    protected String _getHelpString() {
+        return "Temporarily Changes SubRace";
+    }
+
+    @Override
+    protected String _getUsageString() {
+        return "' /subrace mobBaseID";
+    }
+
+}
diff --git a/src/engine/devcmd/cmds/GetMemoryCmd.java b/src/engine/devcmd/cmds/GetMemoryCmd.java
new file mode 100644
index 00000000..d6b1a0a8
--- /dev/null
+++ b/src/engine/devcmd/cmds/GetMemoryCmd.java
@@ -0,0 +1,57 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.devcmd.cmds;
+
+import engine.devcmd.AbstractDevCmd;
+import engine.objects.AbstractGameObject;
+import engine.objects.PlayerCharacter;
+
+public class GetMemoryCmd extends AbstractDevCmd {
+
+	public GetMemoryCmd() {
+        super("getmemory");
+    }
+
+	@Override
+	protected void _doCmd(PlayerCharacter pcSender, String[] words,
+			AbstractGameObject target) {
+		if (pcSender == null) return;
+
+		String hSize = getMemoryOutput(Runtime.getRuntime().totalMemory());
+		String mhSize = getMemoryOutput(Runtime.getRuntime().maxMemory());
+		String fhSize = getMemoryOutput(Runtime.getRuntime().freeMemory());
+
+		String out = "Heap Size: " + hSize + ", Max Heap Size: " + mhSize + ", Free Heap Size: " + fhSize;
+		throwbackInfo(pcSender, out);
+	}
+
+	public static String getMemoryOutput(long memory) {
+		String out = "";
+		if (memory > 1073741824)
+			return (memory / 1073741824) + "GB";
+		else if (memory > 1048576)
+			return (memory / 1048576) + "MB";
+		else if (memory > 1024)
+			return (memory / 1048576) + "KB";
+		else
+			return memory + "B";
+	}
+
+	@Override
+	protected String _getUsageString() {
+		return "' /getmemory'";
+	}
+
+	@Override
+	protected String _getHelpString() {
+		return "lists memory usage";
+	}
+
+}
diff --git a/src/engine/devcmd/cmds/GetMobBaseLoot.java b/src/engine/devcmd/cmds/GetMobBaseLoot.java
new file mode 100644
index 00000000..3ecce72f
--- /dev/null
+++ b/src/engine/devcmd/cmds/GetMobBaseLoot.java
@@ -0,0 +1,59 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.devcmd.cmds;
+
+import engine.Enum.GameObjectType;
+import engine.devcmd.AbstractDevCmd;
+import engine.objects.AbstractGameObject;
+import engine.objects.Mob;
+import engine.objects.MobLootBase;
+import engine.objects.PlayerCharacter;
+
+/**
+ * @author Eighty
+ *
+ */
+public class GetMobBaseLoot extends AbstractDevCmd {
+
+	public GetMobBaseLoot() {
+        super("mobbaseloot");
+    }
+
+	@Override
+	protected void _doCmd(PlayerCharacter pc, String[] words,
+			AbstractGameObject target) {
+		if (target.getObjectType() != GameObjectType.Mob){
+			this.throwbackError(pc, "Must be targeting a Valid Mob For this Command.");
+			return;
+		}
+
+
+		Mob mob = (Mob)target;
+		for (MobLootBase mlb : MobLootBase.MobLootSet.get(mob.getMobBase().getLoadID())){
+
+			this.throwbackInfo(pc, "LootTable11 = " + mlb.getLootTableID() + "\rn ");
+			this.throwbackInfo(pc, "Chance = " + mlb.getChance() + "\rn ");
+
+		}
+
+
+	}
+
+	@Override
+	protected String _getHelpString() {
+        return "Copies a Mob of type 'mobID' with optional new name";
+	}
+
+	@Override
+	protected String _getUsageString() {
+        return "' /mob mobID [name]'";
+	}
+
+}
diff --git a/src/engine/devcmd/cmds/GetOffsetCmd.java b/src/engine/devcmd/cmds/GetOffsetCmd.java
new file mode 100644
index 00000000..515b5d3f
--- /dev/null
+++ b/src/engine/devcmd/cmds/GetOffsetCmd.java
@@ -0,0 +1,63 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.devcmd.cmds;
+
+import engine.devcmd.AbstractDevCmd;
+import engine.gameManager.ZoneManager;
+import engine.objects.AbstractGameObject;
+import engine.objects.PlayerCharacter;
+import engine.objects.Zone;
+
+public class GetOffsetCmd extends AbstractDevCmd {
+
+	public GetOffsetCmd() {
+        super("getoffset");
+    }
+
+	@Override
+	protected void _doCmd(PlayerCharacter pcSender, String[] words,
+			AbstractGameObject target) {
+		if (pcSender == null) return;
+
+		Zone zone = null;
+		try {
+			int loadID = Integer.parseInt(words[0]);
+			zone = ZoneManager.getZoneByZoneID(loadID);
+			if (zone == null) {
+				throwbackError(pcSender, "Error:  can't find the zone of ID " + loadID + '.');
+				return;
+			}
+		} catch (Exception e) {
+			zone = ZoneManager.findSmallestZone(pcSender.getLoc());
+		}
+
+		if (zone == null) {
+			throwbackError(pcSender, "Error:  can't find the zone you're in.");
+			return;
+		}
+
+		float difX = pcSender.getLoc().x - zone.absX;
+		float difY = pcSender.getLoc().y - zone.absY;
+		float difZ = pcSender.getLoc().z - zone.absZ;
+
+		throwbackInfo(pcSender, zone.getName() + ": (x: " + difX + ", y: " + difY + ", z: " + difZ + ')');
+	}
+
+	@Override
+	protected String _getUsageString() {
+		return "'./getoffset [zoneID]'";
+	}
+
+	@Override
+	protected String _getHelpString() {
+		return "lists offset from center of zone";
+	}
+
+}
diff --git a/src/engine/devcmd/cmds/GetRuneDropRateCmd.java b/src/engine/devcmd/cmds/GetRuneDropRateCmd.java
new file mode 100644
index 00000000..7dea4c5c
--- /dev/null
+++ b/src/engine/devcmd/cmds/GetRuneDropRateCmd.java
@@ -0,0 +1,42 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.devcmd.cmds;
+
+import engine.devcmd.AbstractDevCmd;
+import engine.objects.AbstractGameObject;
+import engine.objects.PlayerCharacter;
+
+
+public class GetRuneDropRateCmd extends AbstractDevCmd {
+
+	public GetRuneDropRateCmd() {
+        super("getrunedroprate");
+    }
+
+	@Override
+	protected void _doCmd(PlayerCharacter pcSender, String[] words,
+			AbstractGameObject target) {
+		if (pcSender == null) return;
+
+		String out = "Depracated";
+		throwbackInfo(pcSender, out);
+	}
+
+	@Override
+	protected String _getUsageString() {
+		return "' /getrunedroprate'";
+	}
+
+	@Override
+	protected String _getHelpString() {
+		return "lists drop rates for runes and contracts in non-hotzone.";
+	}
+
+}
diff --git a/src/engine/devcmd/cmds/GetVaultCmd.java b/src/engine/devcmd/cmds/GetVaultCmd.java
new file mode 100644
index 00000000..df50846e
--- /dev/null
+++ b/src/engine/devcmd/cmds/GetVaultCmd.java
@@ -0,0 +1,47 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.devcmd.cmds;
+
+import engine.devcmd.AbstractDevCmd;
+import engine.gameManager.SessionManager;
+import engine.net.client.ClientConnection;
+import engine.net.client.msg.VendorDialogMsg;
+import engine.objects.AbstractGameObject;
+import engine.objects.PlayerCharacter;
+
+public class GetVaultCmd extends AbstractDevCmd {
+
+	public GetVaultCmd() {
+        super("getvault");
+    }
+
+	@Override
+    protected void _doCmd(PlayerCharacter pcSender, String[] args,
+			AbstractGameObject target) {
+		if (pcSender == null) return;
+
+		ClientConnection cc = SessionManager.getClientConnection(pcSender);
+		if (cc == null) return;
+
+		VendorDialogMsg.getVault(pcSender, null, cc);
+		this.setTarget(pcSender); //for logging
+	}
+
+	@Override
+	protected String _getUsageString() {
+        return "' /getvault'";
+	}
+
+	@Override
+	protected String _getHelpString() {
+        return "Opens account vault";
+	}
+
+}
diff --git a/src/engine/devcmd/cmds/GetZoneCmd.java b/src/engine/devcmd/cmds/GetZoneCmd.java
new file mode 100644
index 00000000..bab3ae9d
--- /dev/null
+++ b/src/engine/devcmd/cmds/GetZoneCmd.java
@@ -0,0 +1,66 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.devcmd.cmds;
+
+import engine.devcmd.AbstractDevCmd;
+import engine.gameManager.ZoneManager;
+import engine.objects.AbstractGameObject;
+import engine.objects.PlayerCharacter;
+import engine.objects.Zone;
+
+import java.util.ArrayList;
+
+public class GetZoneCmd extends AbstractDevCmd {
+
+	public GetZoneCmd() {
+        super("getzone");
+    }
+
+	@Override
+	protected void _doCmd(PlayerCharacter pcSender, String[] words,
+			AbstractGameObject target) {
+		if (pcSender == null) return;
+
+		if (words.length != 1) {
+			this.sendUsage(pcSender);
+			return;
+		}
+
+		ArrayList<Zone> allIn = new ArrayList<>();
+		switch (words[0].toLowerCase()) {
+		case "all":
+			throwbackInfo(pcSender, "All zones currently in");
+			allIn = ZoneManager.getAllZonesIn(pcSender.getLoc());
+			break;
+		case "smallest":
+			throwbackInfo(pcSender, "Smallest zone currently in");
+			Zone zone = ZoneManager.findSmallestZone(pcSender.getLoc());
+			allIn.add(zone);
+			break;
+		default:
+			this.sendUsage(pcSender);
+			return;
+		}
+
+		for (Zone zone : allIn)
+			throwbackInfo(pcSender, zone.getName() + "; UUID: " + zone.getObjectUUID() + ", loadNum: " + zone.getLoadNum());
+	}
+
+	@Override
+	protected String _getUsageString() {
+		return "' /getzone smallest/all'";
+	}
+
+	@Override
+	protected String _getHelpString() {
+		return "lists what zones a player is in";
+	}
+
+}
diff --git a/src/engine/devcmd/cmds/GetZoneMobsCmd.java b/src/engine/devcmd/cmds/GetZoneMobsCmd.java
new file mode 100644
index 00000000..f069ef20
--- /dev/null
+++ b/src/engine/devcmd/cmds/GetZoneMobsCmd.java
@@ -0,0 +1,82 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.devcmd.cmds;
+
+import engine.devcmd.AbstractDevCmd;
+import engine.gameManager.ZoneManager;
+import engine.objects.AbstractGameObject;
+import engine.objects.Mob;
+import engine.objects.PlayerCharacter;
+import engine.objects.Zone;
+
+public class GetZoneMobsCmd extends AbstractDevCmd {
+
+	public GetZoneMobsCmd() {
+        super("getzonemobs");
+    }
+
+	@Override
+	protected void _doCmd(PlayerCharacter pcSender, String[] words,
+			AbstractGameObject target) {
+		if (pcSender == null) return;
+
+		int loadID = 0;
+		if (words.length == 1) {
+			try {
+				loadID = Integer.parseInt(words[0]);
+			} catch (Exception e) {}
+		}
+
+		//find the zone
+		Zone zone = null;
+		if (loadID != 0) {
+			zone = ZoneManager.getZoneByZoneID(loadID);
+			if (zone == null)
+				zone = ZoneManager.getZoneByUUID(loadID);
+		} else
+			zone = ZoneManager.findSmallestZone(pcSender.getLoc());
+
+		if (zone == null) {
+			if (loadID != 0)
+				throwbackError(pcSender, "Error:  can't find the zone of ID " + loadID + '.');
+			else
+				throwbackError(pcSender, "Error:  can't find the zone you are in.");
+			return;
+		}
+
+		//get all mobs for the zone
+
+		throwbackInfo(pcSender, zone.getName() + " (" + zone.getLoadNum() + ") " + zone.getObjectUUID());
+
+		for (Mob m : zone.zoneMobSet) {
+
+			if (m != null) {
+				String out = m.getName() + '(' + m.getDBID() + "): ";
+				if (m.isAlive())
+					out += m.getLoc().x + "x" + m.getLoc().z + "; isAlive: " + m.isAlive();
+				else
+					out += " isAlive: " + m.isAlive();
+				throwbackInfo(pcSender, out);
+			} else
+				throwbackInfo(pcSender, "Unknown (" + m.getDBID() + "): not loaded");
+		}
+	}
+
+	@Override
+	protected String _getUsageString() {
+		return "' /getzonemobs [zoneID]'";
+	}
+
+	@Override
+	protected String _getHelpString() {
+		return "lists all mobs for a zone";
+	}
+
+}
diff --git a/src/engine/devcmd/cmds/GotoBoundsCmd.java b/src/engine/devcmd/cmds/GotoBoundsCmd.java
new file mode 100644
index 00000000..ae10766b
--- /dev/null
+++ b/src/engine/devcmd/cmds/GotoBoundsCmd.java
@@ -0,0 +1,108 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+
+package engine.devcmd.cmds;
+
+import engine.Enum.GameObjectType;
+import engine.devcmd.AbstractDevCmd;
+import engine.math.Vector3fImmutable;
+import engine.objects.AbstractGameObject;
+import engine.objects.Building;
+import engine.objects.PlayerCharacter;
+
+public class GotoBoundsCmd extends AbstractDevCmd {
+
+	public GotoBoundsCmd() {
+        super("gotobounds");
+    }
+
+	@Override
+	protected void _doCmd(PlayerCharacter player, String[] words,
+			AbstractGameObject target) {
+
+		String corner = words[0];
+		Vector3fImmutable targetLoc =  Vector3fImmutable.ZERO;
+		
+		if (target == null || !target.getObjectType().equals(GameObjectType.Building)){
+			this.throwbackError(player, "No Building Selected");
+			return;
+		}
+		
+		Building building = (Building)target;
+		
+		if (building.getBounds() == null){
+			this.throwbackInfo(player, "No valid Bounds for building UUID " + building.getObjectUUID());
+			return;
+		}
+		 float x = building.getBounds().getHalfExtents().x;
+		 float z = building.getBounds().getHalfExtents().y;
+		 
+		 if (building.getBlueprint() != null){
+			 x = building.getBlueprint().getExtents().x;
+			 z = building.getBlueprint().getExtents().y;
+		 }
+		 
+		float topLeftX = building.getLoc().x - x;
+		float topLeftY = building.getLoc().z -z;
+		
+		float topRightX = building.getLoc().x + x;
+		float topRightY = building.getLoc().z - z;
+		
+		float bottomLeftX = building.getLoc().x - x;
+		float bottomLeftY = building.getLoc().z + z;
+		
+		float bottomRightX = building.getLoc().x +x;
+		float bottomRightY = building.getLoc().z + z;
+		
+		
+		switch (corner){
+		case "topleft":
+			targetLoc = new Vector3fImmutable(topLeftX, 0, topLeftY);
+			break;
+		case "topright":
+			targetLoc = new Vector3fImmutable(topRightX, 0, topRightY);
+			break;
+		case "bottomleft":
+			targetLoc = new Vector3fImmutable(bottomLeftX, 0, bottomLeftY);
+			break;
+		case "bottomright":
+			targetLoc = new Vector3fImmutable(bottomRightX, 0, bottomRightY);
+			break;
+			default:
+				this.throwbackInfo(player, "wrong corner name. use topleft , topright , bottomleft , bottomright");
+				return;
+				
+		}
+		
+		targetLoc = Vector3fImmutable.transform(building.getLoc(),targetLoc , building.getBounds().getRotationDegrees());
+		
+		// Teleport player
+
+		if (targetLoc == Vector3fImmutable.ZERO) {
+			this.throwbackError(player, "Failed to locate UUID");
+			return;
+		}
+
+		player.teleport(targetLoc);
+
+	}
+
+	@Override
+	protected String _getHelpString() {
+        return "Teleports player to a UUID";
+	}
+
+	@Override
+	protected String _getUsageString() {
+		return "' /gotoobj <UID>'";
+
+	}
+
+}
diff --git a/src/engine/devcmd/cmds/GotoCmd.java b/src/engine/devcmd/cmds/GotoCmd.java
new file mode 100644
index 00000000..3825ae4a
--- /dev/null
+++ b/src/engine/devcmd/cmds/GotoCmd.java
@@ -0,0 +1,182 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+
+package engine.devcmd.cmds;
+
+import engine.Enum.GameObjectType;
+import engine.devcmd.AbstractDevCmd;
+import engine.gameManager.DbManager;
+import engine.gameManager.SessionManager;
+import engine.math.Vector3fImmutable;
+import engine.objects.*;
+import engine.server.MBServerStatics;
+
+import java.util.concurrent.ThreadLocalRandom;
+
+public class GotoCmd extends AbstractDevCmd {
+
+	public GotoCmd() {
+        super("goto");
+    }
+
+	@Override
+	protected void _doCmd(PlayerCharacter pc, String[] words,
+			AbstractGameObject target) {
+		Vector3fImmutable loc = null;
+
+		// Arg Count Check
+
+
+		if (target != null && words[0].isEmpty()){
+			AbstractWorldObject targetAgo = (AbstractWorldObject)target;
+			pc.teleport(targetAgo.getLoc());
+			return;
+		}
+
+		if (words[0].isEmpty()){
+			this.sendUsage(pc);
+			return;
+		}
+
+		if (words[0].equalsIgnoreCase("playground")){
+			if (target instanceof AbstractCharacter){
+				loc = new Vector3fImmutable(63276,0,-54718);
+			}
+
+			if (loc != null)
+				pc.teleport(loc);
+
+			return;
+		}
+		
+		if (words[0].equalsIgnoreCase("coc")){
+			if (target instanceof AbstractCharacter){
+				loc = new Vector3fImmutable(98561.656f,0,-13353.778f);
+			}
+
+			if (loc != null)
+				pc.teleport(loc);
+
+			return;
+		}
+
+		String cityName = "";
+		for (String partial: words){
+			cityName += partial + ' ';
+		}
+
+		cityName = cityName.substring(0, cityName.length() - 1);
+
+		for (AbstractGameObject cityAgo: DbManager.getList(GameObjectType.City)){
+			City city = (City)cityAgo;
+			if (city == null)
+				continue;
+			if (!city.getCityName().equalsIgnoreCase(cityName))
+				continue;
+			Zone zone = city.getParent();
+			if (zone != null){
+				if (zone.isNPCCity() || zone.isPlayerCity())
+					loc = Vector3fImmutable.getRandomPointOnCircle(zone.getLoc(), MBServerStatics.TREE_TELEPORT_RADIUS);
+				else
+					loc = zone.getLoc();
+
+				int random = ThreadLocalRandom.current().nextInt(5);
+				if (random == 1)
+					break;
+			}
+		}
+
+		if (loc == null){
+			for (AbstractGameObject zoneAgo: DbManager.getList(GameObjectType.Zone)){
+				Zone zone = (Zone)zoneAgo;
+				if (zone == null)
+					continue;
+				if (!zone.getName().equalsIgnoreCase(cityName))
+					continue;
+				if (zone != null){
+					if (zone.isNPCCity() || zone.isPlayerCity())
+						loc = Vector3fImmutable.getRandomPointOnCircle(zone.getLoc(), MBServerStatics.TREE_TELEPORT_RADIUS);
+					else
+						loc = zone.getLoc();
+
+					int random = ThreadLocalRandom.current().nextInt(5);
+					if (random == 1)
+						break;
+				}
+			}
+		}
+		if (loc == null && words.length == 1){
+
+			try {
+				PlayerCharacter pcDest = SessionManager
+						.getPlayerCharacterByLowerCaseName(words[0]);
+				if (pcDest == null){
+					this.throwbackError(pc, "Player or Zone not found by name: "
+							+ words[0]);
+					this.throwbackInfo(pc, "If you have spaces in the zone name, replace them with '_'");
+					return;
+				}
+
+				if (pcDest.getCombinedName().equals(pc.getCombinedName())) {
+					this
+					.throwbackError(pc,
+							"Cannot goto yourself.  Well, you can, but you wont go anywhere.");
+					return;
+				}
+
+				loc = pcDest.getLoc();
+			} catch (Exception e) {
+				this.throwbackError(pc,
+						"An unknown exception occurred while attempting to goto a character named '"
+								+ words[0] + '\'');
+				return;
+			}
+
+		}
+		if (loc == null) { // lat lon mode
+			if (words.length != 2) {
+				throwbackError(pc, this.getUsageString());
+				return;
+			}
+			float lat = 0.0f, lon = 0.0f;
+			String latLong = '\'' + words[0] + ", " + words[1] + '\'';
+
+			try {
+				lat = Float.parseFloat(words[0]);
+				lon = Float.parseFloat(words[1]);
+				loc = new Vector3fImmutable(lat, 0f, -lon);
+			} catch (NumberFormatException e) {
+				this.throwbackError(pc, "Supplied LatLong: " + latLong
+						+ " failed to parse to Floats");
+				return;
+
+			} catch (Exception e) {
+				this.throwbackError(pc,
+						"An unknown exception occurred while attempting to goto LatLong of "
+								+ latLong);
+				return;
+			}
+		}
+		if (loc != null) {
+			pc.teleport(loc);
+		}
+	}
+
+	@Override
+	protected String _getHelpString() {
+		return  "Alters your characters position TO 'lat' and 'long', or TO the position of 'characterName'.  This does not transport you BY 'lat' and 'long', but rather TO 'lat' and 'long' ";
+	}
+
+	@Override
+	protected String _getUsageString() {
+		return  "'[ /goto lat lon] || [ /goto characterName] || [/goto zoneName  \replace spaces with `_`]`";
+	}
+
+}
diff --git a/src/engine/devcmd/cmds/GotoObj.java b/src/engine/devcmd/cmds/GotoObj.java
new file mode 100644
index 00000000..2b535564
--- /dev/null
+++ b/src/engine/devcmd/cmds/GotoObj.java
@@ -0,0 +1,103 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+
+package engine.devcmd.cmds;
+
+import engine.Enum;
+import engine.devcmd.AbstractDevCmd;
+import engine.gameManager.DbManager;
+import engine.math.Vector3fImmutable;
+import engine.objects.*;
+
+public class GotoObj extends AbstractDevCmd {
+
+	public GotoObj() {
+        super("gotoobj");
+    }
+
+	@Override
+	protected void _doCmd(PlayerCharacter player, String[] words,
+			AbstractGameObject target) {
+
+		int uuid;
+		Vector3fImmutable targetLoc =  Vector3fImmutable.ZERO;
+		Enum.DbObjectType objectType;
+
+		try {
+			uuid = Integer.parseInt(words[0]);
+		} catch (NumberFormatException e) {
+			this.throwbackError(player, "Failed to parse UUID" + e.toString());
+			return;
+		}
+
+		objectType = DbManager.BuildingQueries.GET_UID_ENUM(uuid);
+
+		switch (objectType) {
+
+			case NPC:
+				NPC npc = (NPC) DbManager.getFromCache(Enum.GameObjectType.NPC, uuid);
+
+				if (npc != null)
+					targetLoc = npc.getLoc();
+					break;
+			case MOB:
+				Mob mob = (Mob) DbManager.getFromCache(Enum.GameObjectType.Mob, uuid);
+
+				if (mob != null)
+					targetLoc = mob.getLoc();
+				break;
+			case CHARACTER:
+				PlayerCharacter playerCharacter = (PlayerCharacter) DbManager.getFromCache(Enum.GameObjectType.PlayerCharacter, uuid);
+
+				if (playerCharacter != null)
+					targetLoc = playerCharacter.getLoc();
+				break;
+			case BUILDING:
+				Building building = (Building) DbManager.getFromCache(Enum.GameObjectType.Building, uuid);
+
+				if (building != null)
+					targetLoc = building.getLoc();
+				break;
+			case ZONE:
+				Zone zone = (Zone) DbManager.getFromCache(Enum.GameObjectType.Zone, uuid);
+
+				if (zone != null)
+					targetLoc = zone.getLoc();
+				break;
+			case CITY:
+				City city = (City) DbManager.getFromCache(Enum.GameObjectType.City, uuid);
+
+				if (city != null)
+					targetLoc = city.getLoc();
+				break;
+		}
+		// Teleport player
+
+		if (targetLoc == Vector3fImmutable.ZERO) {
+			this.throwbackError(player, "Failed to locate UUID");
+			return;
+		}
+
+		player.teleport(targetLoc);
+
+	}
+
+	@Override
+	protected String _getHelpString() {
+        return "Teleports player to a UUID";
+	}
+
+	@Override
+	protected String _getUsageString() {
+		return "' /gotoobj <UID>'";
+
+	}
+
+}
diff --git a/src/engine/devcmd/cmds/GuildListCmd.java b/src/engine/devcmd/cmds/GuildListCmd.java
new file mode 100644
index 00000000..71437576
--- /dev/null
+++ b/src/engine/devcmd/cmds/GuildListCmd.java
@@ -0,0 +1,105 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.devcmd.cmds;
+
+import engine.devcmd.AbstractDevCmd;
+import engine.gameManager.DbManager;
+import engine.objects.AbstractGameObject;
+import engine.objects.PlayerCharacter;
+
+
+/**
+ * @author 
+ * Summary: Lists UID, Name and GL UID of either
+ *          Player or NPC sovereign guilds
+ */
+ 
+public class GuildListCmd extends AbstractDevCmd {
+
+    // Instance variables
+    
+    private int _guildType; // 0 = Player : 1 = NPC sovereign guilds
+    private String outputStr = null;
+    
+	public GuildListCmd() {
+        super("guildlist");
+    }
+
+        
+        // AbstractDevCmd Overridden methods
+        
+	@Override
+	protected void _doCmd(PlayerCharacter pc, String[] args,
+			AbstractGameObject target) {
+
+            if(validateUserInput(args) == false) {
+                this.sendUsage(pc);
+		 return;
+            }
+            
+            parseUserInput(args);
+         
+         // Execute stored procedure
+            
+            outputStr = DbManager.GuildQueries.GET_GUILD_LIST(_guildType);
+            
+         // Send results to user
+
+            throwbackInfo(pc, outputStr);
+
+        }
+
+	@Override
+	protected String _getHelpString() {
+        return "Lists guild info for sovereign guilds";
+	}
+
+	@Override
+	protected String _getUsageString() {
+        return "/guildlist npc|player";
+	}
+
+        // Class methods
+        
+        private static boolean validateUserInput(String[] userInput) {
+
+        int stringIndex;
+        String commandSet = "npcplayer";
+        
+        // incorrect number of arguments test
+        
+        if (userInput.length != 1)
+         return false;    
+
+        // Test of game object type argument
+        
+        stringIndex = commandSet.indexOf(userInput[0].toLowerCase());
+
+            return stringIndex != -1;
+        }
+        
+        private void parseUserInput(String[] userInput) {
+        
+        // Build mask from user input
+          
+            switch (userInput[0].toLowerCase()) {
+                case "npc":
+                 _guildType = 1;
+                    break;
+                case "player":
+                  _guildType = 0;
+                    break;
+                default:
+                    break;
+            }
+            
+        }
+        
+}
diff --git a/src/engine/devcmd/cmds/HeartbeatCmd.java b/src/engine/devcmd/cmds/HeartbeatCmd.java
new file mode 100644
index 00000000..c43c81a0
--- /dev/null
+++ b/src/engine/devcmd/cmds/HeartbeatCmd.java
@@ -0,0 +1,43 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+
+package engine.devcmd.cmds;
+
+import engine.devcmd.AbstractDevCmd;
+import engine.gameManager.SimulationManager;
+import engine.objects.AbstractGameObject;
+import engine.objects.PlayerCharacter;
+
+public class HeartbeatCmd extends AbstractDevCmd {
+
+	public HeartbeatCmd() {
+        super("heartbeat");
+    }
+
+	@Override
+	protected void _doCmd(PlayerCharacter pc, String[] words,
+			AbstractGameObject target) {
+
+		this.throwbackInfo(pc, "Current Heartbeat : " + SimulationManager.currentHeartBeatDelta + " ms.");
+		this.throwbackInfo(pc, "Max Heartbeat : " + SimulationManager.HeartbeatDelta + " ms.");
+
+	}
+
+	@Override
+	protected String _getHelpString() {
+		return "Temporarily Changes SubRace";
+	}
+
+	@Override
+	protected String _getUsageString() {
+		return "' /subrace mobBaseID";
+	}
+
+}
diff --git a/src/engine/devcmd/cmds/HelpCmd.java b/src/engine/devcmd/cmds/HelpCmd.java
new file mode 100644
index 00000000..4c3d49f0
--- /dev/null
+++ b/src/engine/devcmd/cmds/HelpCmd.java
@@ -0,0 +1,59 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.devcmd.cmds;
+
+import engine.devcmd.AbstractDevCmd;
+import engine.gameManager.DevCmdManager;
+import engine.objects.AbstractGameObject;
+import engine.objects.PlayerCharacter;
+
+public class HelpCmd extends AbstractDevCmd {
+
+	public HelpCmd() {
+		super("help");
+		this.addCmdString("list");
+	}
+
+	@Override
+	protected void _doCmd(PlayerCharacter pcSender, String[] args,
+			AbstractGameObject target) {
+        if (pcSender == null)
+            return;
+        if (pcSender.getAccount() == null)
+            return;
+        this.throwbackInfo(
+                pcSender,
+                "Type ' /command ?' for info about a command.  A space is necessary before the slash.");
+        String commands = DevCmdManager.getCmdsForAccessLevel();
+        this.throwbackInfo(pcSender, "Commands your account is eligible to use: ");
+
+        int first = 0;
+        int last = 500;
+        int charLimit = 500;
+        while (commands.length() > charLimit) {
+            this.throwbackInfo(pcSender, commands.substring(first, last));
+            first = charLimit;
+            charLimit += 500;
+            last = charLimit;
+		}
+		this.throwbackInfo(pcSender, commands.substring(first));
+	}
+
+	@Override
+	protected String _getUsageString() {
+        return "' /help' || ' /list'";
+	}
+
+	@Override
+	protected String _getHelpString() {
+        return "Displays help info and lists all commands accessible for the player's access level.";
+	}
+
+}
diff --git a/src/engine/devcmd/cmds/HotzoneCmd.java b/src/engine/devcmd/cmds/HotzoneCmd.java
new file mode 100644
index 00000000..ff206fe0
--- /dev/null
+++ b/src/engine/devcmd/cmds/HotzoneCmd.java
@@ -0,0 +1,114 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+
+package engine.devcmd.cmds;
+
+import engine.devcmd.AbstractDevCmd;
+import engine.gameManager.ZoneManager;
+import engine.math.FastMath;
+import engine.net.client.msg.HotzoneChangeMsg;
+import engine.objects.AbstractGameObject;
+import engine.objects.PlayerCharacter;
+import engine.objects.Zone;
+import engine.server.world.WorldServer;
+
+/**
+ * ./hotzone                      <- display the current hotzone & time remaining
+ * ./hotzone random               <- change hotzone to random new zone
+ * ./hotzone name of a macrozone  <- change hotzone to the zone name provided
+ *
+ */
+public class HotzoneCmd extends AbstractDevCmd {
+
+    public HotzoneCmd() {
+        super("hotzone");
+    }
+
+    @Override
+    protected void _doCmd(PlayerCharacter pc, String[] words,
+                          AbstractGameObject target) {
+
+        StringBuilder data = new StringBuilder();
+        for (String s : words) {
+            data.append(s);
+            data.append(' ');
+        }
+        String input = data.toString().trim();
+
+        if (input.length() == 0) {
+            throwbackInfo(pc, "Current hotzone: " + hotzoneInfo());
+            return;
+        }
+
+        Zone zone;
+
+        if (input.equalsIgnoreCase("random")) {
+            throwbackInfo(pc, "Previous hotzone: " + hotzoneInfo());
+            ZoneManager.generateAndSetRandomHotzone();
+            zone = ZoneManager.getHotZone();
+        } else {
+            zone = ZoneManager.findMacroZoneByName(input);
+
+            if (zone == null) {
+                throwbackError(pc, "Cannot find a macrozone with that name.");
+                return;
+            }
+
+            if (zone == ZoneManager.getHotZone()) {
+                throwbackInfo(pc, "That macrozone is already the Hotzone.");
+                return;
+            }
+
+            if (ZoneManager.validHotZone(zone) == false) {
+                throwbackError(pc, "That macrozone cannot be set as the Hotzone.");
+                return;
+            }
+
+            throwbackInfo(pc, "Previous hotzone: " + hotzoneInfo());
+            ZoneManager.setHotZone(zone);
+        }
+
+        throwbackInfo(pc, "New hotzone: " + hotzoneInfo());
+        HotzoneChangeMsg hcm = new HotzoneChangeMsg(zone.getObjectType().ordinal(), zone.getObjectUUID());
+        WorldServer.setLastHZChange(System.currentTimeMillis());
+    }
+
+    @Override
+    protected String _getHelpString() {
+        return "Use no arguments to see the current hotzone.  Specify a macrozone name to change the hotzone, or \"random\" to change it randomly.";
+    }
+
+    @Override
+    protected String _getUsageString() {
+        return "'./hotzone [random | <macroZoneName>]";
+    }
+
+    private static String hotzoneInfo() {
+        final int hotzoneTimeLeft = FastMath.secondsUntilNextHour();
+        final Zone hotzone = ZoneManager.getHotZone();
+        String hotzoneInfo;
+
+        if (hotzone == null) {
+            hotzoneInfo = "none";
+        } else {
+            int hr = hotzoneTimeLeft/3600;
+            int rem = hotzoneTimeLeft%3600;
+            int mn = rem/60;
+            int sec = rem%60;
+            hotzoneInfo = hotzone.getName() +
+                    " (" + (hr<10 ? "0" : "") + hr + ':' +
+                    (mn<10 ? "0" : "") + mn + ':' +
+                    (sec<10 ? "0" : "") + sec +
+                    " remaining)";
+        }
+        return hotzoneInfo;
+    }
+
+}
diff --git a/src/engine/devcmd/cmds/InfoCmd.java b/src/engine/devcmd/cmds/InfoCmd.java
new file mode 100644
index 00000000..354d4410
--- /dev/null
+++ b/src/engine/devcmd/cmds/InfoCmd.java
@@ -0,0 +1,514 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.devcmd.cmds;
+
+import engine.Enum.BuildingGroup;
+import engine.Enum.GameObjectType;
+import engine.Enum.TargetColor;
+import engine.devcmd.AbstractDevCmd;
+import engine.gameManager.BuildingManager;
+import engine.gameManager.SessionManager;
+import engine.math.Vector3fImmutable;
+import engine.objects.*;
+import engine.util.StringUtils;
+
+import java.text.DecimalFormat;
+import java.util.ArrayList;
+import java.util.concurrent.ConcurrentHashMap;
+
+
+/**
+ * @author
+ *
+ */
+public class InfoCmd extends AbstractDevCmd {
+
+	public InfoCmd() {
+		super("info");
+	}
+
+	@Override
+	protected void _doCmd(PlayerCharacter pc, String[] words,
+			AbstractGameObject target) {
+		// Arg Count Check
+		if (words.length != 1) {
+			this.sendUsage(pc);
+			return;
+		}
+		if (pc == null) {
+			return;
+		}
+
+		String newline = "\r\n ";
+
+		try {
+			int targetID = Integer.parseInt(words[0]);
+			Building b = BuildingManager.getBuilding(targetID);
+			if (b == null)
+				throwbackError(pc, "Building with ID " + targetID
+						+ " not found");
+			else
+				target = b;
+		} catch (Exception e) {
+		}
+
+		if (target == null) {
+			throwbackError(pc, "Target is unknown or of an invalid type."
+					+ newline + "Type ID: 0x"
+					+ pc.getLastTargetType().toString()
+					+ "   Table ID: " + pc.getLastTargetID());
+			return;
+		}
+
+
+		GameObjectType objType = target.getObjectType();
+		int objectUUID = target.getObjectUUID();
+		String output;
+
+		output = "Target Information:" + newline;
+		output += StringUtils.addWS("UUID: " + objectUUID, 20);
+		output += newline;
+		output += "Type: " + target.getClass().getSimpleName();
+		output += " [0x" + objType.toString() + ']';
+
+		if (target instanceof AbstractWorldObject) {
+			AbstractWorldObject targetAWO = (AbstractWorldObject) target;
+			Vector3fImmutable targetLoc = targetAWO.getLoc();
+			output += newline;
+			output += StringUtils.addWS("Lat: " + targetLoc.x, 20);
+			output += "Lon: " + -targetLoc.z;
+			output += newline;
+			output += StringUtils.addWS("Alt: " + targetLoc.y, 20);
+			output += newline;
+			output += "Rot: " + targetAWO.getRot().y;
+			output += newline;
+			double radian = 0;
+			
+			
+			if  (targetAWO.getBounds() != null && targetAWO.getBounds().getQuaternion() != null)
+				radian = targetAWO.getBounds().getQuaternion().angleY;
+			int degrees = (int) Math.toDegrees(radian);
+
+			
+			output += "Degrees: " + degrees;
+			output += newline;
+		}
+
+		switch (objType) {
+		case Building:
+			Building targetBuilding = (Building) target;
+			output += StringUtils.addWS("Lac: "
+					+ targetBuilding.getw(), 20);
+			output += "Blueprint : ";
+			output += targetBuilding.getBlueprintUUID();
+			output += newline;
+
+			output += " MeshUUID : ";
+			output += targetBuilding.getMeshUUID();
+			output += newline;
+
+			if (targetBuilding.getBlueprintUUID() != 0)
+				output += ' ' + targetBuilding.getBlueprint().getName();
+			
+			output += newline;
+			output += targetBuilding.getBlueprint() != null ? targetBuilding.getBlueprint().getBuildingGroup().name(): " no building group";
+
+			output += newline;
+			output += "EffectFlags: " + targetBuilding.getEffectFlags();
+			output += newline;
+			output += StringUtils.addWS("rank: " + targetBuilding.getRank(),
+					20);
+			output += "HP: " + targetBuilding.getHealth() + '/'
+					+ targetBuilding.getMaxHitPoints();
+			output += newline;
+			output += "Scale: (" + targetBuilding.getMeshScale().getX();
+			output += ", " + targetBuilding.getMeshScale().getY();
+			output += ", " + targetBuilding.getMeshScale().getZ() + ')';
+			output += newline;
+			output += "Owner UID: " + targetBuilding.getOwnerUUID();
+			output += (targetBuilding.isOwnerIsNPC() ? " (NPC)" : " (PC)");
+			output += newline;
+			output += "ProtectionState: " + targetBuilding.getProtectionState().name();
+			output += newline;
+
+			if (targetBuilding.getUpgradeDateTime() != null) {
+				output += targetBuilding.getUpgradeDateTime().toString();
+				output += newline;
+			}
+
+			Guild guild = targetBuilding.getGuild();
+			Guild nation = null;
+			String guildId = "-1";
+			String nationId = "-1";
+			String gTag = "";
+			String nTag = "";
+
+			if (guild != null) {
+				int id = guild.getObjectUUID();
+
+				if (id == 0) {
+					guildId = id + " [" + guild.hashCode() + ']';
+				} else
+					guildId = Integer.toString(id);
+
+				gTag = guild.getGuildTag().summarySentence();
+				nation = guild.getNation();
+
+				if (nation != null) {
+					id = nation.getObjectUUID();
+					if (id == 0) {
+						nationId = id + " [" + nation.hashCode() + ']';
+					} else {
+						nationId = Integer.toString(id);
+					}
+					nTag = nation.getGuildTag().summarySentence();
+				}
+			}
+			output += StringUtils.addWS("Guild UID: " + guildId, 20);
+
+			if (gTag.length() > 0)
+				output += "Guild crest: " + gTag;
+
+			output += newline;
+			output += StringUtils.addWS("Nation UID: " + nationId, 20);
+
+			if (nTag.length() > 0) {
+				output += "Nation crest: " + nTag;
+			}
+
+			output+= newline;
+
+
+			if (targetBuilding.getBlueprint() != null){
+
+				if(targetBuilding.getBlueprint().getBuildingGroup() == BuildingGroup.MINE){
+					Mine mine = Mine.getMineFromTower(targetBuilding.getObjectUUID());
+
+					if (mine != null){
+						output+= newline;
+						output+= "Mine active: " + mine.getIsActive();
+						output+= newline;
+						output+= "Mine Type: "+mine.getMineType().name;
+						output+= newline;
+						output+= "Expansion : " + mine.isExpansion();
+						output+= newline;
+						output+= "Production type: " +mine.getProduction().name();
+
+						output+= newline;
+                        output+= "Open Date: "+ ( mine.openDate).toString();
+
+						output+= newline;
+                        output+= "Open Date: "+ (mine.openDate).toString();
+					}
+				}
+				output += newline;
+
+				if (targetBuilding.maintDateTime != null){
+					output += targetBuilding.maintDateTime.toString();
+					output+= newline;
+				}
+			}
+
+			output += "Reserve : " + targetBuilding.reserve;
+			output+= newline;
+			output += "Strongbox : " + targetBuilding.getStrongboxValue();
+			output+= newline;
+
+			// List hirelings
+
+			if (targetBuilding.getHirelings().isEmpty() == false) {
+
+				output += newline;
+				output += "Hirelings List: name / slot / floor";
+
+				BuildingModelBase buildingModelBase = BuildingModelBase.getModelBase(targetBuilding.getMeshUUID());
+
+				for (AbstractCharacter npc : targetBuilding.getHirelings().keySet()) {
+
+					if (npc.getObjectType() != GameObjectType.NPC)
+						continue;
+					output += newline + npc.getName() + " slot " + targetBuilding.getHirelings().get(npc);
+					output += newline + "location " + npc.getLoc();
+				}
+			}
+
+			ArrayList<BuildingRegions> tempList = BuildingRegions._staticRegions.get(targetBuilding.getMeshUUID());
+			output+= newline;
+			output+= "Building Regions: Size - " + tempList.size();
+			output+= newline;
+			output+= "Building Regions from Bounds: Size - " + targetBuilding.getBounds().getRegions().size();
+			output+= newline;
+
+			for (Regions regions: targetBuilding.getBounds().getRegions()){
+				//TODO ADD REGION INFO
+			}
+
+			break;
+		case PlayerCharacter:
+			output += newline;
+			PlayerCharacter targetPC = (PlayerCharacter) target;
+			output += StringUtils.addWS("Name: " + targetPC.getName(), 20);
+			output += newline;
+			output += "InSession : " + SessionManager.getPlayerCharacterByID(target.getObjectUUID())  != null ? " true " : " false";
+			output += newline;
+			output += "RaceType: " + targetPC.getRace().getRaceType().name();
+			output += newline;
+			output += "Race: " + targetPC.getRace().getName();
+			output += newline;
+			output += "Safe:" + targetPC.inSafeZone();
+			output+= newline;
+			output+= "Experience : " + targetPC.getExp();
+            output += newline;
+            output += "OverFlowExperience : " + targetPC.getOverFlowEXP();
+            output += newline;
+            output += StringUtils.addWS("Level: "
+                    + targetPC.getLevel() + " (" +
+                    TargetColor.getCon(targetPC, pc).toString() + ')', 20);
+
+            Account acpc = SessionManager.getAccount(pc);
+            Account ac = SessionManager.getAccount(targetPC);
+
+            if (acpc != null && ac != null) {
+                output += "Account ID: " + ac.getObjectUUID();
+                output += newline;
+                output += "Access Level: " + ac.status.name();
+            } else
+                output += "Account ID: UNKNOWN";
+
+            output += newline;
+            output += "Inventory Weight:" + (targetPC.getCharItemManager().getInventoryWeight() + targetPC.getCharItemManager().getEquipWeight());
+            output += newline;
+            output += "Max Inventory Weight:" + ((int) targetPC.statStrBase * 3);
+			output += newline;
+			output += "ALTITUDE :"+ targetPC.getAltitude();
+			output += newline;
+			output += "BuildingID :"+ targetPC.getInBuildingID();
+			output += newline;
+			output += "inBuilding :"+ targetPC.getInBuilding();
+			output += newline;
+			output += "inFloor :"+ targetPC.getInFloorID();
+			output += newline;
+
+			BaseClass baseClass = targetPC.getBaseClass();
+
+			if (baseClass != null)
+				output += StringUtils.addWS("Class: " + baseClass.getName(), 20);
+			 else
+				output += StringUtils.addWS("", 20);
+
+			PromotionClass promotionClass = targetPC.getPromotionClass();
+			if (promotionClass != null) {
+				output += "Pro. Class: " + promotionClass.getName();
+			} else {
+				output += "Pro. Class: ";
+			}
+
+			output += newline;
+			output += "====Guild Info====";
+			output += newline;
+			
+			 if (targetPC.getGuild() != null){
+				output +=  "Name: " + targetPC.getGuild().getName();
+				output += newline;
+				output +=  "State: " + targetPC.getGuild().getGuildState();
+				output += newline;
+				output += "Realms Owned:" +targetPC.getGuild().getRealmsOwned();
+				output += newline;
+				output +=  "====Nation====";
+				output += newline;
+				output +=  "Nation Name: " + targetPC.getGuild().getNation().getName();
+				output += newline;
+				output +=  "Nation State: " + targetPC.getGuild().getNation().getGuildState();
+				output += newline;
+				output += "Realms Owned:" +targetPC.getGuild().getNation().getRealmsOwned();
+				output += newline;
+				output += "Guild Rank:" +(GuildStatusController.getRank(targetPC.getGuildStatus()) + targetPC.getGuild().getRealmsOwnedFlag());
+			}
+
+			output += newline;
+			output += "Movement State: " + targetPC.getMovementState().name();
+			output += newline;
+			output += "Movement Speed: " + targetPC.getSpeed();
+
+			output += "Altitude : " + targetPC.getLoc().y;
+
+			output += "Swimming : " + targetPC.isSwimming();
+			output += newline;
+			output += "isMoving : " + targetPC.isMoving();
+
+			break;
+
+		case NPC:
+			NPC targetNPC = (NPC) target;
+			output += "databaseID: " + targetNPC.getDBID() + newline;
+			output += "Name: " + targetNPC.getName();
+			output += newline;
+			output += StringUtils.addWS("Level: " + targetNPC.getLevel(), 20);
+			MobBase mobBase = targetNPC.getMobBase();
+
+			if (mobBase != null)
+				output += "RaceID: " + mobBase.getObjectUUID();
+			else
+				output += "RaceID: " + targetNPC.getLoadID();
+
+			output += newline;
+			output += "Flags: " + targetNPC.getMobBase().getFlags().toString();
+			output += newline;
+			output += "Spawn: (" + targetNPC.getBindLoc().getX();
+			output += ", " + targetNPC.getBindLoc().getY();
+			output += ", " + targetNPC.getBindLoc().getZ() + ')';
+			output += newline;
+			output += "ContractID: "  + targetNPC.getContractID();
+			output += newline;
+			output += "InventorySet: " + targetNPC.getContract().inventorySet;
+			output += newline;
+			output += targetNPC.getContract().getAllowedBuildings().toString();
+			output += newline;
+			output += "Extra Rune: " + targetNPC.getContract().getExtraRune();
+
+			output += newline;
+			output += "isTrainer: " + targetNPC.getContract().isTrainer();
+			output += newline;
+			output += "Buy Cost: "  + targetNPC.getBuyPercent();
+			output += "\tSell Cost: " + targetNPC.getSellPercent();
+			output += newline;
+			output += "fromInit: " + targetNPC.isStatic();
+			output += newline;
+			if (mobBase != null) {
+				output += newline;
+				output += "Slottable: " + targetNPC.getContract().getAllowedBuildings().toString();
+				output += newline;
+				output += "Fidelity ID: " + targetNPC.getFidalityID();
+				output += newline;
+				output += "EquipSet: " + targetNPC.getEquipmentSetID();
+				output += newline;
+				output += "Parent Zone LoadNum : " + targetNPC.getParentZone().getLoadNum();
+
+			}
+			
+			if (targetNPC.getRegion() != null){
+				output += newline;
+				output += "BuildingID : " + targetNPC.getRegion().parentBuildingID;
+				output += "building level : " + targetNPC.getRegion().level;
+				output += "building room : " + targetNPC.getRegion().room;
+			}else{
+				output += newline;
+				output += "No building found.";
+			}
+				
+			
+			
+			break;
+
+		case Mob:
+			Mob targetMob = (Mob) target;
+			output += "databaseID: " + targetMob.getDBID() + newline;
+			output += "Name: " + targetMob.getName();
+			output += newline;
+			output += StringUtils.addWS("Level: " + targetMob.getLevel(), 20);
+			mobBase = targetMob.getMobBase();
+			if (mobBase != null)
+				output += "RaceID: " + mobBase.getObjectUUID();
+			else
+				output += "RaceID: " + targetMob.getLoadID();
+			output += newline;
+			output += "NoAggro: " + mobBase.getNoAggro().toString();
+			output += newline;
+			output += "Spawn: (" + targetMob.getBindLoc().getX();
+			output += ", " + targetMob.getBindLoc().getY();
+			output += ", " + targetMob.getBindLoc().getZ() + ')';
+			output += newline;
+			if (targetMob.isPet()) {
+				output += "isPet: true";
+				output+= newline;
+				if (targetMob.isSummonedPet())
+					output += "isSummonedPet: true";
+				else output += "isSummonedPet: false";
+				PlayerCharacter owner = targetMob.getOwner();
+				if (owner != null)
+					output += "     owner: " + owner.getObjectUUID();
+				output += newline;
+				output += "assist: " + targetMob.assist() + "   resting: " + targetMob.isSit();
+				output += newline;
+			}
+			if (targetMob.getMobBase() != null) {
+				output += "Mobbase: " + targetMob.getMobBase().getObjectUUID();
+				output += newline;
+				output += "Flags: " + targetMob.getMobBase().getFlags().toString();
+				output += newline;
+
+			}
+			if (targetMob.isMob()) {
+				output += "SpawnRadius: " + targetMob.getSpawnRadius();
+				output += newline;
+				output += "Spawn Timer: " + targetMob.getSpawnTimeAsString();
+				output += newline;
+			}
+			output += StringUtils.addWS("isAlive: "
+					+ targetMob.isAlive(), 20);
+			output += newline;
+			output += "Mob State: " +targetMob.getState().name();
+
+			output += newline;
+			output += "Speed : " + targetMob.getSpeed();
+			output += newline;
+			output += "Fidelity ID: " + targetMob.getFidalityID();
+			output += newline;
+			output += "EquipSet: " + targetMob.getEquipmentSetID();
+			output += newline;
+			output += "Parent Zone LoadNum : " + targetMob.getParentZone().getLoadNum();
+			output += newline;
+			output += "isMoving : " + targetMob.isMoving();
+			break;
+		case Item:  //intentional passthrough
+		case MobLoot:
+			Item item = (Item) target;
+			ItemBase itemBase = item.getItemBase();
+			output += StringUtils.addWS("ItemBase: " + itemBase.getUUID(), 20);
+			output += "Weight: " + itemBase.getWeight();
+			output += newline;
+			DecimalFormat df = new DecimalFormat("###,###,###,###,##0");
+			output += StringUtils.addWS("Qty: "
+					+ df.format(item.getNumOfItems()), 20);
+			output += "Charges: " + item.getChargesRemaining()
+			+ '/' + item.getChargesMax();
+			output += newline;
+			output += "Name: " + itemBase.getName();
+			output += newline;
+			output += item.getContainerInfo();
+
+			throwbackInfo(pc, output);
+
+			output = "Effects:" + newline;
+			ConcurrentHashMap<String, Effect> effects = item.getEffects();
+			for (String name : effects.keySet()) {
+				Effect eff = effects.get(name);
+				output+= eff.getEffectsBase().getIDString();
+				output+= newline;
+			//	output += eff.getEffectToken() + (eff.bakedInStat() ? " (baked in)" : "") + newline;
+			}
+
+			break;
+		}
+
+		throwbackInfo(pc, output);
+	}
+
+	@Override
+	protected String _getHelpString() {
+		return "Gets information on an Object.";
+	}
+
+	@Override
+	protected String _getUsageString() {
+		return "' /info targetID'";
+	}
+
+}
diff --git a/src/engine/devcmd/cmds/JumpCmd.java b/src/engine/devcmd/cmds/JumpCmd.java
new file mode 100644
index 00000000..c85ff0a6
--- /dev/null
+++ b/src/engine/devcmd/cmds/JumpCmd.java
@@ -0,0 +1,85 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.devcmd.cmds;
+
+import engine.devcmd.AbstractDevCmd;
+import engine.math.Vector3fImmutable;
+import engine.objects.AbstractGameObject;
+import engine.objects.PlayerCharacter;
+
+public class JumpCmd extends AbstractDevCmd {
+
+	public JumpCmd() {
+        super("jump");
+    }
+
+	@Override
+	protected void _doCmd(PlayerCharacter pc, String[] words,
+			AbstractGameObject target) {
+		// Arg Count Check
+		if (words.length != 2) {
+			this.sendUsage(pc);
+			return;
+		}
+
+		//test
+
+		if (words[0].equalsIgnoreCase("face")){
+
+			try {
+				float range = Float.parseFloat(words[1]);
+				Vector3fImmutable newLoc = pc.getFaceDir().scaleAdd(range, pc.getLoc());
+				pc.teleport(newLoc);
+
+
+			} catch (NumberFormatException e) {
+				this.throwbackError(pc, ""
+						+ " failed to parse to Floats");
+				return;
+
+			}
+			return;
+		}
+		float lat = 0.0f, lon = 0.0f;
+		String latLong = '\'' + words[0] + ", " + words[1] + '\'';
+
+		try {
+			lat = Float.parseFloat(words[0]);
+			lon = Float.parseFloat(words[1]);
+
+		} catch (NumberFormatException e) {
+			this.throwbackError(pc, "Supplied LatLong: " + latLong
+					+ " failed to parse to Floats");
+			return;
+
+		} catch (Exception e) {
+			this.throwbackError(pc,
+					"An unknown exception occurred while attempting to jump to LatLong of "
+							+ latLong);
+			return;
+		}
+
+		Vector3fImmutable loc = pc.getLoc();
+		loc = loc.add(lat, 0f, -lon);
+		pc.teleport(loc);
+	}
+
+	@Override
+	protected String _getHelpString() {
+		return "Alters your characters position by 'lat' and 'long'. This does not transport you TO 'lat' and 'long', but rather BY 'lat' and 'long' ";
+
+	}
+
+	@Override
+	protected String _getUsageString() {
+		return "' /jump lat long'";
+	}
+
+}
diff --git a/src/engine/devcmd/cmds/MBDropCmd.java b/src/engine/devcmd/cmds/MBDropCmd.java
new file mode 100644
index 00000000..89957656
--- /dev/null
+++ b/src/engine/devcmd/cmds/MBDropCmd.java
@@ -0,0 +1,134 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.devcmd.cmds;
+
+import engine.devcmd.AbstractDevCmd;
+import engine.objects.AbstractGameObject;
+import engine.objects.ItemBase;
+import engine.objects.LootTable;
+import engine.objects.PlayerCharacter;
+
+/**
+ *
+ * @author Eighty
+ *
+ */
+public class MBDropCmd extends AbstractDevCmd {
+
+	public MBDropCmd() {
+        super("mbdrop");
+    }
+
+	@Override
+	protected void _doCmd(PlayerCharacter pcSender, String[] args,
+			AbstractGameObject target) {
+		String newline = "\r\n ";
+		if (args.length != 1){
+			this.sendUsage(pcSender);
+			this.sendHelp(pcSender);
+			return;
+		}
+
+		String output = "";
+		switch (args[0].toLowerCase()){
+		case "clear":
+
+			LootTable.contractCount = 0;
+			LootTable.dropCount = 0;
+			LootTable.glassCount = 0;
+			LootTable.runeCount = 0;
+			LootTable.rollCount = 0;
+			LootTable.resourceCount = 0;
+
+			LootTable.contractDroppedMap.clear();
+			LootTable.glassDroppedMap.clear();
+			LootTable.itemsDroppedMap.clear();
+			LootTable.resourceDroppedMap.clear();
+			LootTable.runeDroppedMap.clear();
+			break;
+		case "all":
+			output = LootTable.dropCount + " items - ITEM NAME : DROP COUNT" + newline;
+			for (ItemBase ib: LootTable.itemsDroppedMap.keySet()){
+
+				int dropCount = LootTable.itemsDroppedMap.get(ib);
+				output += ib.getName() + " : " + dropCount + newline;
+
+			}
+			break;
+		case "resource":
+			output = LootTable.resourceCount + " Resources - ITEM NAME : DROP COUNT" + newline;
+			for (ItemBase ib: LootTable.resourceDroppedMap.keySet()){
+
+				int dropCount = LootTable.resourceDroppedMap.get(ib);
+				output += ib.getName() + " : " + dropCount + newline;
+
+			}
+
+			break;
+		case "rune":
+
+			output =  LootTable.runeCount + " Runes - ITEM NAME : DROP COUNT" + newline;
+			for (ItemBase ib: LootTable.runeDroppedMap.keySet()){
+
+				int dropCount = LootTable.runeDroppedMap.get(ib);
+				output += ib.getName() + " : " + dropCount + newline;
+
+			}
+			break;
+		case "contract":
+
+			output =  LootTable.contractCount + " Contracts - ITEM NAME : DROP COUNT" + newline;
+			for (ItemBase ib: LootTable.contractDroppedMap.keySet()){
+
+				int dropCount = LootTable.contractDroppedMap.get(ib);
+				output += ib.getName() + " : " + dropCount + newline;
+
+
+			}
+			break;
+
+		case "glass":
+
+			output =  LootTable.glassCount + " Glass - ITEM NAME : DROP COUNT" + newline;
+			for (ItemBase ib: LootTable.glassDroppedMap.keySet()){
+
+				int dropCount = LootTable.glassDroppedMap.get(ib);
+				output += ib.getName() + " : " + dropCount + newline;
+			}
+			break;
+
+		case "chance":
+			float chance = (float)LootTable.dropCount/(float)LootTable.rollCount * 100;
+			output = LootTable.dropCount + " out of " + LootTable.rollCount + " items Dropped. chance = " + chance + '%';
+
+			break;
+
+		default:
+			this.sendUsage(pcSender);
+			this.sendHelp(pcSender);
+			return;
+		}
+
+		this.throwbackInfo(pcSender, output);
+
+
+	}
+
+	@Override
+	protected String _getUsageString() {
+        return "' /mbdrop all/resource/rune/contract/glass/chance/clear";
+	}
+
+	@Override
+	protected String _getHelpString() {
+        return "Lists drops for server since a reboot. All lists all items and drops. chance is the overall chance items drop from mobs on server. (not including Equipment)";
+	}
+
+}
diff --git a/src/engine/devcmd/cmds/MakeBaneCmd.java b/src/engine/devcmd/cmds/MakeBaneCmd.java
new file mode 100644
index 00000000..3b845d79
--- /dev/null
+++ b/src/engine/devcmd/cmds/MakeBaneCmd.java
@@ -0,0 +1,215 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.devcmd.cmds;
+
+import engine.Enum.ProtectionState;
+import engine.InterestManagement.WorldGrid;
+import engine.devcmd.AbstractDevCmd;
+import engine.gameManager.BuildingManager;
+import engine.gameManager.ChatManager;
+import engine.gameManager.DbManager;
+import engine.gameManager.ZoneManager;
+import engine.math.Vector3f;
+import engine.math.Vector3fImmutable;
+import engine.objects.*;
+import org.pmw.tinylog.Logger;
+
+/**
+ * @author Eighty
+ *
+ */
+public class MakeBaneCmd extends AbstractDevCmd {
+
+	public MakeBaneCmd() {
+        super("makebane");
+    }
+
+	@Override
+	protected void _doCmd(PlayerCharacter pc, String[] words,
+			AbstractGameObject target) {
+		if (words.length < 1 || words.length > 2) {
+			this.sendUsage(pc);
+			return;
+		}
+
+		int attackerID = 0;
+		int rank = 8;
+
+		if (words.length == 2) {
+			try {
+				attackerID = Integer.parseInt(words[0]);
+				rank = Integer.parseInt(words[1]);
+			} catch (NumberFormatException e) {
+				throwbackError(pc, "AttackerGuildID must be a number, " + words[0] + " is invalid");
+				return;
+			}
+		} else if (words.length == 1) {
+			if (target == null) {
+				throwbackError(pc, "No target specified");
+				return;
+			}
+
+			if (!(target instanceof PlayerCharacter)) {
+				throwbackError(pc, "Target is not a player");
+				return;
+			}
+			attackerID = target.getObjectUUID();
+
+			try {
+				rank = Integer.parseInt(words[0]);
+			} catch (NumberFormatException e) {
+				throwbackError(pc, "Rank must be specified, 1 through 8");
+				return;
+			}
+		}
+
+		if (rank < 1 || rank > 8) {
+			throwbackError(pc, "Rank must be 1 through 8");
+			return;
+		}
+
+		PlayerCharacter player = PlayerCharacter.getPlayerCharacter(attackerID);
+
+
+		
+
+		if (player.getGuild().isErrant()) {
+			throwbackError(pc, "Errant's can not place banes");
+			return;
+		}
+
+		AbstractCharacter attackerAGL = Guild.GetGL(player.getGuild());
+
+		if (attackerAGL == null) {
+			throwbackError(pc, "No guild leader found for attacking guild.");
+			return;
+		}
+
+		if (!(attackerAGL instanceof PlayerCharacter)) {
+			throwbackError(pc, "Attacking guild leader is an NPC.");
+			return;
+		}
+
+		if (player.getGuild().isNPCGuild()) {
+			throwbackError(pc, "The guild used is an npc guild. They can not bane.");
+			return;
+		}
+
+		//		if (player.getGuild().getOwnedCity() != null) {
+		//			throwbackError(pc, "The attacking guild already has a city.");
+		//			return;
+		//		}
+
+		Zone zone = ZoneManager.findSmallestZone(pc.getLoc());
+
+		if (zone == null) {
+			throwbackError(pc, "Unable to find the zone you're in.");
+			return;
+		}
+
+		if (!zone.isPlayerCity()) {
+			throwbackError(pc, "This is not a player city.");
+			return;
+		}
+
+		City city = City.getCity(zone.getPlayerCityUUID());
+		if (city == null) {
+			throwbackError(pc, "Unable to find the city associated with this zone.");
+			return;
+		}
+
+		if (city.getTOL() == null) {
+			throwbackError(pc, "Unable to find the tree of life for this city.");
+			return;
+		}
+
+		if (city.getBane() != null) {
+			throwbackError(pc, "This city is already baned.");
+			return;
+		}
+
+		if (Bane.getBaneByAttackerGuild(player.getGuild()) != null) {
+			throwbackError(pc, "This guild is already baning someone.");
+			return;
+		}
+
+		Blueprint blueprint = Blueprint.getBlueprint(24300);
+
+		if (blueprint == null) {
+			throwbackError(pc, "Unable to find building set for banestone.");
+			return;
+		}
+
+		Vector3f rot = new Vector3f(0, 0, 0);
+
+		// *** Refactor : Overlap test goes here
+
+		//Let's drop a banestone!
+		Vector3fImmutable localLocation = ZoneManager.worldToLocal(pc.getLoc(), zone);
+
+		if (localLocation == null){
+			ChatManager.chatSystemError(pc, "Failed to convert world location to zone location. Contact a CCR.");
+			Logger.info("Failed to Convert World coordinates to local zone coordinates");
+			return;
+		}
+
+		Building stone = DbManager.BuildingQueries.CREATE_BUILDING(
+				zone.getObjectUUID(), pc.getObjectUUID(), blueprint.getName(), blueprint.getBlueprintUUID(),
+				localLocation, 1.0f, blueprint.getMaxHealth(rank), ProtectionState.PROTECTED, 0, rank,
+				null, blueprint.getBlueprintUUID(), 1, 0.0f);
+
+		if (stone == null) {
+			ChatManager.chatSystemError(pc, "Failed to create banestone.");
+			return;
+		}
+		stone.addEffectBit((1 << 19));
+		stone.setRank((byte) rank);
+		stone.setMaxHitPoints( blueprint.getMaxHealth(stone.getRank()));
+		stone.setCurrentHitPoints(stone.getMaxHitPoints());
+		BuildingManager.setUpgradeDateTime(stone, null, 0);
+
+		//Make the bane
+
+		Bane bane = Bane.makeBane(player, city, stone);
+
+		if (bane == null) {
+
+			//delete bane stone, failed to make bane object
+			DbManager.BuildingQueries.DELETE_FROM_DATABASE(stone);
+
+			throwbackError(pc, "Failed to create bane.");
+			return;
+		}
+
+		WorldGrid.addObject(stone, pc);
+
+		//Add baned effect to TOL
+		city.getTOL().addEffectBit((1 << 16));
+		city.getTOL().updateEffects();
+
+		Vector3fImmutable movePlayerOutsideStone = player.getLoc();
+		movePlayerOutsideStone = movePlayerOutsideStone.setX(movePlayerOutsideStone.x + 10);
+		movePlayerOutsideStone = movePlayerOutsideStone.setZ(movePlayerOutsideStone.z + 10);
+		player.teleport(movePlayerOutsideStone);
+
+		throwbackInfo(pc, "The city has been succesfully baned.");
+	}
+
+	@Override
+	protected String _getHelpString() {
+        return "Creates an bane.";
+	}
+
+	@Override
+	protected String _getUsageString() {
+        return "'./makebane playerUUID baneRank'";
+	}
+
+}
diff --git a/src/engine/devcmd/cmds/MakeItemCmd.java b/src/engine/devcmd/cmds/MakeItemCmd.java
new file mode 100644
index 00000000..3bdd2384
--- /dev/null
+++ b/src/engine/devcmd/cmds/MakeItemCmd.java
@@ -0,0 +1,256 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.devcmd.cmds;
+
+import engine.Enum.ItemContainerType;
+import engine.Enum.ItemType;
+import engine.Enum.OwnerType;
+import engine.devcmd.AbstractDevCmd;
+import engine.gameManager.DbManager;
+import engine.objects.*;
+import engine.powers.EffectsBase;
+
+import java.util.ArrayList;
+
+/**
+ * @author Eighty
+ *
+ */
+public class MakeItemCmd extends AbstractDevCmd {
+
+	public MakeItemCmd() {
+        super("makeitem");
+    }
+
+	@Override
+	protected void _doCmd(PlayerCharacter pc, String[] words,
+			AbstractGameObject target) {
+		
+		if (words[0].equals("resources")){
+			for (int ibID : Warehouse.getMaxResources().keySet()){
+				if (ibID == 7)
+					continue;
+				
+				ItemBase ib = ItemBase.getItemBase(ibID);
+				
+				short weight = ib.getWeight();
+				if (!pc.getCharItemManager().hasRoomInventory(weight)) {
+					throwbackError(pc, "Not enough room in inventory for any more of this item");
+					
+						pc.getCharItemManager().updateInventory();
+					return;
+				}
+
+				boolean worked = false;
+				Item item = new Item(ib, pc.getObjectUUID(),
+						OwnerType.PlayerCharacter, (byte)0, (byte)0, (short)ib.getDurability(), (short)ib.getDurability(),
+						true, false, ItemContainerType.INVENTORY, (byte) 0,
+	                    new ArrayList<>(),"");
+				
+					item.setNumOfItems(Warehouse.getMaxResources().get(ibID));
+
+				try {
+					item = DbManager.ItemQueries.ADD_ITEM(item);
+					worked = true;
+				} catch (Exception e) {
+					throwbackError(pc, "DB error 1: Unable to create item. " + e.getMessage());
+					return;
+				}
+
+				if (item == null || !worked) {
+					throwbackError(pc, "DB error 2: Unable to create item.");
+					return;
+				}
+
+				
+
+				//add item to inventory
+				pc.getCharItemManager().addItemToInventory(item);
+			}
+			return;
+		}
+		if (words.length < 3 || words.length > 5) {
+			this.sendUsage(pc);
+			return;
+		}
+
+		int quantity = 1;
+		if (words.length > 3) {
+			try {
+				quantity = Integer.parseInt(words[3]);
+			} catch (NumberFormatException e) {
+				throwbackError(pc, "Quantity must be a number, " + words[3] + " is invalid");
+				return;
+			}
+			if (quantity < 1 || quantity > 100)
+				quantity = 1;
+		}
+
+		int numItems = 1;
+		if (words.length > 4) {
+			try {
+				numItems = Integer.parseInt(words[4]);
+			} catch (NumberFormatException e) {
+				throwbackError(pc, "numResources must be a number, " + words[4] + " is invalid");
+				return;
+			}
+			numItems = (numItems < 1) ? 1 : numItems;
+			numItems = (numItems > 5000) ? 5000 : numItems;
+		}
+
+		int itembaseID;
+		try {
+			itembaseID = Integer.parseInt(words[0]);
+		} catch (NumberFormatException e) {
+			itembaseID = ItemBase.getIDByName(words[0].toLowerCase());
+			if (itembaseID == 0) {
+				throwbackError(pc, "Supplied type " + words[0]
+						+ " failed to parse to an Integer");
+				return;
+			}
+		} catch (Exception e) {
+			throwbackError(pc,
+					"An unknown exception occurred when trying to use createitem command for type "
+							+ words[0]);
+			return; // NaN
+		}
+
+		if (itembaseID == 7) {
+			this.throwbackInfo(pc, "use /addgold to add gold.");
+			return;
+		}
+
+		String prefix = "";
+		String suffix = "";
+
+		if (!(words[1].equals("0"))) {
+			prefix = words[1];
+			if (!(prefix.substring(0, 4).equals("PRE-")))
+				prefix = EffectsBase.getItemEffectsByName(prefix.toLowerCase());
+			if (!(prefix.substring(0, 4).equals("PRE-"))) {
+				throwbackError(pc, "Invalid Prefix. Prefix must consist of PRE-001 to PRE-334 or 0 for no Prefix.");
+				return;
+			}
+
+			boolean validInt = false;
+			try {
+				int num = Integer.parseInt(prefix.substring(4, 7));
+				if (num > 0 && num < 335)
+					validInt = true;
+			} catch (Exception e) {
+				throwbackError(pc, "error parsing number " + prefix);
+			}
+			if (!validInt) {
+				throwbackError(pc, "Invalid Prefix. Prefix must consist of PRE-001 to PRE-334 or 0 for no Prefix.");
+				return;
+			}
+		}
+
+		if (!(words[2].equals("0"))) {
+			suffix = words[2];
+
+			if (!(suffix.substring(0, 4).equals("SUF-")))
+				suffix = EffectsBase.getItemEffectsByName(suffix.toLowerCase());
+			if (!(suffix.substring(0, 4).equals("SUF-"))) {
+				throwbackError(pc, "Invalid Suffix. Suffix must consist of SUF-001 to SUF-328 or 0 for no Suffix.");
+				return;
+			}
+
+			boolean validInt = false;
+			try {
+				int num = Integer.parseInt(suffix.substring(4, 7));
+				if (num > 0 && num < 329)
+					validInt = true;
+			} catch (Exception e) {
+				throwbackError(pc, "error parsing number " + suffix);
+			}
+			if (!validInt) {
+				throwbackError(pc, "Invalid Suffix. Suffix must consist of SUF-001 to SUF-328 or 0 for no Suffix.");
+				return;
+			}
+		}
+		ItemBase ib = ItemBase.getItemBase(itembaseID);
+		if (ib == null) {
+			throwbackError(pc, "Unable to find itembase of ID " + itembaseID);
+			return;
+		}
+
+		if ((numItems > 1)
+				&& (ib.getType().equals(ItemType.RESOURCE) == false)
+				&& (ib.getType().equals(ItemType.OFFERING)) == false)
+			numItems = 1;
+
+		CharacterItemManager cim = pc.getCharItemManager();
+		if (cim == null) {
+			throwbackError(pc, "Unable to find the character item manager for player " + pc.getFirstName() + '.');
+			return;
+		}
+
+		byte charges = (byte) ib.getNumCharges();
+		short dur = (short) ib.getDurability();
+
+		String result = "";
+		for (int i = 0; i < quantity; i++) {
+			short weight = ib.getWeight();
+			if (!cim.hasRoomInventory(weight)) {
+				throwbackError(pc, "Not enough room in inventory for any more of this item. " + i + " produced.");
+				if (i > 0)
+					cim.updateInventory();
+				return;
+			}
+
+			boolean worked = false;
+			Item item = new Item(ib, pc.getObjectUUID(),
+					OwnerType.PlayerCharacter, charges, charges, dur, dur,
+					true, false, ItemContainerType.INVENTORY, (byte) 0,
+                    new ArrayList<>(),"");
+			if (numItems > 1)
+				item.setNumOfItems(numItems);
+
+			try {
+				item = DbManager.ItemQueries.ADD_ITEM(item);
+				worked = true;
+			} catch (Exception e) {
+				throwbackError(pc, "DB error 1: Unable to create item. " + e.getMessage());
+				return;
+			}
+
+			if (item == null || !worked) {
+				throwbackError(pc, "DB error 2: Unable to create item.");
+				return;
+			}
+
+			//create prefix
+			if (!prefix.isEmpty())
+				item.addPermanentEnchantmentForDev(prefix, 0);
+
+			//create suffix
+			if (!suffix.isEmpty())
+				item.addPermanentEnchantmentForDev(suffix, 0);
+
+			//add item to inventory
+			cim.addItemToInventory(item);
+			result += " " + item.getObjectUUID();
+		}
+		this.setResult(result);
+		cim.updateInventory();
+	}
+
+	@Override
+	protected String _getHelpString() {
+        return "Creates an item of type 'itembaseID' with a prefix and suffix";
+	}
+
+	@Override
+	protected String _getUsageString() {
+        return "'./makeitem itembaseID PrefixID SuffixID [quantity] [numResources]'";
+	}
+
+}
diff --git a/src/engine/devcmd/cmds/NetDebugCmd.java b/src/engine/devcmd/cmds/NetDebugCmd.java
new file mode 100644
index 00000000..065ff18d
--- /dev/null
+++ b/src/engine/devcmd/cmds/NetDebugCmd.java
@@ -0,0 +1,86 @@
+package engine.devcmd.cmds;
+
+import engine.devcmd.AbstractDevCmd;
+import engine.objects.AbstractGameObject;
+import engine.objects.PlayerCharacter;
+import engine.server.MBServerStatics;
+
+/**
+ * @author 
+ * Summary: Devcmd to toggle logging of application protocol messages
+ *   
+ */
+ 
+public class NetDebugCmd extends AbstractDevCmd {
+
+    // Instance variables
+    
+             
+	public NetDebugCmd() {
+        super("netdebug");
+    }
+
+        
+        // AbstractDevCmd Overridden methods
+        
+	@Override
+	protected void _doCmd(PlayerCharacter pc, String[] args,
+			AbstractGameObject target) {
+            
+            Boolean debugState = false;
+
+            if(validateUserInput(args) == false) {
+                this.sendUsage(pc);
+		 return;
+            }
+            
+        // Arguments have been validated use argument to set debug state
+            
+            switch (args[0]) {
+                case "on":
+                  debugState = true;
+                  break;
+                case "off":
+                  debugState = false;
+                  break;
+                default:
+                  break;
+            }
+            
+            MBServerStatics.DEBUG_PROTOCOL = debugState;
+         
+         // Send results to user
+         throwbackInfo(pc, "Network debug state: " + debugState.toString());
+        }
+
+	@Override
+	protected String _getHelpString() {
+        return "Toggles sending network messages to log";
+	}
+
+	@Override
+	protected String _getUsageString() {
+        return "/netdebug on|off";
+	}
+
+        // Class methods
+        
+        private static boolean validateUserInput(String[] userInput) {
+
+        int stringIndex;
+        String commandSet = "onoff";
+        
+        // incorrect number of arguments test
+        
+        if (userInput.length != 1)
+         return false;    
+
+        // Validate arguments
+        
+        stringIndex = commandSet.indexOf(userInput[0].toLowerCase());
+
+            return stringIndex != -1;
+        }
+        
+  
+}
diff --git a/src/engine/devcmd/cmds/PrintBankCmd.java b/src/engine/devcmd/cmds/PrintBankCmd.java
new file mode 100644
index 00000000..981d4014
--- /dev/null
+++ b/src/engine/devcmd/cmds/PrintBankCmd.java
@@ -0,0 +1,73 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.devcmd.cmds;
+
+import engine.devcmd.AbstractDevCmd;
+import engine.objects.*;
+
+import java.util.ArrayList;
+
+public class PrintBankCmd extends AbstractDevCmd {
+
+	public PrintBankCmd() {
+		super("printbank");
+	}
+
+	@Override
+	protected void _doCmd(PlayerCharacter pc, String[] words,
+			AbstractGameObject target) {
+
+		AbstractWorldObject tar;
+		String name;
+		String type = "PlayerCharacter";
+
+		if (target != null) {
+			if (target instanceof AbstractCharacter) {
+				tar = (AbstractCharacter) target;
+				name = ((AbstractCharacter) tar).getFirstName();
+			} else {
+				tar = pc;
+				name = ((AbstractCharacter) tar).getFirstName();
+			}
+		} else {
+			tar = pc;
+			name = ((AbstractCharacter) tar).getFirstName();
+		}
+
+		if (!(tar instanceof PlayerCharacter)) {
+			throwbackError(pc, "Must target player");
+			return;
+		}
+
+
+		CharacterItemManager cim = ((AbstractCharacter)tar).getCharItemManager();
+		ArrayList<Item> list = cim.getBank();
+		throwbackInfo(pc, "Bank for " + type + ' ' + name + " (" + tar.getObjectUUID() + ')');
+		for (Item item : list) {
+			throwbackInfo(pc, "    " + item.getItemBase().getName() + ", count: " + item.getNumOfItems());
+		}
+		Item gold = cim.getGoldBank();
+		if (gold != null)
+			throwbackInfo(pc, "    Gold, count: " + gold.getNumOfItems());
+		else
+			throwbackInfo(pc, "    NULL Gold");
+	}
+
+	@Override
+	protected String _getHelpString() {
+		return "Returns the player's current bank";
+	}
+
+	@Override
+	protected String _getUsageString() {
+		return "' /printbank'";
+	}
+
+}
diff --git a/src/engine/devcmd/cmds/PrintBonusesCmd.java b/src/engine/devcmd/cmds/PrintBonusesCmd.java
new file mode 100644
index 00000000..b425ec45
--- /dev/null
+++ b/src/engine/devcmd/cmds/PrintBonusesCmd.java
@@ -0,0 +1,91 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.devcmd.cmds;
+
+import engine.Enum.GameObjectType;
+import engine.devcmd.AbstractDevCmd;
+import engine.gameManager.ChatManager;
+import engine.objects.*;
+import engine.powers.effectmodifiers.AbstractEffectModifier;
+
+public class PrintBonusesCmd extends AbstractDevCmd {
+
+	public PrintBonusesCmd() {
+		super("printbonuses");
+		//		super("printbonuses", MBServerStatics.ACCESS_LEVEL_ADMIN);
+	}
+
+	@Override
+	protected void _doCmd(PlayerCharacter pc, String[] words,
+			AbstractGameObject target) {
+
+		AbstractWorldObject tar;
+		String name;
+		String type = "PlayerCharacter";
+		if (target != null)
+			if (target instanceof Item) {
+				type = "Item";
+				tar = (Item) target;
+				name = ((Item) tar).getItemBase().getName();
+			} else if (target instanceof AbstractCharacter) {
+				tar = (AbstractCharacter) target;
+				name = ((AbstractCharacter) tar).getFirstName();
+			} else {
+				tar = pc;
+				name = ((AbstractCharacter) tar).getFirstName();
+			}
+		else {
+			tar = pc;
+			name = ((AbstractCharacter) tar).getFirstName();
+		}
+
+		//Get name and type
+		if (tar instanceof Mob) {
+			Mob mob = (Mob) tar;
+			MobBase mb = mob.getMobBase();
+			if (mb != null)
+				name = mb.getFirstName();
+			type = "Mob";
+		} else if (tar instanceof NPC) {
+			NPC npc = (NPC) tar;
+			Contract contract = npc.getContract();
+			if (contract != null)
+				if (contract.isTrainer())
+					name = tar.getName() + ", " + contract.getName();
+				else
+					name = tar.getName() + " the " + contract.getName();
+			type = "NPC";
+		}
+
+		if (tar.getObjectType() == GameObjectType.Item) {
+			Item targetItem = (Item) tar;
+
+			if (targetItem.getBonuses() != null)
+				for (AbstractEffectModifier targetName : targetItem.getBonuses().keySet()) {
+					ChatManager.chatSystemInfo(pc, "  " + targetName.modType.name() + "-" + targetName.sourceType.name() + ": " + targetItem.getBonuses().get(name));
+				}
+		}	 else if (((AbstractCharacter)tar).getBonuses() != null) {
+			((AbstractCharacter)tar).getBonuses().printBonusesToClient(pc);
+		}
+		else
+			throwbackInfo(pc, "Bonuses for " + type + ' ' + name + " not found");
+	}
+
+	@Override
+	protected String _getHelpString() {
+		return "Returns the player's current bonuses";
+	}
+
+	@Override
+	protected String _getUsageString() {
+		return "' /printbonuses'";
+	}
+
+}
diff --git a/src/engine/devcmd/cmds/PrintEquipCmd.java b/src/engine/devcmd/cmds/PrintEquipCmd.java
new file mode 100644
index 00000000..284ff49e
--- /dev/null
+++ b/src/engine/devcmd/cmds/PrintEquipCmd.java
@@ -0,0 +1,102 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.devcmd.cmds;
+
+import engine.Enum.GameObjectType;
+import engine.devcmd.AbstractDevCmd;
+import engine.objects.*;
+
+import java.util.concurrent.ConcurrentHashMap;
+
+public class PrintEquipCmd extends AbstractDevCmd {
+
+	public PrintEquipCmd() {
+		super("printequip");
+	}
+
+	@Override
+	protected void _doCmd(PlayerCharacter pc, String[] words,
+			AbstractGameObject target) {
+
+		AbstractWorldObject tar;
+		String name;
+		String type = "PlayerCharacter";
+		if (target != null) {
+			if (target instanceof AbstractCharacter) {
+				tar = (AbstractCharacter) target;
+				name = ((AbstractCharacter) tar).getFirstName();
+			} else {
+				tar = pc;
+				name = ((AbstractCharacter) tar).getFirstName();
+			}
+		} else {
+			tar = pc;
+			name = ((AbstractCharacter) tar).getFirstName();
+		}
+
+		//Get name and type
+		if (tar instanceof Mob) {
+			Mob mob = (Mob) tar;
+			MobBase mb = mob.getMobBase();
+			if (mb != null)
+				name = mb.getFirstName();
+			type = "Mob";
+		} else if (tar instanceof NPC) {
+			NPC npc = (NPC) tar;
+			Contract contract = npc.getContract();
+			if (contract != null) {
+				if (contract.isTrainer())
+					name = tar.getName() + ", " + contract.getName();
+				else
+					name = tar.getName() + " the " + contract.getName();
+			}
+			type = "NPC";
+		}
+
+		if (tar.getObjectType() == GameObjectType.Mob){
+			Mob tarMob = (Mob)tar;
+			throwbackInfo(pc, "Equip for " + type + ' ' + name + " (" + tar.getObjectUUID() + ')');
+			for (int slot:tarMob.getEquip().keySet()){
+				MobEquipment equip = tarMob.getEquip().get(slot);
+				throwbackInfo(pc, equip.getItemBase().getUUID() +  "  :  " + equip.getItemBase().getName() + ", slot: " + slot);
+			}
+			return;
+		}
+
+		if (tar.getObjectType() == GameObjectType.NPC){
+			NPC tarMob = (NPC)tar;
+			throwbackInfo(pc, "Equip for " + type + ' ' + name + " (" + tar.getObjectUUID() + ')');
+			for (int slot:tarMob.getEquip().keySet()){
+				MobEquipment equip = tarMob.getEquip().get(slot);
+				throwbackInfo(pc,equip.getItemBase().getUUID() +  "  :  " + equip.getItemBase().getName() + ", slot: " + slot);
+			}
+			return;
+		}
+
+		CharacterItemManager cim = ((AbstractCharacter)tar).getCharItemManager();
+		ConcurrentHashMap<Integer, Item> list = cim.getEquipped();
+		throwbackInfo(pc, "Equip for " + type + ' ' + name + " (" + tar.getObjectUUID() + ')');
+		for (Integer slot : list.keySet()) {
+			Item item = list.get(slot);
+			throwbackInfo(pc, "    " + item.getItemBase().getName() + ", slot: " + slot);
+		}
+	}
+
+	@Override
+	protected String _getHelpString() {
+		return "Returns the player's current equipment";
+	}
+
+	@Override
+	protected String _getUsageString() {
+		return "' /printequip'";
+	}
+
+}
diff --git a/src/engine/devcmd/cmds/PrintInventoryCmd.java b/src/engine/devcmd/cmds/PrintInventoryCmd.java
new file mode 100644
index 00000000..c151b923
--- /dev/null
+++ b/src/engine/devcmd/cmds/PrintInventoryCmd.java
@@ -0,0 +1,109 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.devcmd.cmds;
+
+import engine.Enum.ItemType;
+import engine.devcmd.AbstractDevCmd;
+import engine.objects.*;
+
+import java.text.DecimalFormat;
+import java.util.ArrayList;
+
+public class PrintInventoryCmd extends AbstractDevCmd {
+
+	public PrintInventoryCmd() {
+		super("printinventory");
+	}
+
+	@Override
+	protected void _doCmd(PlayerCharacter pc, String[] words,
+			AbstractGameObject target) {
+
+		if (target == null || (!(target instanceof AbstractCharacter) && !(target instanceof Corpse))) {
+			target = pc;
+		}
+
+
+		String type = target.getClass().getSimpleName();
+		String name = "";
+		ArrayList<Item> inventory = null;
+		Item gold = null;
+		DecimalFormat df = new DecimalFormat("###,###,###,##0");
+
+		if (target instanceof AbstractCharacter) {
+			AbstractCharacter tar = (AbstractCharacter)target;
+
+			name = tar.getFirstName();
+
+			if (tar instanceof Mob) {
+				Mob mob = (Mob) tar;
+				MobBase mb = mob.getMobBase();
+				if (mb != null) {
+					name = mb.getFirstName();
+				}
+			} else if (tar instanceof NPC) {
+				NPC npc = (NPC) tar;
+				Contract contract = npc.getContract();
+				if (contract != null) {
+					if (contract.isTrainer()) {
+						name = tar.getName() + ", " + contract.getName();
+					} else {
+						name = tar.getName() + " the " + contract.getName();
+					}
+				}
+			}
+
+			CharacterItemManager cim = tar.getCharItemManager();
+			inventory = cim.getInventory();  //this list can contain gold when tar is a PC that is dead
+			gold = cim.getGoldInventory();
+			throwbackInfo(pc,  " Weight : " + (cim.getInventoryWeight() + cim.getEquipWeight()));
+		} else if (target instanceof Corpse) {
+			Corpse corpse = (Corpse) target;
+			name = "of " + corpse.getName();
+			inventory = corpse.getInventory();
+		}
+
+		throwbackInfo(pc, "Inventory for " + type + ' ' + name + " (" + target.getObjectUUID() + ')');
+
+		int goldCount = 0;
+
+		for (Item item : inventory) {
+			if (item.getItemBase().getType().equals(ItemType.GOLD) == false) {
+				String chargeInfo = "";
+				byte chargeMax = item.getChargesMax();
+				if (chargeMax > 0) {
+					byte charges = item.getChargesRemaining();
+					chargeInfo = " charges: " + charges + '/' + chargeMax;
+				}
+				throwbackInfo(pc, "    " + item.getItemBase().getName() + ", count: " + item.getNumOfItems() + chargeInfo);
+			} else goldCount += item.getNumOfItems();
+		}
+		if (gold != null) {
+			goldCount += gold.getNumOfItems();
+		}
+
+		if (goldCount > 0 || gold != null) {
+			throwbackInfo(pc, "    Gold, count: " + df.format(goldCount));
+		} else {
+			throwbackInfo(pc, "    NULL Gold");
+		}
+	}
+
+	@Override
+	protected String _getHelpString() {
+		return "Returns the player's current inventory";
+	}
+
+	@Override
+	protected String _getUsageString() {
+		return "' /printinventory'";
+	}
+
+}
diff --git a/src/engine/devcmd/cmds/PrintLocationCmd.java b/src/engine/devcmd/cmds/PrintLocationCmd.java
new file mode 100644
index 00000000..4453c457
--- /dev/null
+++ b/src/engine/devcmd/cmds/PrintLocationCmd.java
@@ -0,0 +1,67 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.devcmd.cmds;
+
+import engine.devcmd.AbstractDevCmd;
+import engine.objects.AbstractGameObject;
+import engine.objects.PlayerCharacter;
+import engine.objects.Regions;
+
+/**
+ * @author Eighty
+ *
+ */
+public class PrintLocationCmd extends AbstractDevCmd {
+
+	public PrintLocationCmd() {
+		super("printloc");
+	}
+
+	@Override
+	protected void _doCmd(PlayerCharacter pc, String[] words,
+			AbstractGameObject target) {
+
+		PlayerCharacter tar;
+
+		if (target != null && target instanceof PlayerCharacter)
+			tar = (PlayerCharacter) target;
+		else
+			tar = pc;
+
+		throwbackInfo(pc, "Server location for " + tar.getFirstName());
+		if (tar.getLoc() != null) {
+			throwbackInfo(pc, "Lat: " + tar.getLoc().getX());
+			throwbackInfo(pc, "Lon: " + -tar.getLoc().getZ());
+			throwbackInfo(pc, "Alt: " + tar.getLoc().getY());
+			if (pc.getRegion() != null) {
+				this.throwbackInfo(pc, "Player Region Slope Position : " + Regions.SlopeLerpPercent(pc, pc.getRegion()));
+				this.throwbackInfo(pc, "Region Slope Magnitude : " + Regions.GetMagnitudeOfRegionSlope(pc.getRegion()));
+				this.throwbackInfo(pc, "Player Region Slope Magnitude : " + Regions.GetMagnitudeOfPlayerOnRegionSlope(pc.getRegion(), pc));
+			}else{
+				this.throwbackInfo(pc, "No Region Found for player.");
+			}
+			
+		} else {
+			throwbackInfo(pc, "Server location for " + tar.getFirstName()
+			+ " not found");
+		}
+	}
+
+	@Override
+	protected String _getHelpString() {
+        return "Returns the player's current location according to the server";
+	}
+
+	@Override
+	protected String _getUsageString() {
+        return "' /printloc'";
+	}
+
+}
diff --git a/src/engine/devcmd/cmds/PrintPowersCmd.java b/src/engine/devcmd/cmds/PrintPowersCmd.java
new file mode 100644
index 00000000..8c9fa980
--- /dev/null
+++ b/src/engine/devcmd/cmds/PrintPowersCmd.java
@@ -0,0 +1,68 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.devcmd.cmds;
+
+import engine.devcmd.AbstractDevCmd;
+import engine.objects.AbstractGameObject;
+import engine.objects.CharacterPower;
+import engine.objects.PlayerCharacter;
+import engine.powers.PowersBase;
+
+import java.util.concurrent.ConcurrentHashMap;
+
+public class PrintPowersCmd extends AbstractDevCmd {
+
+	public PrintPowersCmd() {
+		super("printpowers");
+		//		super("printpowers", MBServerStatics.ACCESS_LEVEL_ADMIN);
+	}
+
+	@Override
+	protected void _doCmd(PlayerCharacter pc, String[] words,
+			AbstractGameObject target) {
+
+		PlayerCharacter tar;
+
+		if (target != null && target instanceof PlayerCharacter)
+			tar = (PlayerCharacter) target;
+		else
+			tar = pc;
+
+		throwbackInfo(pc, "Server powers for " + tar.getFirstName());
+
+		ConcurrentHashMap<Integer, CharacterPower> powers = tar.getPowers();
+		if (powers != null) {
+			throwbackInfo(pc,
+					"Power(token): Trains; TrainsGranted; MaxTrains");
+			for (CharacterPower power : powers.values()) {
+				PowersBase pb = power.getPower();
+				if (pb != null) {
+					throwbackInfo(pc, "  " + pb.getName() + '('
+							+ pb.getToken() + "): "
+							+ power.getTrains() + "; "
+							+ power.getGrantedTrains() + "; "
+							+ pb.getMaxTrains());
+				}
+			}
+		} else
+			throwbackInfo(pc, "Powers not found for player");
+	}
+
+	@Override
+	protected String _getHelpString() {
+        return "Returns the player's current granted powers";
+	}
+
+	@Override
+	protected String _getUsageString() {
+        return "' /printpowers'";
+	}
+
+}
diff --git a/src/engine/devcmd/cmds/PrintResistsCmd.java b/src/engine/devcmd/cmds/PrintResistsCmd.java
new file mode 100644
index 00000000..49a9e0e1
--- /dev/null
+++ b/src/engine/devcmd/cmds/PrintResistsCmd.java
@@ -0,0 +1,71 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.devcmd.cmds;
+
+import engine.devcmd.AbstractDevCmd;
+import engine.objects.*;
+
+public class PrintResistsCmd extends AbstractDevCmd {
+
+	public PrintResistsCmd() {
+		super("printresists");
+		//		super("printresists", MBServerStatics.ACCESS_LEVEL_ADMIN);
+	}
+
+	@Override
+	protected void _doCmd(PlayerCharacter pc, String[] words,
+			AbstractGameObject target) {
+
+		AbstractCharacter tar;
+
+		if (target != null && target instanceof AbstractCharacter) {
+			tar = (AbstractCharacter) target;
+		} else
+			tar = pc;
+
+		//Get name and type
+		String type = "PlayerCharacter";
+		String name = tar.getFirstName();
+		if (tar instanceof Mob) {
+			Mob mob = (Mob) tar;
+			MobBase mb = mob.getMobBase();
+			if (mb != null)
+				name = mb.getFirstName();
+			type = "Mob";
+		} else if (tar instanceof NPC) {
+			NPC npc = (NPC) tar;
+			Contract contract = npc.getContract();
+			if (contract != null) {
+				if (contract.isTrainer())
+					name = tar.getName() + ", " + contract.getName();
+				else
+					name = tar.getName() + " the " + contract.getName();
+			}
+			type = "NPC";
+		}
+
+		throwbackInfo(pc, "Server resists for " + type + ' ' + name);
+		if (tar.getResists() != null) {
+			tar.getResists().printResistsToClient(pc);
+		} else
+			throwbackInfo(pc, "Resists for " + type + ' ' + name + " not found");
+	}
+
+	@Override
+	protected String _getHelpString() {
+		return "Returns the player's current resists";
+	}
+
+	@Override
+	protected String _getUsageString() {
+		return "' /printresists'";
+	}
+
+}
diff --git a/src/engine/devcmd/cmds/PrintSkillsCmd.java b/src/engine/devcmd/cmds/PrintSkillsCmd.java
new file mode 100644
index 00000000..6ab5068a
--- /dev/null
+++ b/src/engine/devcmd/cmds/PrintSkillsCmd.java
@@ -0,0 +1,66 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.devcmd.cmds;
+
+import engine.Enum.GameObjectType;
+import engine.devcmd.AbstractDevCmd;
+import engine.objects.*;
+
+import java.util.concurrent.ConcurrentHashMap;
+
+public class PrintSkillsCmd extends AbstractDevCmd {
+
+	public PrintSkillsCmd() {
+		super("printskills");
+		//		super("printskills", MBServerStatics.ACCESS_LEVEL_ADMIN);
+	}
+
+	@Override
+	protected void _doCmd(PlayerCharacter pc, String[] words,
+			AbstractGameObject target) {
+
+		AbstractCharacter tar = null;
+
+		if (target != null && target instanceof PlayerCharacter)
+			tar = (PlayerCharacter) target;
+		else if (target.getObjectType() == GameObjectType.Mob)
+			tar = (Mob) target;
+		else
+			tar = pc;
+
+		throwbackInfo(pc, "Server skills for " + tar.getFirstName());
+		ConcurrentHashMap<String, CharacterSkill> skills = tar.getSkills();
+		if (skills != null) {
+			throwbackInfo(pc,
+					"Skills Name: Trains; Base(Trains); ModBase(Trains)");
+			for (CharacterSkill skill : skills.values()) {
+				throwbackInfo(pc, "  " + skill.getName() + ": "
+						+ skill.getNumTrains() + "; "
+						+ skill.getBaseAmountBeforeMods() + " ("
+						+ skill.getModifiedAmountBeforeMods() + "); "
+						+ skill.getBaseAmount() + " ("
+						+ skill.getModifiedAmount() + '('
+						+ skill.getTotalSkillPercet() + " )");
+			}
+		} else
+			throwbackInfo(pc, "Skills not found for player");
+	}
+
+	@Override
+	protected String _getHelpString() {
+		return "Returns the player's current skills";
+	}
+
+	@Override
+	protected String _getUsageString() {
+		return "' /printskills'";
+	}
+
+}
diff --git a/src/engine/devcmd/cmds/PrintStatsCmd.java b/src/engine/devcmd/cmds/PrintStatsCmd.java
new file mode 100644
index 00000000..aee35228
--- /dev/null
+++ b/src/engine/devcmd/cmds/PrintStatsCmd.java
@@ -0,0 +1,154 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.devcmd.cmds;
+
+import engine.devcmd.AbstractDevCmd;
+import engine.objects.*;
+
+import java.util.HashMap;
+
+/**
+ *
+ */
+
+public class PrintStatsCmd extends AbstractDevCmd {
+
+	public PrintStatsCmd() {
+		super("printstats");
+		//		super("printstats", MBServerStatics.ACCESS_LEVEL_ADMIN);
+	}
+
+	@Override
+	protected void _doCmd(PlayerCharacter pc, String[] words,
+			AbstractGameObject target) {
+
+		AbstractCharacter tar;
+
+		if (target != null && target instanceof AbstractCharacter) {
+			tar = (AbstractCharacter) target;
+
+			if (tar instanceof PlayerCharacter) {
+				printStatsPlayer(pc, (PlayerCharacter) tar);
+				this.setTarget(tar); //for logging
+			} else if (tar instanceof Mob)
+				printStatsMob(pc, (Mob) tar);
+			else if (tar instanceof NPC)
+				printStatsNPC(pc, (NPC) tar);
+		}
+	}
+
+	public void printStatsPlayer(PlayerCharacter pc, PlayerCharacter tar) {
+		String newline = "\r\n ";
+		String out = "Server stats for Player " + tar.getFirstName() + newline;
+		out += "Unused Stats: " + tar.getUnusedStatPoints() + newline;
+		out += "Stats Base (Modified)" + newline;
+        out += "  Str: " + (int) tar.statStrBase + " (" + tar.getStatStrCurrent() + ')' + ", maxStr: " + tar.getStrMax() + newline;
+		out += "  Dex: " + (int) tar.statDexBase + " (" + tar.getStatDexCurrent() + ')' + ", maxDex: " + tar.getDexMax() + newline;
+		out += "  Con: " + (int) tar.statConBase + " (" + tar.getStatConCurrent() + ')' + ", maxCon: " + tar.getConMax() + newline;
+		out += "  Int: " + (int) tar.statIntBase + " (" + tar.getStatIntCurrent() + ')' + ", maxInt: " + tar.getIntMax() + newline;
+		out += "  Spi: " + (int) tar.statSpiBase + " (" + tar.getStatSpiCurrent() + ')' + ", maxSpi: " + tar.getSpiMax() + newline;
+		throwbackInfo(pc, out);
+		out = "Health: " + tar.getHealth() + ", maxHealth: " + tar.getHealthMax() + newline;
+		out += "Mana: " + tar.getMana() + ", maxMana: " + tar.getManaMax() + newline;
+		out += "Stamina: " + tar.getStamina() + ", maxStamina: " + tar.getStaminaMax() + newline;
+		out += "Defense: " + tar.getDefenseRating() + newline;
+		out += "Main Hand: atr: " + tar.getAtrHandOne() + ", damage: " + tar.getMinDamageHandOne() + " to " + tar.getMaxDamageHandOne() + ", speed: " + tar.getSpeedHandOne() + newline;
+		out += "Off Hand:  atr: " + tar.getAtrHandTwo() + ", damage: " + tar.getMinDamageHandTwo() + " to " + tar.getMaxDamageHandTwo() + ", speed: " + tar.getSpeedHandTwo() + newline;
+		out += "isAlive: " + tar.isAlive() + ", Combat: " + tar.isCombat() + newline;
+		throwbackInfo(pc, out);
+	}
+
+	public void printStatsMob(PlayerCharacter pc, Mob tar) {
+		MobBase mb = tar.getMobBase();
+		if (mb == null)
+			return;
+
+
+
+		String newline = "\r\n ";
+		String out = "Server stats for Mob " + mb.getFirstName() + newline;
+		out += "Stats Base (Modified)" + newline;
+		out += "  Str: " + tar.getStatStrCurrent() + " (" + tar.getStatStrCurrent() + ')' + newline;
+		out += "  Dex: " + tar.getStatDexCurrent() + " (" + tar.getStatDexCurrent() + ')' + ", maxDex: " + tar.getStatDexCurrent() + newline;
+		out += "  Con: " + tar.getStatConCurrent() + " (" + tar.getStatConCurrent() + ')' + ", maxCon: " + tar.getStatConCurrent() + newline;
+		out += "  Int: " + tar.getStatIntCurrent() + " (" + tar.getStatIntCurrent() + ')' + ", maxInt: " + tar.getStatIntCurrent() + newline;
+		out += "  Spi: " + tar.getStatSpiCurrent() + " (" + tar.getStatSpiCurrent() + ')' + ", maxSpi: " + tar.getStatSpiCurrent() + newline;
+
+		out += "Health: " + tar.getHealth() + ", maxHealth: " + tar.getHealthMax() + newline;
+		out += "Mana: " + tar.getMana() + ", maxMana: " + tar.getManaMax() + newline;
+		out += "Stamina: " + tar.getStamina() + ", maxStamina: " + tar.getStaminaMax() + newline;
+		out += "Defense: " + tar.getDefenseRating() + newline;
+
+		//get weapons
+		HashMap<Integer, MobEquipment> equip = tar.getEquip();
+		ItemBase main =  null;
+
+		if (equip != null)
+			main = getWeaponBase(1, equip);
+		ItemBase off = null;
+
+		if (equip != null)
+			getWeaponBase(2, equip);
+		if (main == null && off == null) {
+			out += "Main Hand: atr: " + tar.getAtrHandOne() + ", damage: " + tar.getMinDamageHandOne() + " to " + tar.getMaxDamageHandOne() + ", speed: " + tar.getSpeedHandOne() + ", range: 6" + newline;
+		} else {
+			if (main != null)
+				out += "Main Hand: atr: " + tar.getAtrHandOne() + ", damage: " + tar.getMinDamageHandOne() + " to " + tar.getMaxDamageHandOne() + ", speed: " + tar.getSpeedHandOne() + ", range: " + main.getRange() + newline;
+			if (off != null)
+				out += "Main Hand: atr: " + tar.getAtrHandTwo() + ", damage: " + tar.getMinDamageHandTwo() + " to " + tar.getMaxDamageHandTwo() + ", speed: " + tar.getSpeedHandTwo() + ", range: " + off.getRange() + newline;
+		}
+		out += "isAlive: " + tar.isAlive() + ", Combat: " + tar.isCombat() + newline;
+
+		throwbackInfo(pc, out);
+	}
+
+	public void printStatsNPC(PlayerCharacter pc, NPC tar) {
+		Contract contract = tar.getContract();
+		if (contract == null)
+			return;
+
+		String newline = "\r\n ";
+
+		String name;
+		if (contract != null) {
+			if (contract.isTrainer())
+				name = tar.getName() + ", " + contract.getName();
+			else
+				name = tar.getName() + " the " + contract.getName();
+		} else
+			name = tar.getName();
+		String out = "Server stats for NPC " + name + newline;
+		out += "Sell Percent: " + tar.getSellPercent() + ", Buy Percent: " + tar.getBuyPercent() + newline;
+
+		throwbackInfo(pc, out);
+	}
+
+	public static ItemBase getWeaponBase(int slot, HashMap<Integer, MobEquipment> equip) {
+		if (equip.containsKey(slot)) {
+			MobEquipment item = equip.get(slot);
+			if (item != null && item.getItemBase() != null) {
+				return item.getItemBase();
+			}
+		}
+		return null;
+	}
+
+
+	@Override
+	protected String _getHelpString() {
+		return "Returns the player's current stats";
+	}
+
+	@Override
+	protected String _getUsageString() {
+		return "' /printstats'";
+	}
+
+}
diff --git a/src/engine/devcmd/cmds/PrintVaultCmd.java b/src/engine/devcmd/cmds/PrintVaultCmd.java
new file mode 100644
index 00000000..7ddc411d
--- /dev/null
+++ b/src/engine/devcmd/cmds/PrintVaultCmd.java
@@ -0,0 +1,72 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.devcmd.cmds;
+
+import engine.devcmd.AbstractDevCmd;
+import engine.objects.*;
+
+import java.util.ArrayList;
+
+public class PrintVaultCmd extends AbstractDevCmd {
+
+	public PrintVaultCmd() {
+		super("printvault");
+	}
+
+	@Override
+	protected void _doCmd(PlayerCharacter pc, String[] words,
+			AbstractGameObject target) {
+
+		AbstractWorldObject tar;
+		String name;
+		String type = "PlayerCharacter";
+		if (target != null) {
+			if (target instanceof AbstractCharacter) {
+				tar = (AbstractCharacter) target;
+				name = ((AbstractCharacter) tar).getFirstName();
+			} else {
+				tar = pc;
+				name = ((AbstractCharacter) tar).getFirstName();
+			}
+		} else {
+			tar = pc;
+			name = ((AbstractCharacter) tar).getFirstName();
+		}
+
+		if (!(tar instanceof PlayerCharacter)) {
+			throwbackError(pc, "Must target player");
+			return;
+		}
+
+
+		CharacterItemManager cim = ((AbstractCharacter)tar).getCharItemManager();
+		ArrayList<Item> list = cim.getVault();
+		throwbackInfo(pc, "Vault for " + type + ' ' + name + " (" + tar.getObjectUUID() + ')');
+		for (Item item : list) {
+			throwbackInfo(pc, "    " + item.getItemBase().getName() + ", count: " + item.getNumOfItems());
+		}
+		Item gold = cim.getGoldVault();
+		if (gold != null)
+			throwbackInfo(pc, "    Gold, count: " + gold.getNumOfItems());
+		else
+			throwbackInfo(pc, "    NULL Gold");
+	}
+
+	@Override
+	protected String _getHelpString() {
+		return  "Returns the player's current vault";
+	}
+
+	@Override
+	protected String _getUsageString() {
+		return  "' /printvault'";
+	}
+
+}
diff --git a/src/engine/devcmd/cmds/PullCmd.java b/src/engine/devcmd/cmds/PullCmd.java
new file mode 100644
index 00000000..16c4f4d4
--- /dev/null
+++ b/src/engine/devcmd/cmds/PullCmd.java
@@ -0,0 +1,104 @@
+package engine.devcmd.cmds;
+
+import engine.InterestManagement.WorldGrid;
+import engine.devcmd.AbstractDevCmd;
+import engine.gameManager.DbManager;
+import engine.gameManager.ZoneManager;
+import engine.math.Vector3fImmutable;
+import engine.objects.*;
+
+/**
+ *
+ * @author
+ * Dev command to move mobile and it's spawn location
+ * to the player's current location
+ */
+public class PullCmd extends AbstractDevCmd {
+
+	public PullCmd() {
+        super("pull");
+    }
+
+	@Override
+	protected void _doCmd(PlayerCharacter pcSender, String[] args,
+			AbstractGameObject target) {
+
+		Mob targetMobile;
+		Vector3fImmutable targetLoc;
+		Zone serverZone;
+
+		if (validateUserInput(pcSender, target, args) == false) {
+			this.sendUsage(pcSender);
+			return;
+		}
+
+		targetLoc = pcSender.getLoc();
+		serverZone = ZoneManager.findSmallestZone(targetLoc);
+		switch (target.getObjectType()) {
+		case Mob:
+			MoveMobile((Mob) target, pcSender, targetLoc, serverZone);
+			break;
+		case Building:
+			MoveBuilding((Building) target, pcSender, targetLoc, serverZone);
+			break;
+		case NPC:
+			MoveNPC((NPC) target, pcSender, targetLoc, serverZone);
+		}
+	}
+
+	@Override
+	protected String _getUsageString() {
+        return "/pull";
+	}
+
+	@Override
+	protected String _getHelpString() {
+        return "Moves mobile (and spawn) to player's location";
+	}
+
+	private boolean validateUserInput(PlayerCharacter pcSender, AbstractGameObject currTarget, String[] userInput) {
+
+		// No target
+		if (currTarget == null) {
+			throwbackError(pcSender, "Requires a Mobile be targeted");
+			return false;
+		}
+		return true;
+	}
+
+	private static void MoveMobile(Mob targetMobile, PlayerCharacter pcSender, Vector3fImmutable newLoc, Zone serverZone) {
+
+		Vector3fImmutable localCoords;
+
+		localCoords = ZoneManager.worldToLocal(newLoc, serverZone);
+
+		DbManager.MobQueries.MOVE_MOB(targetMobile.getObjectUUID(), serverZone.getObjectUUID(), localCoords.x, localCoords.y, localCoords.z);
+		targetMobile.setBindLoc(newLoc);
+		targetMobile.setLoc(newLoc);
+		targetMobile.refresh();
+	}
+
+	private static void MoveBuilding(Building targetBuilding, PlayerCharacter pcSender, Vector3fImmutable newLoc, Zone serverZone) {
+
+		Vector3fImmutable localCoords;
+
+		localCoords = ZoneManager.worldToLocal(newLoc, serverZone);
+
+		DbManager.BuildingQueries.MOVE_BUILDING(targetBuilding.getObjectUUID(), serverZone.getObjectUUID(), localCoords.x, localCoords.y, localCoords.z);
+		targetBuilding.setLoc(newLoc);
+		targetBuilding.getBounds().setBounds(targetBuilding);
+		targetBuilding.refresh(true);
+	}
+
+	private static void MoveNPC(NPC targetNPC, PlayerCharacter pcSender, Vector3fImmutable newLoc, Zone serverZone) {
+
+		Vector3fImmutable localCoords;
+
+		localCoords = ZoneManager.worldToLocal(newLoc, serverZone);
+
+		DbManager.NPCQueries.MOVE_NPC(targetNPC.getObjectUUID(), serverZone.getObjectUUID(), localCoords.x, localCoords.y, localCoords.z);
+		targetNPC.setBindLoc(newLoc);
+		targetNPC.setLoc(newLoc);
+		WorldGrid.updateObject(targetNPC, pcSender);
+	}
+}
diff --git a/src/engine/devcmd/cmds/PurgeObjectsCmd.java b/src/engine/devcmd/cmds/PurgeObjectsCmd.java
new file mode 100644
index 00000000..13545c84
--- /dev/null
+++ b/src/engine/devcmd/cmds/PurgeObjectsCmd.java
@@ -0,0 +1,315 @@
+package engine.devcmd.cmds;
+
+import engine.Enum;
+import engine.Enum.BuildingGroup;
+import engine.Enum.GameObjectType;
+import engine.InterestManagement.WorldGrid;
+import engine.devcmd.AbstractDevCmd;
+import engine.gameManager.BuildingManager;
+import engine.gameManager.DbManager;
+import engine.gameManager.ZoneManager;
+import engine.math.Vector3fImmutable;
+import engine.objects.*;
+import engine.server.MBServerStatics;
+import org.pmw.tinylog.Logger;
+
+import java.util.HashSet;
+import java.util.concurrent.locks.ReadWriteLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+/**
+ * @author
+ * Summary: Game designer utility command to purge all
+        objects of a given type within a supplied range
+ */
+
+public class PurgeObjectsCmd extends AbstractDevCmd {
+
+	// Instance variables
+
+	private Vector3fImmutable _currentLocation;
+	private float _targetRange;
+	private int _targetMask;
+
+	// Concurrency support
+
+	private ReadWriteLock lock = new ReentrantReadWriteLock();
+
+	// Constructor
+
+	public PurgeObjectsCmd() {
+        super("purge");
+    }
+
+    private static void PurgeWalls(Zone zone, PlayerCharacter pc){
+
+        if (!zone.isPlayerCity())
+            return;
+
+        for (Building building: zone.zoneBuildingSet){
+            if (!BuildingManager.IsWallPiece(building))
+                continue;
+            for (AbstractCharacter ac: building.getHirelings().keySet()){
+                NPC npc = null;
+                Mob mobA = null;
+
+                if (ac.getObjectType() == GameObjectType.NPC)
+                    npc = (NPC)ac;
+                else if (ac.getObjectType() == GameObjectType.Mob)
+                    mobA = (Mob)ac;
+
+
+
+                if (npc != null){
+                    for (Mob mob: npc.getSiegeMinionMap().keySet()){
+                        WorldGrid.RemoveWorldObject(mob);
+                        WorldGrid.removeObject(mob, pc);
+                        //Mob.getRespawnMap().remove(mob);
+                        if (mob.getParentZone() != null)
+                            mob.getParentZone().zoneMobSet.remove(mob);
+                    }
+                    DbManager.NPCQueries.DELETE_NPC(npc);
+                    DbManager.removeFromCache(GameObjectType.NPC,
+                            npc.getObjectUUID());
+                    WorldGrid.RemoveWorldObject(npc);
+                }else if (mobA != null){
+                    for (Mob mob: mobA.getSiegeMinionMap().keySet()){
+                        WorldGrid.RemoveWorldObject(mob);
+                        WorldGrid.removeObject(mob, pc);
+                        //Mob.getRespawnMap().remove(mob);
+                        if (mob.getParentZone() != null)
+                            mob.getParentZone().zoneMobSet.remove(mob);
+                    }
+                    DbManager.MobQueries.DELETE_MOB(mobA);
+                    DbManager.removeFromCache(GameObjectType.Mob,
+                            mobA.getObjectUUID());
+                    WorldGrid.RemoveWorldObject(mobA);
+                }
+
+            }
+
+
+            DbManager.BuildingQueries.DELETE_FROM_DATABASE(building);
+            DbManager.removeFromCache(building);
+            WorldGrid.RemoveWorldObject(building);
+            WorldGrid.removeObject(building, pc);
+        }
+
+    }
+
+
+    // AbstractDevCmd Overridden methods
+
+	@Override
+	protected void _doCmd(PlayerCharacter pc, String[] args,
+			AbstractGameObject target) {
+
+		// Grab write lock due to use of instance variables
+
+		lock.writeLock().lock();
+
+		try {
+			
+			if (args[0].toLowerCase().equals("walls")){
+				Zone zone = ZoneManager.findSmallestZone(pc.getLoc());
+				
+				PurgeWalls(zone, pc);
+				return;
+			}
+
+			if(validateUserInput(args) == false) {
+				this.sendUsage(pc);
+				return;
+			}
+
+			parseUserInput(args);
+
+			// Arguments have been validated and parsed at this point
+			// Build array of requested objects
+
+			_currentLocation = pc.getLoc();
+
+			HashSet<AbstractWorldObject> objectList =
+					WorldGrid.getObjectsInRangePartial(_currentLocation, _targetRange, _targetMask);
+
+			// Iterate through array and remove objects from game world and database
+
+			for (AbstractWorldObject awo : objectList) {
+
+				switch(awo.getObjectType()) {
+				case Building:
+					removeBuilding(pc, (Building) awo);
+					break;
+				case NPC:
+					removeNPC(pc, (NPC) awo);
+					break;
+				case Mob:
+					removeMob(pc, (Mob) awo);
+					break;
+				default:
+					break;
+				}
+			}
+
+			// Send results to user
+			throwbackInfo(pc, "Purge: " + objectList.size() + " objects were removed in range " + _targetRange);
+		}catch(Exception e){
+			Logger.error(e);
+		}
+
+		// Release Reentrant lock
+
+		finally {
+			lock.writeLock().unlock();
+		}
+	}
+
+	@Override
+	protected String _getHelpString() {
+        return "Purges game objects within range";
+	}
+
+	@Override
+	protected String _getUsageString() {
+        return "/purge [npc|mob|mesh|all] [range <= 200]";
+	}
+
+	// Class methods
+
+	private static boolean validateUserInput(String[] userInput) {
+
+		int stringIndex;
+		String commandSet = "npcmobmeshall";
+
+		// incorrect number of arguments test
+
+		if (userInput.length != 2)
+			return false;
+
+		// Test of game object type argument
+
+		stringIndex = commandSet.indexOf(userInput[0].toLowerCase());
+
+		if (stringIndex == -1)
+			return false;
+
+		// Test if range argument can convert to a float
+
+		try {
+			Float.parseFloat(userInput[1]); }
+		catch (NumberFormatException | NullPointerException e) {
+			return false;
+		}
+
+		// User input passes validation
+
+		return true;
+	}
+
+	private void parseUserInput(String[] userInput) {
+
+		_targetMask = 0;
+		_targetRange = 0f;
+
+		// Build mask from user input
+
+		switch (userInput[0].toLowerCase()) {
+		case "npc":
+			_targetMask = MBServerStatics.MASK_NPC;
+			break;
+		case "mob":
+			_targetMask = MBServerStatics.MASK_MOB;
+			break;
+		case "mesh":
+			_targetMask = MBServerStatics.MASK_BUILDING;
+			break;
+		case "all":
+			_targetMask = MBServerStatics.MASK_NPC | MBServerStatics.MASK_MOB | MBServerStatics.MASK_BUILDING;
+			break;
+		default:
+			break;
+		}
+
+		// Parse second argument into range parameter. Cap at 200 units.
+
+		_targetRange = Float.parseFloat(userInput[1]);
+		_targetRange = Math.min(_targetRange, 200f);
+	}
+
+	private static void removeBuilding(PlayerCharacter pc, Building building) {
+
+		if ((building.getBlueprintUUID() != 0) &&
+				(building.getBlueprint().getBuildingGroup() == BuildingGroup.TOL))
+			return;
+		if ((building.getBlueprintUUID() != 0) &&
+				(building.getBlueprint().getBuildingGroup() == BuildingGroup.SHRINE))
+			Shrine.RemoveShrineFromCacheByBuilding(building);
+
+		if ((building.getBlueprint() != null) && (building.getBlueprint().getBuildingGroup() == BuildingGroup.SPIRE))
+			building.disableSpire(false);
+
+		for (AbstractCharacter ac: building.getHirelings().keySet()){
+			NPC npc = null;
+			Mob mobA = null;
+
+			if (ac.getObjectType() == GameObjectType.NPC)
+				npc = (NPC)ac;
+			else if (ac.getObjectType() == GameObjectType.Mob)
+				mobA = (Mob)ac;
+
+
+
+			if (npc != null){
+				for (Mob mob: npc.getSiegeMinionMap().keySet()){
+					WorldGrid.RemoveWorldObject(mob);
+					WorldGrid.removeObject(mob, pc);
+					//Mob.getRespawnMap().remove(mob);
+					if (mob.getParentZone() != null)
+						mob.getParentZone().zoneMobSet.remove(mob);
+				}
+				DbManager.NPCQueries.DELETE_NPC(npc);
+				DbManager.removeFromCache(Enum.GameObjectType.NPC,
+						npc.getObjectUUID());
+				WorldGrid.RemoveWorldObject(npc);
+			}else if (mobA != null){
+				for (Mob mob: mobA.getSiegeMinionMap().keySet()){
+					WorldGrid.RemoveWorldObject(mob);
+					WorldGrid.removeObject(mob, pc);
+					//Mob.getRespawnMap().remove(mob);
+					if (mob.getParentZone() != null)
+						mob.getParentZone().zoneMobSet.remove(mob);
+				}
+				DbManager.MobQueries.DELETE_MOB(mobA);
+				DbManager.removeFromCache(Enum.GameObjectType.Mob,
+						mobA.getObjectUUID());
+				WorldGrid.RemoveWorldObject(mobA);
+			}
+
+		}
+
+
+		DbManager.BuildingQueries.DELETE_FROM_DATABASE(building);
+		DbManager.removeFromCache(building);
+		WorldGrid.RemoveWorldObject(building);
+		WorldGrid.removeObject(building, pc);
+	}
+
+	private static void removeNPC(PlayerCharacter pc, NPC npc) {
+		DbManager.NPCQueries.DELETE_NPC(npc);
+		DbManager.removeFromCache(npc);
+		WorldGrid.RemoveWorldObject(npc);
+		WorldGrid.removeObject(npc, pc);
+	}
+
+	private static void removeMob(PlayerCharacter pc, Mob mob) {
+		mob.setLoc(Vector3fImmutable.ZERO);	//Move it off the plane..
+		mob.setBindLoc(Vector3fImmutable.ZERO);	//Reset the bind loc..
+		//mob.setHealth(-1, pc); //Kill it!
+
+		DbManager.MobQueries.DELETE_MOB(mob);
+		DbManager.removeFromCache(mob);
+		WorldGrid.RemoveWorldObject(mob);
+		WorldGrid.removeObject(mob, pc);
+	}
+
+}
diff --git a/src/engine/devcmd/cmds/RealmInfoCmd.java b/src/engine/devcmd/cmds/RealmInfoCmd.java
new file mode 100644
index 00000000..9ee6b695
--- /dev/null
+++ b/src/engine/devcmd/cmds/RealmInfoCmd.java
@@ -0,0 +1,92 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+
+package engine.devcmd.cmds;
+
+
+import engine.InterestManagement.RealmMap;
+import engine.devcmd.AbstractDevCmd;
+import engine.gameManager.ZoneManager;
+import engine.objects.AbstractGameObject;
+import engine.objects.PlayerCharacter;
+import engine.objects.Realm;
+import engine.objects.Zone;
+
+public class RealmInfoCmd extends AbstractDevCmd {
+
+	public RealmInfoCmd() {
+        super("realminfo");
+    }
+
+	@Override
+	protected void _doCmd(PlayerCharacter pc, String[] words,
+			AbstractGameObject target) {
+
+		Zone serverZone;
+		Zone parentZone;
+		Realm serverRealm;
+		int realmID;
+		String outString = "";
+
+		if (pc == null) {
+			throwbackError(pc, "Unable to find the pc making the request.");
+			return;
+		}
+
+		serverZone = ZoneManager.findSmallestZone(pc.getLoc());
+
+		if (serverZone == null) {
+			throwbackError(pc, "Zone not found");
+			return;
+		}
+
+		parentZone = serverZone.getParent();
+
+		realmID = RealmMap.getRealmIDAtLocation(pc.getLoc());
+
+		String newline = "\r\n ";
+
+		outString = newline;
+		outString += "RealmID: " + realmID;
+
+		serverRealm = Realm.getRealm(realmID);
+
+		if (serverRealm == null)
+			outString += " Name: SeaFloor";
+		else
+			outString += serverRealm.getRealmName();
+
+		outString += newline;
+
+		outString += " Zone: " + serverZone.getName();
+
+		outString += newline;
+
+		if (serverZone.getParent() != null)
+			outString += " Parent: " + serverZone.getParent().getName();
+		else
+			outString += "Parent: NONE";
+
+		outString += newline;
+
+		throwbackInfo(pc, outString);
+	}
+
+	@Override
+	protected String _getHelpString() {
+        return "Returns info on realm.";
+	}
+
+	@Override
+	protected String _getUsageString() {
+        return "' /info targetID'";
+	}
+
+}
diff --git a/src/engine/devcmd/cmds/RebootCmd.java b/src/engine/devcmd/cmds/RebootCmd.java
new file mode 100644
index 00000000..2fe01c44
--- /dev/null
+++ b/src/engine/devcmd/cmds/RebootCmd.java
@@ -0,0 +1,48 @@
+package engine.devcmd.cmds;
+
+import engine.devcmd.AbstractDevCmd;
+import engine.objects.AbstractGameObject;
+import engine.objects.PlayerCharacter;
+import org.pmw.tinylog.Logger;
+
+/**
+ * @author
+ * Summary: Devcmd to reboot server
+ *
+ */
+
+public class RebootCmd extends AbstractDevCmd {
+
+	// Instance variables
+
+	public RebootCmd() {
+        super("reboot");
+    }
+
+
+	// AbstractDevCmd Overridden methods
+
+	@Override
+	protected void _doCmd(PlayerCharacter pc, String[] args,
+			AbstractGameObject target) {
+
+		try {
+			Runtime rt = Runtime.getRuntime();
+			rt.exec("./mbrestart.sh");
+		} catch (java.io.IOException err) {
+			Logger.info( err.getMessage());
+		}
+
+	}
+
+	@Override
+	protected String _getHelpString() {
+        return "Reboot server";
+	}
+
+	@Override
+	protected String _getUsageString() {
+        return "./reboot";
+	}
+
+}
diff --git a/src/engine/devcmd/cmds/RegionCmd.java b/src/engine/devcmd/cmds/RegionCmd.java
new file mode 100644
index 00000000..48fd5390
--- /dev/null
+++ b/src/engine/devcmd/cmds/RegionCmd.java
@@ -0,0 +1,79 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+
+package engine.devcmd.cmds;
+
+import engine.devcmd.AbstractDevCmd;
+import engine.objects.AbstractGameObject;
+import engine.objects.PlayerCharacter;
+
+import java.lang.reflect.Field;
+
+public class RegionCmd extends AbstractDevCmd {
+
+	public RegionCmd() {
+        super("region");
+    }
+
+	@Override
+	protected void _doCmd(PlayerCharacter pc, String[] words,
+			AbstractGameObject target) {
+
+
+	if (pc.getRegion() == null){
+		this.throwbackInfo(pc, "No Region Found.");
+		return;
+	}
+	
+	
+	  String newLine = System.getProperty("line.separator");
+	  String result = "";
+	  result+=(pc.getRegion().getClass().getSimpleName());
+	    result+=( " {" );
+	    result+=(newLine);
+	 Field[] fields = pc.getRegion().getClass().getDeclaredFields();
+
+	  //print field names paired with their values
+	  for ( Field field : fields  ) {
+		  field.setAccessible(true);
+	    result+=(" ");
+	    try {
+	    	
+	    	if(field.getName().contains("Furniture"))
+	    		continue;
+	      result+=( field.getName());
+	      result+=(": ");
+	      //requires access to private field:
+	      result+=( field.get(pc.getRegion()).toString());
+	    } catch ( IllegalAccessException ex ) {
+	      System.out.println(ex);
+	    }
+	    result.trim();
+	    result+=(newLine);
+	  }
+	  result+=("}");
+	
+	this.throwbackInfo(pc, result.toString());
+
+
+	}
+
+	@Override
+	protected String _getHelpString() {
+		return "Temporarily Changes SubRace";
+	}
+
+	@Override
+	protected String _getUsageString() {
+		return "' /setBuildingCollidables add/remove 'add creates a collision line.' needs 4 integers. startX, endX, startY, endY";
+
+	}
+
+}
diff --git a/src/engine/devcmd/cmds/RemoveBaneCmd.java b/src/engine/devcmd/cmds/RemoveBaneCmd.java
new file mode 100644
index 00000000..98de9f90
--- /dev/null
+++ b/src/engine/devcmd/cmds/RemoveBaneCmd.java
@@ -0,0 +1,70 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.devcmd.cmds;
+
+import engine.Enum.SiegeResult;
+import engine.devcmd.AbstractDevCmd;
+import engine.gameManager.ZoneManager;
+import engine.objects.*;
+
+/**
+ * @author Eighty
+ *
+ */
+public class RemoveBaneCmd extends AbstractDevCmd {
+
+	public RemoveBaneCmd() {
+        super("removebane");
+    }
+
+	@Override
+	protected void _doCmd(PlayerCharacter pc, String[] words,
+			AbstractGameObject target) {
+
+		Zone zone = ZoneManager.findSmallestZone(pc.getLoc());
+
+		if (zone == null) {
+			throwbackError(pc, "Unable to find the zone you're in.");
+			return;
+		}
+
+		if (!zone.isPlayerCity()) {
+			throwbackError(pc, "This is not a player city.");
+			return;
+		}
+
+		City city = City.getCity(zone.getPlayerCityUUID());
+		if (city == null) {
+			throwbackError(pc, "Unable to find the city associated with this zone.");
+			return;
+		}
+
+		Bane bane = city.getBane();
+		if (bane == null) {
+			throwbackError(pc, "Could not find bane to remove.");
+			return;
+		}
+
+		bane.endBane(SiegeResult.DEFEND);
+
+		throwbackInfo(pc, "The bane has been removed.");
+	}
+
+	@Override
+	protected String _getHelpString() {
+        return "Removes a bane from the city grid you're standing on.";
+	}
+
+	@Override
+	protected String _getUsageString() {
+        return "'./removebane'";
+	}
+
+}
diff --git a/src/engine/devcmd/cmds/RemoveObjectCmd.java b/src/engine/devcmd/cmds/RemoveObjectCmd.java
new file mode 100644
index 00000000..5b176002
--- /dev/null
+++ b/src/engine/devcmd/cmds/RemoveObjectCmd.java
@@ -0,0 +1,253 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.devcmd.cmds;
+
+import engine.Enum.BuildingGroup;
+import engine.Enum.DbObjectType;
+import engine.Enum.GameObjectType;
+import engine.InterestManagement.WorldGrid;
+import engine.devcmd.AbstractDevCmd;
+import engine.gameManager.BuildingManager;
+import engine.gameManager.ChatManager;
+import engine.gameManager.DbManager;
+import engine.gameManager.ZoneManager;
+import engine.math.Vector3fImmutable;
+import engine.objects.*;
+
+/**
+ *
+ */
+public class RemoveObjectCmd extends AbstractDevCmd {
+
+	public RemoveObjectCmd() {
+		//set to Player access level so it can be run by non-admins on production.
+		//Actual access level is set in _doCmd.
+		super("remove");
+		this.addCmdString("delete");
+	}
+
+	@Override
+	protected void _doCmd(PlayerCharacter player, String[] words, AbstractGameObject target) {
+
+		int targetID;
+		DbObjectType targetObjType;
+		Building targetBuilding;
+		NPC targetNPC;
+		Mob targetMob;
+
+		if (target == null && words.length != 1) {
+			this.sendUsage(player);
+			return;
+		}
+
+		// Delete the targeted building
+
+		if (target != null) {
+
+			switch (target.getObjectType()) {
+
+			case Building:
+				removeBuilding(player, (Building) target);
+				break;
+			case NPC:
+				removeNPC(player, (NPC) target);
+				break;
+			case Mob:
+				removeMob(player, (Mob) target);
+				break;
+			default:
+				throwbackError(player, "Target " + target.getObjectType()
+				+ " is not a valid object type");
+				break;
+			}
+			return;
+		}
+
+		// Attempt to delete object based upon parsed UUID from input
+
+		// Parse Target UUID
+
+		try {
+			targetID = Integer.parseInt(words[0]);
+		} catch (NumberFormatException e) {
+			throwbackError(player, "Supplied object ID " + words[0]
+					+ " failed to parse to an Integer");
+			return;
+		}
+
+		// Determine object type of given UUID
+
+		targetObjType = DbManager.BuildingQueries.GET_UID_ENUM(targetID);
+
+		// Process accordingly
+
+		switch (targetObjType) {
+		case BUILDING:
+			targetBuilding = BuildingManager.getBuilding(targetID);
+			removeBuilding(player, targetBuilding);
+			break;
+		case NPC:
+			targetNPC = NPC.getNPC(targetID);
+			removeNPC(player, targetNPC);
+			break;
+		case MOB:
+			targetMob = Mob.getMob(targetID);
+			removeMob(player, targetMob);
+			break;
+		default:
+			throwbackError(player, "Invalid UUID: Not found in database");
+			break;
+		}
+
+	}
+
+	@Override
+	protected String _getHelpString() {
+        return "Removes targeted or specified object";
+	}
+
+	@Override
+	protected String _getUsageString() {
+        return "' /remove [objectID]' || ' /delete [objectID]'";
+	}
+
+	private void removeBuilding(PlayerCharacter pc, Building building) {
+
+		Zone currentZone = ZoneManager.findSmallestZone(pc.getLoc());
+
+		if (currentZone == null) {
+			this.throwbackError(pc, "Could not locate zone for player.");
+			return;
+		}
+
+		if ((building.getBlueprint() != null) && (building.getBlueprint().getBuildingGroup() == BuildingGroup.SPIRE))
+			building.disableSpire(false);
+
+		if ((building.getBlueprint() != null) && (building.getBlueprint().getBuildingGroup() == BuildingGroup.WAREHOUSE)){
+			City city =City.getCity(building.getParentZone().getPlayerCityUUID());
+			if (city != null){
+				city.setWarehouseBuildingID(0);
+			}
+			Warehouse.warehouseByBuildingUUID.remove(building.getObjectUUID());
+		}
+
+
+		//remove cached shrines.
+		if ((building.getBlueprintUUID() != 0)
+				&& (building.getBlueprint().getBuildingGroup() == BuildingGroup.SHRINE))
+			Shrine.RemoveShrineFromCacheByBuilding(building);
+
+		for (AbstractCharacter ac : building.getHirelings().keySet()) {
+			NPC npc = null;
+			Mob mobA = null;
+
+			if (ac.getObjectType() == GameObjectType.NPC)
+				npc = (NPC)ac;
+			else if (ac.getObjectType() == GameObjectType.Mob)
+				mobA = (Mob)ac;
+
+			if (npc != null){
+				for (Mob mob : npc.getSiegeMinionMap().keySet()) {
+					WorldGrid.RemoveWorldObject(mob);
+					WorldGrid.removeObject(mob, pc);
+					//Mob.getRespawnMap().remove(mob);
+					if (mob.getParentZone() != null)
+						mob.getParentZone().zoneMobSet.remove(mob);
+				}
+				DbManager.NPCQueries.DELETE_NPC(npc);
+				DbManager.removeFromCache(npc);
+				WorldGrid.RemoveWorldObject(npc);
+				WorldGrid.removeObject(npc, pc);
+			}else if (mobA != null){
+				for (Mob mob : mobA.getSiegeMinionMap().keySet()) {
+					WorldGrid.RemoveWorldObject(mob);
+					WorldGrid.removeObject(mob, pc);
+					//Mob.getRespawnMap().remove(mob);
+					if (mob.getParentZone() != null)
+						mob.getParentZone().zoneMobSet.remove(mob);
+				}
+				DbManager.MobQueries.DELETE_MOB(mobA);
+				DbManager.removeFromCache(mobA);
+				WorldGrid.RemoveWorldObject(mobA);
+				WorldGrid.removeObject(mobA, pc);
+			}
+
+
+		}
+		Zone zone = building.getParentZone();
+		DbManager.BuildingQueries.DELETE_FROM_DATABASE(building);
+		DbManager.removeFromCache(building);
+		zone.zoneBuildingSet.remove(building);
+		WorldGrid.RemoveWorldObject(building);
+		WorldGrid.removeObject(building, pc);
+
+		ChatManager.chatSayInfo(pc,
+				"Building with ID " + building.getObjectUUID() + " removed");
+		this.setResult(String.valueOf(building.getObjectUUID()));
+	}
+
+	private void removeNPC(PlayerCharacter pc, NPC npc) {
+
+		Zone currentZone = ZoneManager.findSmallestZone(pc.getLoc());
+		if (currentZone == null) {
+			this.throwbackError(pc, "Could not locate zone for player.");
+			return;
+		}
+
+
+
+		for (Mob mob : npc.getSiegeMinionMap().keySet()) {
+			WorldGrid.RemoveWorldObject(mob);
+			WorldGrid.removeObject(mob, pc);
+			if (mob.getParentZone() != null)
+				mob.getParentZone().zoneMobSet.remove(mob);
+		}
+
+		DbManager.NPCQueries.DELETE_NPC(npc);
+		DbManager.removeFromCache(npc);
+		WorldGrid.RemoveWorldObject(npc);
+		WorldGrid.removeObject(npc, pc);
+		ChatManager.chatSayInfo(pc,
+				"NPC with ID " + npc.getDBID() + " removed");
+		this.setResult(String.valueOf(npc.getDBID()));
+	}
+
+	private void removeMob(PlayerCharacter pc, Mob mob) {
+
+		Zone currentZone = ZoneManager.findSmallestZone(pc.getLoc());
+		if (currentZone == null) {
+			this.throwbackError(pc, "Could not locate zone for player.");
+			return;
+		}
+
+		if (mob.getParentZone() != null && mob.getParentZone() != currentZone && !mob.isPet() && !mob.isNecroPet()) {
+			this.throwbackError(pc, "Error 376954: Could not Remove Mob.Mob is not in the same zone as player.");
+			return;
+		}
+
+		mob.setLoc(Vector3fImmutable.ZERO);	//Move it off the plane..
+		mob.setBindLoc(Vector3fImmutable.ZERO);	//Reset the bind loc..
+		//mob.setHealth(-1, pc); //Kill it!
+
+		DbManager.MobQueries.DELETE_MOB(mob);
+
+		DbManager.removeFromCache(mob);
+		WorldGrid.RemoveWorldObject(mob);
+		WorldGrid.removeObject(mob, pc);
+
+		if (mob.getParentZone() != null)
+			mob.getParentZone().zoneMobSet.remove(mob);
+
+		ChatManager.chatSayInfo(pc,
+				"Mob with ID " + mob.getDBID() + " removed");
+		this.setResult(String.valueOf(mob.getDBID()));
+	}
+
+}
diff --git a/src/engine/devcmd/cmds/RenameCmd.java b/src/engine/devcmd/cmds/RenameCmd.java
new file mode 100644
index 00000000..73db6a24
--- /dev/null
+++ b/src/engine/devcmd/cmds/RenameCmd.java
@@ -0,0 +1,81 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.devcmd.cmds;
+
+import engine.InterestManagement.WorldGrid;
+import engine.devcmd.AbstractDevCmd;
+import engine.gameManager.DbManager;
+import engine.objects.AbstractGameObject;
+import engine.objects.Building;
+import engine.objects.NPC;
+import engine.objects.PlayerCharacter;
+
+/**
+ *
+ * @author Eighty
+ *
+ */
+public class RenameCmd extends AbstractDevCmd {
+
+	public RenameCmd() {
+        super("rename");
+    }
+
+	@Override
+	protected void _doCmd(PlayerCharacter pcSender, String[] args,
+			AbstractGameObject target) {
+		if (args.length < 1) {
+			this.sendUsage(pcSender);
+			return;
+		}
+		if (args[0].isEmpty()) {
+			throwbackError(pcSender, "Invalid rename Command. must specify a name.");
+			return;
+		}
+		NPC npc = null;
+		Building building = null;
+		
+		if (target != null) {
+			if (target instanceof NPC)
+				npc = (NPC) target;
+			else if (target instanceof Building)
+				building = (Building)target;
+		} else
+			npc = getTargetAsNPC(pcSender);
+		if (npc != null) {
+			DbManager.NPCQueries.SET_PROPERTY(npc, "npc_name", args[0]);
+			String name = args[0];
+			name = name.replaceAll("_", " ");
+			
+			npc.setName(name);
+
+			this.setResult(String.valueOf(npc.getDBID()));
+
+//			npc.updateDatabase();
+			WorldGrid.updateObject(npc, pcSender);
+		} else if (building != null){
+			String name = args[0];
+			name = name.replaceAll("_", " ");
+			building.setName(name);
+		}
+			throwbackError(pcSender, "Invalid rename Command. must target an npc.");
+	}
+
+	@Override
+	protected String _getUsageString() {
+        return "' /rename npcName'";
+	}
+
+	@Override
+	protected String _getHelpString() {
+        return "Renames an NPC.";
+	}
+
+}
diff --git a/src/engine/devcmd/cmds/RenameMobCmd.java b/src/engine/devcmd/cmds/RenameMobCmd.java
new file mode 100644
index 00000000..0e0dee1f
--- /dev/null
+++ b/src/engine/devcmd/cmds/RenameMobCmd.java
@@ -0,0 +1,101 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.devcmd.cmds;
+
+import engine.devcmd.AbstractDevCmd;
+import engine.gameManager.ChatManager;
+import engine.objects.AbstractGameObject;
+import engine.objects.MobBase;
+import engine.objects.NPC;
+import engine.objects.PlayerCharacter;
+
+/**
+ *
+ * @author Eighty
+ *
+ */
+public class RenameMobCmd extends AbstractDevCmd {
+
+	public RenameMobCmd() {
+        super("renamemob");
+    }
+
+	@Override
+	protected void _doCmd(PlayerCharacter pcSender, String[] args,
+			AbstractGameObject target) {
+		if (args.length < 1) {
+			this.sendUsage(pcSender);
+			return;
+		}
+		int loadID = 0;
+		String name = "";
+		NPC npc;
+		if (target != null && target instanceof NPC)
+			npc = (NPC) target;
+		else
+			npc = getTargetAsNPC(pcSender);
+		if (npc != null) {
+			for (int i = 0; i < args.length; i++) {
+				name += args[i];
+				if (i + 1 < args.length)
+					name += " ";
+			}
+			npc.setName(name);
+			npc.updateDatabase();
+			ChatManager.chatSayInfo(
+					pcSender,
+					"NPC with ID " + npc.getObjectUUID() + " renamed to "
+							+ npc.getFirstName());
+		} else {
+			try {
+				loadID = Integer.parseInt(args[0]);
+				if (args.length > 1) {
+					for (int i = 1; i < args.length; i++) {
+						name += args[i];
+						if (i + 1 < args.length)
+							name += " ";
+					}
+				}
+			} catch (Exception e) {
+				throwbackError(pcSender,
+						"Invalid renameMob Command. Need mob ID specified.");
+				return; // NaN
+			}
+			MobBase mob = MobBase.getMobBase(loadID);
+			if (mob == null) {
+				throwbackError(pcSender,
+						"Invalid renameMob Command. Mob ID specified is not valid.");
+				return;
+			}
+			if (!MobBase.renameMobBase(mob.getObjectUUID(), name)) {
+				throwbackError(pcSender,
+						"renameMob SQL Error. Failed to rename mob.");
+				return;
+			}
+			mob = MobBase.getMobBase(mob.getObjectUUID(), true); // force refresh
+																// from db
+			ChatManager.chatSayInfo(
+					pcSender,
+					"MobBase with ID " + mob.getObjectUUID() + " renamed to "
+							+ mob.getFirstName());
+		}
+	}
+
+	@Override
+	protected String _getUsageString() {
+        return "' /renamemob [ID] newName'";
+	}
+
+	@Override
+	protected String _getHelpString() {
+        return "Changes a mobs old name to a new name";
+	}
+
+}
diff --git a/src/engine/devcmd/cmds/ResetLevelCmd.java b/src/engine/devcmd/cmds/ResetLevelCmd.java
new file mode 100644
index 00000000..ef3dc496
--- /dev/null
+++ b/src/engine/devcmd/cmds/ResetLevelCmd.java
@@ -0,0 +1,36 @@
+package engine.devcmd.cmds;
+
+import engine.devcmd.AbstractDevCmd;
+import engine.objects.AbstractGameObject;
+import engine.objects.PlayerCharacter;
+
+public class ResetLevelCmd extends AbstractDevCmd {
+      
+	public ResetLevelCmd() {
+        super("resetlevel");
+    }
+
+        // AbstractDevCmd Overridden methods
+        
+	@Override
+	protected void _doCmd(PlayerCharacter player, String[] args,
+			AbstractGameObject target)  {
+       
+          player.ResetLevel(Short.parseShort(args[0]));
+        }
+
+	@Override
+	protected String _getHelpString() {
+        return "Resets character level to `level`. All training points are reset. Player must relog for changes to update.";
+	}
+
+	@Override
+	protected String _getUsageString() {
+
+
+        return "/resetlevel <level>";
+	}
+
+ 
+        
+}
diff --git a/src/engine/devcmd/cmds/RotateCmd.java b/src/engine/devcmd/cmds/RotateCmd.java
new file mode 100644
index 00000000..c6dc7c73
--- /dev/null
+++ b/src/engine/devcmd/cmds/RotateCmd.java
@@ -0,0 +1,251 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.devcmd.cmds;
+
+import engine.InterestManagement.WorldGrid;
+import engine.devcmd.AbstractDevCmd;
+import engine.gameManager.BuildingManager;
+import engine.gameManager.ChatManager;
+import engine.gameManager.DbManager;
+import engine.math.Vector3f;
+import engine.math.Vector3fImmutable;
+import engine.objects.*;
+
+public class RotateCmd extends AbstractDevCmd {
+
+	public RotateCmd() {
+        super("rotate");
+        this.addCmdString("rot");
+    }
+
+	@Override
+	protected void _doCmd(PlayerCharacter pc, String[] words,
+			AbstractGameObject target) {
+		if (target == null && (words.length != 2) ) {
+			this.sendUsage(pc);
+			return;
+		}
+
+
+		if (words.length == 3){
+			try{
+
+			}catch(Exception e){
+
+			}
+		}
+
+
+		float rot;
+		if (target != null && words.length == 1) {
+
+			try {
+				if (words[0].equalsIgnoreCase("face")){
+					this.rotateFace(pc, target);
+					return;
+				}
+
+				rot = Float.parseFloat(words[0]);
+			} catch (NumberFormatException e) {
+				throwbackError(pc, "Supplied rotation " + words[0]
+						+ " failed to parse to a Float");
+				return;
+			} catch (Exception e) {
+				throwbackError(pc,
+						"Invalid Rotate Command.  Need Rotation specified.");
+				return;
+			}
+
+			Vector3f rotation = new Vector3f(0f, rot, 0f);
+
+			if (target instanceof Building)
+				rotateBuilding(pc, (Building) target, rotation, rot,false);
+			else if (target instanceof NPC)
+				rotateNPC(pc, (NPC) target, rotation,false);
+			else if (target instanceof Mob)
+				rotateMob(pc, (Mob) target, rotation,false);
+			else
+				throwbackError(pc, "Target " + target.getObjectType()
+				+ " is not a valid object type");
+		} else {
+
+			int id = 0;
+			if (words.length == 2) {
+				try {
+					id = Integer.parseInt(words[0]);
+
+					if (words[1].equalsIgnoreCase("face")){
+
+						Building b;
+						if (id != 0)
+							b = BuildingManager.getBuilding(id);
+						else
+							b = getTargetAsBuilding(pc);
+						if (b != null) {
+							rotateFace(pc, b);
+							return;
+						}
+
+						// building failed, try npc
+						NPC npc;
+						if (id != 0)
+							npc = NPC.getNPC(id);
+						else
+							npc = getTargetAsNPC(pc);
+						if (npc != null) {
+							rotateFace(pc, npc);
+							return;
+						}
+
+						// NPC failed, try mob
+						Mob mob;
+						if (id != 0)
+							mob = Mob.getMob(id);
+						else
+							mob = getTargetAsMob(pc);
+						if (mob != null) {
+							rotateFace(pc, mob);
+							return;
+						}
+						throwbackError(pc, "Nothing found to rotate.");
+						return;
+					}
+					rot = Float.parseFloat(words[1]);
+				} catch (NumberFormatException e) {
+					throwbackError(pc, "Supplied arguments " + words[0] + ' '
+							+ words[1] + " failed to parse");
+					return;
+				} catch (Exception e) {
+					throwbackError(pc,
+							"Invalid Rotate Command. Need Rotation specified.");
+					return; // NaN
+				}
+			} else {
+				try {
+					rot = Float.parseFloat(words[0]);
+				} catch (NumberFormatException e) {
+					throwbackError(pc, "Supplied rotation " + words[0]
+							+ " failed to parse to a Float");
+					return;
+				} catch (Exception e) {
+					throwbackError(pc,
+							"Invalid Rotate Command. Need Rotation specified.");
+					return; // NaN
+				}
+			}
+
+			Vector3f rotation = new Vector3f(0f, rot, 0f);
+
+			Building b;
+			if (id != 0)
+				b = BuildingManager.getBuilding(id);
+			else
+				b = getTargetAsBuilding(pc);
+			if (b != null) {
+				rotateBuilding(pc, b, rotation, rot,false);
+				return;
+			}
+
+			// building failed, try npc
+			NPC npc;
+			if (id != 0)
+				npc = NPC.getNPC(id);
+			else
+				npc = getTargetAsNPC(pc);
+			if (npc != null) {
+				rotateNPC(pc, npc, rotation,false);
+				return;
+			}
+
+			// NPC failed, try mob
+			Mob mob;
+			if (id != 0)
+				mob = Mob.getMob(id);
+			else
+				mob = getTargetAsMob(pc);
+			if (mob != null) {
+				rotateMob(pc, mob, rotation,false);
+				return;
+			}
+			throwbackError(pc, "Nothing found to rotate.");
+		}
+	}
+
+	@Override
+	protected String _getHelpString() {
+        return "Rotates targeted or specified object";
+	}
+
+	@Override
+	protected String _getUsageString() {
+        return "' /rotate [objectID] rotation' || ' /rot [objectID] rotation'";
+	}
+
+	private void rotateBuilding(PlayerCharacter pc, Building building, Vector3f rot, float orig, boolean faceDirection) {
+		if (!faceDirection)
+			rot.set(0.0f, (float)Math.sin(Math.toRadians(orig)/2), 0.0f);
+		building.setRot(rot);
+		building.setw( (float) Math.abs(Math.cos(Math.toRadians(orig)/2)) );
+		building.getBounds().setBounds(building);
+		WorldGrid.updateObject(building, pc);
+		DbManager.BuildingQueries.SET_PROPERTY(building, "rotY", building.getRot().getY());
+		DbManager.BuildingQueries.SET_PROPERTY(building, "w", building.getw());
+		ChatManager.chatSayInfo(pc,
+				"Building with ID " + building.getObjectUUID() + " rotated");
+	}
+
+	private void rotateNPC(PlayerCharacter pc, NPC npc, Vector3f rot,boolean faceDirection) {
+		npc.setRot(rot);
+		DbManager.NPCQueries.SET_PROPERTY(npc, "npc_rotation", rot.y);
+		WorldGrid.updateObject(npc, pc);
+		//no rotation for npc's in db currently
+		ChatManager.chatSayInfo(pc,
+				"NPC with ID " + npc.getObjectUUID() + " rotated");
+	}
+
+	private void rotateMob(PlayerCharacter pc, Mob mob, Vector3f rot,boolean faceDirection) {
+		mob.setRot(rot);
+		DbManager.MobQueries.SET_PROPERTY(mob, "mob_rotation", rot.y);
+		WorldGrid.updateObject(mob, pc);
+		//no rotation for mobs's in db currently
+		ChatManager.chatSayInfo(pc,
+				"Mob with ID " + mob.getObjectUUID() + " rotated");
+	}
+
+	private void rotateFace(PlayerCharacter pc, AbstractGameObject target){
+		AbstractWorldObject awo = (AbstractWorldObject)target;
+		if (awo == null)
+			return;
+		Vector3fImmutable buildingLoc = awo.getLoc();
+		Vector3fImmutable playerLoc = pc.getLoc();
+
+		Vector3fImmutable faceDirection = playerLoc.subtract2D(buildingLoc);
+
+		float rotangle = faceDirection.getRotation();
+
+		float rot = (float) Math.toDegrees(rotangle);
+
+		if (rot > 180)
+			rot*=-1;
+
+		Vector3f buildingrotation = new Vector3f(0f, rot, 0f);
+		Vector3f rotation = new Vector3f(0f, rotangle, 0f);
+		if (target instanceof Building)
+			rotateBuilding(pc, (Building) target, buildingrotation, rot,false);
+		else if (target instanceof NPC)
+			rotateNPC(pc, (NPC) target, rotation,true);
+		else if (target instanceof Mob)
+			rotateMob(pc, (Mob) target, rotation,true);
+		else
+			throwbackError(pc, "Target " + target.getObjectType()
+			+ " is not a valid object type");
+
+	}
+}
\ No newline at end of file
diff --git a/src/engine/devcmd/cmds/SetAICmd.java b/src/engine/devcmd/cmds/SetAICmd.java
new file mode 100644
index 00000000..607e1c8b
--- /dev/null
+++ b/src/engine/devcmd/cmds/SetAICmd.java
@@ -0,0 +1,128 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+
+package engine.devcmd.cmds;
+
+import engine.devcmd.AbstractDevCmd;
+import engine.gameManager.DbManager;
+import engine.objects.AbstractGameObject;
+import engine.objects.PlayerCharacter;
+import engine.server.MBServerStatics;
+
+/**
+ * @author Steve
+ *
+ */
+public class SetAICmd extends AbstractDevCmd {
+
+	public SetAICmd() {
+        super("setAI");
+        this.addCmdString("ai");
+    }
+
+	@Override
+	protected void _doCmd(PlayerCharacter pc, String[] words,
+			AbstractGameObject target) {
+		if (words.length < 2){
+			this.sendUsage(pc);
+			return;
+		}
+			
+		int amount;
+
+		try{
+			amount = Integer.valueOf(words[1]);
+		}catch (NumberFormatException e) {
+			this.throwbackError(pc, "Failed to parse amount");
+			return;
+		}
+		
+		switch(words[0]){
+		case "angle" :
+			float angle = Float.parseFloat(words[1]);
+			
+			MBServerStatics.AI_MAX_ANGLE = angle;
+			break;
+			case "aggrorange":
+				MBServerStatics.AI_BASE_AGGRO_RANGE = amount;
+				DbManager.MobBaseQueries.UPDATE_AI_DEFAULTS();
+				this.throwbackInfo(pc, "Aggro Range is now set to " + amount);
+				break;
+			case "dropaggrorange":
+				MBServerStatics.AI_DROP_AGGRO_RANGE = amount;
+				DbManager.MobBaseQueries.UPDATE_AI_DEFAULTS();
+				this.throwbackInfo(pc, "Drop Aggro Range is now set to " + amount);
+				break;
+			case "patroldivisor":
+				MBServerStatics.AI_PATROL_DIVISOR = amount;
+				DbManager.MobBaseQueries.UPDATE_AI_DEFAULTS();
+				this.throwbackInfo(pc, "Patrol Chance is now set to " + amount);
+				break;
+			case "pulse":
+				if (amount < 500){
+					this.throwbackError(pc, "pulse amount must be greather than 500 to execute.");
+					return;
+				}
+				MBServerStatics.AI_PULSE_MOB_THRESHOLD = amount;
+				this.throwbackInfo(pc, "Pulse is now set to " + amount);
+				break;
+			case "sleepthread":
+				if (amount < 500){
+					this.throwbackError(pc, "sleep amount must be greather than 500 to execute.");
+					return;
+				}
+				MBServerStatics.AI_THREAD_SLEEP = amount;
+				this.throwbackInfo(pc, "Thread Sleep is now set to " + amount);
+				break;
+			case "recallrange":
+				MBServerStatics.AI_RECALL_RANGE = amount;
+				DbManager.MobBaseQueries.UPDATE_AI_DEFAULTS();
+				this.throwbackInfo(pc, "Recall Range is now set to " + amount);
+				break;
+			case "powerdivisor":
+				MBServerStatics.AI_POWER_DIVISOR = amount;
+				DbManager.MobBaseQueries.UPDATE_AI_DEFAULTS();
+				this.throwbackInfo(pc, "Power Divisor is now set to " + amount);
+				break;
+			case "losehate":
+				MBServerStatics.PLAYER_HATE_DELIMITER = amount;
+				break;
+			case "hatemodcombat":
+				MBServerStatics.PLAYER_COMBAT_HATE_MODIFIER = amount;
+				default:
+					this.throwbackError(pc, words[0] + " is not a valid AI Command.");
+					break;
+		}
+	}
+
+	@Override
+	protected String _getHelpString() {
+		String help = "Modifies Mob AI Statics. Commands:";
+		help += "\n AGGRORANGE: Sets the range when a mob will aggro it's target. Aggro range is currently " + MBServerStatics.AI_BASE_AGGRO_RANGE;
+		help += "\n DROPAGGRORANGE: Sets the range when a mob will drop aggro from it's target. Drop aggro range is currently " + MBServerStatics.AI_DROP_AGGRO_RANGE;
+		help += "\n PATROLDIVISOR: Sets the Patrol Divisor for Mob AI. Setting this will give a 1/[amount] chance to parol the area. Patrol Chance is currently 1/" + MBServerStatics.AI_PATROL_DIVISOR;
+		help += "\n PULSE: sets how often to run mob's AI. Measured in MS. Pulse is currently  " + MBServerStatics.AI_PULSE_MOB_THRESHOLD + "ms.";
+		help += "\n SLEEPTHREAD: Sets how long to sleep the AI for ALL mobs.Thread sleep is currently " + MBServerStatics.AI_THREAD_SLEEP + "ms.";
+		help += "\n RECALLRANGE: Sets the range of a mob to recall back to it's bind location. Recall range is currently " + MBServerStatics.AI_RECALL_RANGE;
+		help += "\n POWERDIVISOR: Sets the Power Divisor for Mob AI.Setting this will give a 1/[amount] chance to use power on a player. Power Divisor is currently " + MBServerStatics.AI_POWER_DIVISOR;
+		help += "\n LOSEHATE: Sets the amount per second to reduce hate amount for player while they are idle. Hate Delimiter is currently " + MBServerStatics.PLAYER_HATE_DELIMITER;
+		help += "\n HATEMODCOMBAT: sets the modifier for Hate value for Combat. Hate Value is `Damage *[HATEMODCOMBAT]`.Hate Mod Combat is currently " + MBServerStatics.PLAYER_COMBAT_HATE_MODIFIER;
+
+		return help;
+	}
+
+	@Override
+	protected String _getUsageString() {
+		String usage = "' /setai  `command` `amount` `";
+		usage += '\n' + _getHelpString();
+		return usage;
+	}
+
+}
diff --git a/src/engine/devcmd/cmds/SetActivateMineCmd.java b/src/engine/devcmd/cmds/SetActivateMineCmd.java
new file mode 100644
index 00000000..09972318
--- /dev/null
+++ b/src/engine/devcmd/cmds/SetActivateMineCmd.java
@@ -0,0 +1,67 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.devcmd.cmds;
+
+
+import engine.devcmd.AbstractDevCmd;
+import engine.gameManager.BuildingManager;
+import engine.objects.AbstractGameObject;
+import engine.objects.Building;
+import engine.objects.Mine;
+import engine.objects.PlayerCharacter;
+
+/**
+ *
+ */
+public class SetActivateMineCmd extends AbstractDevCmd {
+
+	public SetActivateMineCmd() {
+        super("mineactive");
+    }
+
+	@Override
+	protected void _doCmd(PlayerCharacter pcSender, String[] args,
+			AbstractGameObject target) {
+		Building mineBuilding = BuildingManager.getBuilding(target.getObjectUUID());
+		if (mineBuilding == null)
+			return;
+
+		Mine mine = Mine.getMineFromTower(mineBuilding.getObjectUUID());
+		if (mine == null)
+			return;
+
+		String trigger = args[0];
+		switch (trigger){
+		case "true":
+			mine.handleStartMineWindow();
+			Mine.setLastChange(System.currentTimeMillis());
+			break;
+		case "false":
+			mine.handleEndMineWindow();
+			Mine.setLastChange(System.currentTimeMillis());
+			break;
+		default:
+			this.sendUsage(pcSender);
+			break;
+
+		}
+	}
+
+	@Override
+	protected String _getUsageString() {
+        return "' /mineactive true|false";
+	}
+
+	@Override
+	protected String _getHelpString() {
+        return "Temporarily add visual effects to Character";
+	}
+
+}
diff --git a/src/engine/devcmd/cmds/SetAdminRuneCmd.java b/src/engine/devcmd/cmds/SetAdminRuneCmd.java
new file mode 100644
index 00000000..039a0b91
--- /dev/null
+++ b/src/engine/devcmd/cmds/SetAdminRuneCmd.java
@@ -0,0 +1,86 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.devcmd.cmds;
+
+import engine.InterestManagement.InterestManager;
+import engine.devcmd.AbstractDevCmd;
+import engine.gameManager.ChatManager;
+import engine.objects.AbstractGameObject;
+import engine.objects.CharacterRune;
+import engine.objects.PlayerCharacter;
+
+/**
+ *
+ * @author Eighty
+ *
+ */
+public class SetAdminRuneCmd extends AbstractDevCmd {
+
+	public SetAdminRuneCmd() {
+        super("setadminrune");
+    }
+
+	@Override
+	protected void _doCmd(PlayerCharacter pcSender, String[] args,
+			AbstractGameObject target) {
+		int runeID = 0;
+		boolean add = true;
+		try {
+			runeID = Integer.parseInt(args[0]);
+			if (args.length > 1)
+				add = (args[1].toLowerCase().equals("false")) ? false : true;
+		} catch (NumberFormatException e) {
+			this.sendUsage(pcSender);
+			return;
+		}
+		if (runeID < 2901 || runeID > 2911) {
+			throwbackError(pcSender,
+					"Invalid setrune Command. must specify an ID between 2901 and 2911.");
+			return;
+		}
+
+		if(!(target instanceof PlayerCharacter)) {
+			target = pcSender;
+		}
+
+		boolean worked = false;
+		if (add) {
+			worked = CharacterRune.grantRune((PlayerCharacter) target, runeID);
+			if (worked)
+				ChatManager.chatSayInfo(pcSender,
+						"rune of ID " + runeID + " added");
+			else
+				throwbackError(pcSender, "Failed to add the rune of type "
+						+ runeID);
+		} else {
+			worked = CharacterRune.removeRune((PlayerCharacter) target, runeID);
+			if (worked) {
+				ChatManager.chatSayInfo(pcSender,
+						"rune of ID " + runeID + " removed");
+				InterestManager.reloadCharacter(pcSender);
+			} else
+				throwbackError(pcSender, "Failed to remove the rune of type "
+						+ runeID);
+		}
+		this.setTarget(target); //for logging
+
+	}
+
+	@Override
+	protected String _getUsageString() {
+        return "' /setrune runeID [true/false]'";
+	}
+
+	@Override
+	protected String _getHelpString() {
+        return "Grant or remove the rune with the specified runeID";
+	}
+
+}
diff --git a/src/engine/devcmd/cmds/SetBaneActiveCmd.java b/src/engine/devcmd/cmds/SetBaneActiveCmd.java
new file mode 100644
index 00000000..1dabfc7d
--- /dev/null
+++ b/src/engine/devcmd/cmds/SetBaneActiveCmd.java
@@ -0,0 +1,80 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.devcmd.cmds;
+
+import engine.devcmd.AbstractDevCmd;
+import engine.gameManager.ZoneManager;
+import engine.objects.*;
+
+/**
+ * @author Eighty
+ *
+ */
+public class SetBaneActiveCmd extends AbstractDevCmd {
+
+	public SetBaneActiveCmd() {
+        super("setbaneactive");
+    }
+
+	@Override
+	protected void _doCmd(PlayerCharacter pc, String[] words,
+			AbstractGameObject target) {
+		if (words.length != 1) {
+			this.sendUsage(pc);
+			return;
+		}
+
+		boolean setActive = false;
+		if (words[0].equals("true"))
+			setActive = true;
+
+		Zone zone = ZoneManager.findSmallestZone(pc.getLoc());
+
+		if (zone == null) {
+			throwbackError(pc, "Unable to find the zone you're in.");
+			return;
+		}
+
+		if (!zone.isPlayerCity()) {
+			throwbackError(pc, "This is not a player city.");
+			return;
+		}
+
+		City city = City.getCity(zone.getPlayerCityUUID());
+		if (city == null) {
+			throwbackError(pc, "Unable to find the city associated with this zone.");
+			return;
+		}
+
+		Bane bane = city.getBane();
+		if (bane == null) {
+			throwbackError(pc, "Could not find bane to modify.");
+			return;
+		}
+
+        bane.getCity().protectionEnforced = !setActive;
+
+		if (setActive)
+			throwbackInfo(pc, "The bane has been set active.");
+		else
+			throwbackInfo(pc, "The bane has been set inactive.");
+	}
+
+	@Override
+	protected String _getHelpString() {
+        return "Sets a bane active or deactivates a bane.";
+	}
+
+	@Override
+	protected String _getUsageString() {
+        return "'./setbaneactive true|false'";
+	}
+
+}
diff --git a/src/engine/devcmd/cmds/SetBaseClassCmd.java b/src/engine/devcmd/cmds/SetBaseClassCmd.java
new file mode 100644
index 00000000..0c589c7a
--- /dev/null
+++ b/src/engine/devcmd/cmds/SetBaseClassCmd.java
@@ -0,0 +1,64 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.devcmd.cmds;
+
+import engine.InterestManagement.InterestManager;
+import engine.devcmd.AbstractDevCmd;
+import engine.gameManager.ChatManager;
+import engine.objects.AbstractGameObject;
+import engine.objects.PlayerCharacter;
+
+public class SetBaseClassCmd extends AbstractDevCmd {
+
+	public SetBaseClassCmd() {
+        super("setBaseClass");
+    }
+
+	@Override
+	protected void _doCmd(PlayerCharacter pc, String[] words,
+			AbstractGameObject target) {
+		// Arg Count Check
+		if (words.length != 1) {
+			this.sendUsage(pc);
+			return;
+		}
+
+		int classID = 0;
+		try {
+			classID = Integer.parseInt(words[0]);
+		} catch (Exception e) {
+			throwbackError(pc,
+					"Invalid setBaseClass Command. must specify an ID between 2500 and 2503.");
+			return;
+		}
+		if (classID < 2500 || classID > 2503) {
+			throwbackError(pc,
+					"Invalid setBaseClass Command. must specify an ID between 2500 and 2503.");
+			return;
+		}
+		pc.setBaseClass(classID);
+		this.setTarget(pc); //for logging
+		ChatManager.chatSayInfo(pc,
+				"BaseClass changed to " + classID);
+		InterestManager.reloadCharacter(pc);
+
+	}
+
+	@Override
+	protected String _getHelpString() {
+		return "Sets your character's BaseClass to 'id'";
+	}
+
+	@Override
+	protected String _getUsageString() {
+		return "' /setBaseClass id'";
+	}
+
+}
diff --git a/src/engine/devcmd/cmds/SetBuildingAltitudeCmd.java b/src/engine/devcmd/cmds/SetBuildingAltitudeCmd.java
new file mode 100644
index 00000000..441aec15
--- /dev/null
+++ b/src/engine/devcmd/cmds/SetBuildingAltitudeCmd.java
@@ -0,0 +1,107 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+
+package engine.devcmd.cmds;
+
+import engine.Enum.GameObjectType;
+import engine.InterestManagement.WorldGrid;
+import engine.devcmd.AbstractDevCmd;
+import engine.gameManager.BuildingManager;
+import engine.gameManager.DbManager;
+import engine.math.Vector3fImmutable;
+import engine.objects.AbstractGameObject;
+import engine.objects.Building;
+import engine.objects.PlayerCharacter;
+
+public class SetBuildingAltitudeCmd extends AbstractDevCmd {
+
+	public SetBuildingAltitudeCmd() {
+        super("setbuildingaltitude");
+        this.addCmdString("buildingaltitude");
+    }
+
+    private static boolean UpdateBuildingAltitude(Building building, float altitude) {
+
+        if (!DbManager.BuildingQueries.UPDATE_BUILDING_ALTITUDE(building.getObjectUUID(), altitude))
+            return false;
+
+        building.statAlt = altitude;
+
+        if (building.parentZone != null) {
+            if (building.parentBuildingID != 0) {
+                Building parentBuilding = BuildingManager.getBuilding(building.parentBuildingID);
+                if (parentBuilding != null) {
+                    building.setLoc(new Vector3fImmutable(building.statLat + building.parentZone.absX + parentBuilding.statLat, building.statAlt + building.parentZone.absY + parentBuilding.statAlt, building.statLon + building.parentZone.absZ + parentBuilding.statLon));
+                } else {
+                    building.setLoc(new Vector3fImmutable(building.statLat + building.parentZone.absX, building.statAlt + building.parentZone.absY, building.statLon + building.parentZone.absZ));
+
+                }
+            } else
+                building.setLoc(new Vector3fImmutable(building.statLat + building.parentZone.absX, building.statAlt + building.parentZone.absY, building.statLon + building.parentZone.absZ));
+
+        }
+
+        return true;
+    }
+
+    @Override
+	protected void _doCmd(PlayerCharacter pc, String[] words,
+			AbstractGameObject target) {
+		// Arg Count Check
+		if (words.length != 1) {
+			this.sendUsage(pc);
+			return;
+		}
+
+		if (target.getObjectType() != GameObjectType.Building){
+			this.sendUsage(pc);
+			return;
+		}
+
+		Building targetBuilding = (Building)target;
+
+
+		float altitude = 0;
+		try {
+			altitude  = Float.parseFloat(words[0]);
+
+			if (!UpdateBuildingAltitude(targetBuilding, targetBuilding.getStatAlt() + altitude)){
+				this.throwbackError(pc, "Failed to update building altitude");
+				return;
+			}
+
+
+			WorldGrid.updateObject(targetBuilding);
+
+			this.setTarget(pc); //for logging
+
+			// Update all surrounding clients.
+
+		} catch (NumberFormatException e) {
+			this.throwbackError(pc, "Supplied data: " + words[0]
+					+ " failed to parse to an Integer.");
+		} catch (Exception e) {
+			this.throwbackError(pc,
+					"An unknown exception occurred while attempting to setSlot to "
+							+ words[0]);
+		}
+	}
+
+	@Override
+	protected String _getHelpString() {
+		return "Sets slot position for an NPC to 'slot'";
+	}
+
+	@Override
+	protected String _getUsageString() {
+		return "' /changeslot slot'";
+	}
+
+}
diff --git a/src/engine/devcmd/cmds/SetForceRenameCityCmd.java b/src/engine/devcmd/cmds/SetForceRenameCityCmd.java
new file mode 100644
index 00000000..6e592155
--- /dev/null
+++ b/src/engine/devcmd/cmds/SetForceRenameCityCmd.java
@@ -0,0 +1,54 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+
+package engine.devcmd.cmds;
+
+import engine.devcmd.AbstractDevCmd;
+import engine.gameManager.ZoneManager;
+import engine.objects.AbstractGameObject;
+import engine.objects.City;
+import engine.objects.PlayerCharacter;
+import engine.objects.Zone;
+
+
+public class SetForceRenameCityCmd extends AbstractDevCmd {
+
+	public SetForceRenameCityCmd() {
+        super("forcerename");
+    }
+
+	@Override
+	protected void _doCmd(PlayerCharacter pc, String[] words,
+			AbstractGameObject target) {
+		
+		Zone zone = ZoneManager.findSmallestZone(pc.getLoc());
+		if (zone == null)
+			return;
+		boolean rename = words[0].equalsIgnoreCase("true") ? true : false;
+		if (zone.getPlayerCityUUID() == 0)
+			return;
+		City city = City.getCity(zone.getPlayerCityUUID());
+		if (city == null)
+			return;
+		city.setForceRename(rename);
+	
+	}
+
+	@Override
+	protected String _getHelpString() {
+		return "Temporarily Changes SubRace";
+	}
+
+	@Override
+	protected String _getUsageString() {
+		return "' /subrace mobBaseID";
+	}
+
+}
diff --git a/src/engine/devcmd/cmds/SetGuildCmd.java b/src/engine/devcmd/cmds/SetGuildCmd.java
new file mode 100644
index 00000000..b46282ec
--- /dev/null
+++ b/src/engine/devcmd/cmds/SetGuildCmd.java
@@ -0,0 +1,105 @@
+package engine.devcmd.cmds;
+
+import engine.Enum.GameObjectType;
+import engine.InterestManagement.WorldGrid;
+import engine.devcmd.AbstractDevCmd;
+import engine.gameManager.DbManager;
+import engine.objects.AbstractGameObject;
+import engine.objects.Guild;
+import engine.objects.NPC;
+import engine.objects.PlayerCharacter;
+
+/**
+ *
+ * @author 
+ * Dev command to set the guild of targeted npc.
+ * Argument is a valid guild UID
+ */
+public class SetGuildCmd extends AbstractDevCmd {
+
+        
+	public SetGuildCmd() {
+        super("setguild");
+    }
+
+	@Override
+	protected void _doCmd(PlayerCharacter pcSender, String[] args,
+			AbstractGameObject target) {
+
+        NPC targetNPC;
+        Guild targetGuild;
+        
+        if(validateUserInput(pcSender, target, args) == false) {
+           this.sendUsage(pcSender);
+           return;
+            }
+                        
+        // Valid arguments, attempt to set guild of NPC.
+        
+        targetNPC = getTargetAsNPC(pcSender);
+        targetGuild = Guild.getGuild(Integer.parseInt(args[0]));
+        
+        DbManager.NPCQueries.SET_PROPERTY(targetNPC, "npc_guildID", args[0]);
+        targetNPC.setGuild(targetGuild);
+        
+        // Refresh loaded game object
+        
+        WorldGrid.updateObject(targetNPC, pcSender);
+        
+	}
+
+	@Override
+	protected String _getUsageString() {
+        return "' /setguild [UID]";
+	}
+
+	@Override
+	protected String _getHelpString() {
+        return "Assigns NPC to a given guild";
+	}
+        
+        private boolean validateUserInput(PlayerCharacter pcSender, AbstractGameObject currTarget, String[] userInput) {
+        
+        Guild tempGuild;
+        
+        // Incorrect number of arguments
+            
+        if (userInput.length != 1)
+         return false;    
+
+        // No target
+        
+         if (currTarget == null) {
+           throwbackError(pcSender, "Requires an NPC be targeted");
+           return false;
+         }
+         
+        // Target must be an NPC
+         
+         if (currTarget.getObjectType() != GameObjectType.NPC) {
+             throwbackError(pcSender, "Invalid object. Must be an NPC");
+            return false; 
+         }
+         
+        // Argument must parse as a int.
+         
+         try {
+         Integer.parseInt(userInput[0]); }
+        catch (NumberFormatException | NullPointerException e) {
+         return false;
+        }
+         
+         // Argument must return a valid guild
+         
+         tempGuild = Guild.getGuild(Integer.parseInt(userInput[0]));
+         
+         if (tempGuild == null) {
+             throwbackError(pcSender, "Invalid Guild UID");
+              return false;
+         }
+          
+             
+        return true;
+}
+
+}
diff --git a/src/engine/devcmd/cmds/SetHealthCmd.java b/src/engine/devcmd/cmds/SetHealthCmd.java
new file mode 100644
index 00000000..81ef3064
--- /dev/null
+++ b/src/engine/devcmd/cmds/SetHealthCmd.java
@@ -0,0 +1,67 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+
+package engine.devcmd.cmds;
+
+import engine.Enum.DispatchChannel;
+import engine.devcmd.AbstractDevCmd;
+import engine.net.DispatchMessage;
+import engine.net.client.msg.TargetedActionMsg;
+import engine.objects.AbstractGameObject;
+import engine.objects.PlayerCharacter;
+import engine.server.MBServerStatics;
+
+public class SetHealthCmd extends AbstractDevCmd {
+
+	public SetHealthCmd() {
+        super("setHealth");
+        this.addCmdString("health");
+    }
+
+	@Override
+	protected void _doCmd(PlayerCharacter pc, String[] words,
+			AbstractGameObject target) {
+		// Arg Count Check
+		if (words.length != 1) {
+			this.sendUsage(pc);
+			return;
+		}
+
+		float amount = 0.0f;
+		try {
+			amount = Float.parseFloat(words[0]);
+			pc.modifyHealth(amount, pc, false);
+			this.setTarget(pc); //for logging
+
+			// Update all surrounding clients.
+			TargetedActionMsg cmm = new TargetedActionMsg(pc);
+			DispatchMessage.dispatchMsgToInterestArea(pc, cmm, DispatchChannel.SECONDARY, MBServerStatics.CHARACTER_LOAD_RANGE, true, false);
+
+		} catch (NumberFormatException e) {
+			this.throwbackError(pc, "Supplied data: " + words[0]
+					+ " failed to parse to a Float.");
+		} catch (Exception e) {
+			this.throwbackError(pc,
+					"An unknown exception occurred while attempting to setHealth to "
+							+ words[0]);
+		}
+	}
+
+	@Override
+	protected String _getHelpString() {
+		return "Sets your character's health to 'amount'";
+	}
+
+	@Override
+	protected String _getUsageString() {
+		return "' /setHealth amount'";
+	}
+
+}
diff --git a/src/engine/devcmd/cmds/SetInvulCmd.java b/src/engine/devcmd/cmds/SetInvulCmd.java
new file mode 100644
index 00000000..a9f952a7
--- /dev/null
+++ b/src/engine/devcmd/cmds/SetInvulCmd.java
@@ -0,0 +1,91 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.devcmd.cmds;
+
+
+import engine.Enum;
+import engine.Enum.ProtectionState;
+import engine.devcmd.AbstractDevCmd;
+import engine.objects.AbstractGameObject;
+import engine.objects.Building;
+import engine.objects.City;
+import engine.objects.PlayerCharacter;
+
+public class SetInvulCmd extends AbstractDevCmd {
+
+	public SetInvulCmd() {
+        super("setinvul");
+    }
+
+	@Override
+	protected void _doCmd(PlayerCharacter pcSender, String[] words,
+			AbstractGameObject target) {
+		if (pcSender == null) return;
+
+		if (words.length != 1) {
+			this.sendUsage(pcSender);
+			return;
+		}
+
+		boolean invul;
+		switch (words[0].toLowerCase()) {
+		case "true":
+			invul = true;
+			break;
+		case "false":
+			invul = false;
+			break;
+		default:
+			this.sendUsage(pcSender);
+			return;
+		}
+
+		if (target == null || !(target instanceof Building)) {
+			throwbackError(pcSender, "No building targeted");
+			return;
+		}
+
+		Building b = (Building) target;
+
+		// if strucutre is a TOL then we're modifying the protection
+		// status of the entire city
+
+		if ( (b.getBlueprint() != null) &&
+				(b.getBlueprint().getBuildingGroup().equals(Enum.BuildingGroup.TOL))) {
+
+			City city;
+
+			city = b.getCity();
+			city.protectionEnforced = !city.protectionEnforced;
+			throwbackInfo(pcSender, "City protection contracts enforced: " + city.protectionEnforced);
+			return;
+		}
+
+		if (invul) {
+
+			b.setProtectionState(ProtectionState.PROTECTED);
+			throwbackInfo(pcSender, "The targetted building is now invulnerable.");
+			return;
+		}
+		b.setProtectionState(ProtectionState.NONE);
+		throwbackInfo(pcSender, "The targetted building is no longer invulernable.");
+	}
+
+	@Override
+	protected String _getUsageString() {
+		return "'./setInvul true|false'";
+	}
+
+	@Override
+	protected String _getHelpString() {
+		return "Turns invulernability on or off for building";
+	}
+
+}
diff --git a/src/engine/devcmd/cmds/SetLevelCmd.java b/src/engine/devcmd/cmds/SetLevelCmd.java
new file mode 100644
index 00000000..aabedc40
--- /dev/null
+++ b/src/engine/devcmd/cmds/SetLevelCmd.java
@@ -0,0 +1,73 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.devcmd.cmds;
+
+import engine.InterestManagement.InterestManager;
+import engine.devcmd.AbstractDevCmd;
+import engine.gameManager.ChatManager;
+import engine.objects.AbstractGameObject;
+import engine.objects.PlayerCharacter;
+
+public class SetLevelCmd extends AbstractDevCmd {
+
+	public SetLevelCmd() {
+		super("setLevel");
+	}
+
+	@Override
+	protected void _doCmd(PlayerCharacter pc, String[] words,
+			AbstractGameObject target) {
+		// Arg Count Check
+		if (words.length != 1) {
+			this.sendUsage(pc);
+			return;
+		}
+
+		PlayerCharacter tar;
+		if (target != null) {
+			if (target instanceof PlayerCharacter)
+				tar = (PlayerCharacter) target;
+			else
+				tar = pc;
+		} else
+			tar = pc;
+
+		int level = 0;
+		try {
+			level = Integer.parseInt(words[0]);
+		} catch (NumberFormatException e) {
+			this.sendUsage(pc);
+			return;
+		}
+		if (level < 1 || level > 75) {
+			this.sendHelp(pc);
+			return;
+		}
+
+		if (level > 10 && pc.getPromotionClass() == null)
+			level = 10;
+
+		tar.setLevel((short) level);
+		this.setTarget(tar); //for logging
+		ChatManager.chatSayInfo(pc, tar.getFirstName() + " level changed to " + level);
+		InterestManager.reloadCharacter(tar);
+	}
+
+	@Override
+	protected String _getHelpString() {
+		return "Sets your character's level to 'amount'.  'amount' must be between 1-75";
+	}
+
+	@Override
+	protected String _getUsageString() {
+		return "' /setLevel amount'";
+	}
+
+}
diff --git a/src/engine/devcmd/cmds/SetMaintCmd.java b/src/engine/devcmd/cmds/SetMaintCmd.java
new file mode 100644
index 00000000..e81832e4
--- /dev/null
+++ b/src/engine/devcmd/cmds/SetMaintCmd.java
@@ -0,0 +1,61 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+
+package engine.devcmd.cmds;
+
+import engine.Enum;
+import engine.devcmd.AbstractDevCmd;
+import engine.gameManager.ChatManager;
+import engine.gameManager.MaintenanceManager;
+import engine.objects.AbstractGameObject;
+import engine.objects.Building;
+import engine.objects.PlayerCharacter;
+
+import java.time.LocalDateTime;
+
+public class SetMaintCmd extends AbstractDevCmd {
+
+	public SetMaintCmd() {
+        super("setMaint");
+        this.addCmdString("setmaint");
+    }
+
+	@Override
+	protected void _doCmd(PlayerCharacter player, String[] words,
+			AbstractGameObject target) {
+
+		if (!target.getObjectType().equals(Enum.GameObjectType.Building)) {
+			ChatManager.chatSayInfo(player, "Target is not a valid building");
+			return;
+		}
+
+		Building targetBuilding = (Building)target;
+
+		if (targetBuilding.getProtectionState().equals(Enum.ProtectionState.NPC)) {
+			ChatManager.chatSayInfo(player, "Target is not a valid building");
+			return;
+		}
+
+		MaintenanceManager.setMaintDateTime(targetBuilding, LocalDateTime.now().minusDays(1).withHour(13).withMinute(0).withSecond(0).withNano(0));
+		ChatManager.chatSayInfo(player, "Maint will run for UUID: " + targetBuilding.getObjectUUID());
+		}
+
+
+	@Override
+	protected String _getHelpString() {
+		return "Sets the Rank of either the targets object or the object specified by ID.";
+	}
+
+	@Override
+	protected String _getUsageString() {
+		return "' /setrank ID rank' || ' /setrank rank' || ' /rank ID rank' || ' /rank rank'";
+	}
+
+}
diff --git a/src/engine/devcmd/cmds/SetManaCmd.java b/src/engine/devcmd/cmds/SetManaCmd.java
new file mode 100644
index 00000000..47e059a3
--- /dev/null
+++ b/src/engine/devcmd/cmds/SetManaCmd.java
@@ -0,0 +1,56 @@
+package engine.devcmd.cmds;
+
+import engine.devcmd.AbstractDevCmd;
+import engine.net.DispatchMessage;
+import engine.net.client.msg.TargetedActionMsg;
+import engine.objects.AbstractGameObject;
+import engine.objects.PlayerCharacter;
+import engine.server.MBServerStatics;
+
+public class SetManaCmd extends AbstractDevCmd {
+
+	public SetManaCmd() {
+        super("setMana");
+        this.addCmdString("mana");
+    }
+
+	@Override
+	protected void _doCmd(PlayerCharacter pc, String[] words,
+			AbstractGameObject target) {
+		// Arg Count Check
+		if (words.length != 1) {
+			this.sendUsage(pc);
+			return;
+		}
+
+		float amount = 0.0f;
+		try {
+			amount = Float.parseFloat(words[0]);
+			pc.setMana(amount, pc);
+			this.setTarget(pc); //for logging
+
+			//Update all surrounding clients. - NOT for Mana?
+			TargetedActionMsg cmm = new TargetedActionMsg(pc);
+			DispatchMessage.dispatchMsgToInterestArea(pc, cmm, engine.Enum.DispatchChannel.SECONDARY, MBServerStatics.CHARACTER_LOAD_RANGE, true, false);
+
+		} catch (NumberFormatException e) {
+			this.throwbackError(pc, "Supplied data: " + words[0]
+					+ " failed to parse to a Float.");
+		} catch (Exception e) {
+			this.throwbackError(pc,
+					"An unknown exception occurred while attempting to setMana to "
+							+ words[0]);
+		}
+	}
+
+	@Override
+	protected String _getHelpString() {
+		return "Sets your character's Mana to 'amount'";
+	}
+
+	@Override
+	protected String _getUsageString() {
+		return "' /setMana amount'";
+	}
+
+}
diff --git a/src/engine/devcmd/cmds/SetMineExpansion.java b/src/engine/devcmd/cmds/SetMineExpansion.java
new file mode 100644
index 00000000..0bffe902
--- /dev/null
+++ b/src/engine/devcmd/cmds/SetMineExpansion.java
@@ -0,0 +1,87 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.devcmd.cmds;
+
+
+import engine.Enum.GameObjectType;
+import engine.devcmd.AbstractDevCmd;
+import engine.gameManager.BuildingManager;
+import engine.gameManager.ChatManager;
+import engine.gameManager.DbManager;
+import engine.objects.AbstractGameObject;
+import engine.objects.Building;
+import engine.objects.Mine;
+import engine.objects.PlayerCharacter;
+
+/**
+ * 
+ * @author Eighty
+ * 
+ */
+public class SetMineExpansion extends AbstractDevCmd {
+
+	public SetMineExpansion() {
+        super("setexpansion");
+        this.addCmdString("setexpansion");
+    }
+
+	@Override
+	protected void _doCmd(PlayerCharacter pcSender, String[] args,
+			AbstractGameObject target) {
+		
+		
+		if (target.getObjectType() != GameObjectType.Building)
+			return;
+		Building mineBuilding = BuildingManager.getBuilding(target.getObjectUUID());
+		if (mineBuilding == null)
+			return;
+		Mine mine = Mine.getMineFromTower(mineBuilding.getObjectUUID());
+		if (mine == null)
+			return;
+		int bit = 2;
+		switch (args[0].toUpperCase()) {
+		case "ON":
+
+			bit |= mine.getFlags();
+			if (!DbManager.MineQueries.SET_FLAGS(mine, bit))
+				return;
+			mine.setFlags(bit);
+			ChatManager.chatSystemInfo(pcSender, mine.getZoneName() + "'s " + mine.getMineType().name + " is now an expansion mine.");
+			Mine.setLastChange(System.currentTimeMillis());
+			break;
+			
+		case "OFF":
+			bit &= ~mine.getFlags();
+			if (!DbManager.MineQueries.SET_FLAGS(mine, bit))
+				return;
+			mine.setFlags(bit);
+			ChatManager.chatSystemInfo(pcSender, mine.getZoneName() + "'s " + mine.getMineType().name + " is no longer an expansion mine.");
+			Mine.setLastChange(System.currentTimeMillis());
+			break;
+			
+		}
+
+		
+		
+		
+
+	}
+
+	@Override
+	protected String _getUsageString() {
+        return "' /setmineexpansion on|off";
+	}
+
+	@Override
+	protected String _getHelpString() {
+        return "enables or disables an expansion type for a mine.";
+	}
+
+}
diff --git a/src/engine/devcmd/cmds/SetMineTypeCmd.java b/src/engine/devcmd/cmds/SetMineTypeCmd.java
new file mode 100644
index 00000000..c932923e
--- /dev/null
+++ b/src/engine/devcmd/cmds/SetMineTypeCmd.java
@@ -0,0 +1,65 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.devcmd.cmds;
+
+
+import engine.Enum.GameObjectType;
+import engine.devcmd.AbstractDevCmd;
+import engine.gameManager.BuildingManager;
+import engine.gameManager.ChatManager;
+import engine.gameManager.DbManager;
+import engine.objects.*;
+
+/**
+ * 
+ * @author Eighty
+ * 
+ */
+public class SetMineTypeCmd extends AbstractDevCmd {
+
+	public SetMineTypeCmd() {
+        super("setminetype");
+        this.addCmdString("setminetype");
+    }
+
+	@Override
+	protected void _doCmd(PlayerCharacter pcSender, String[] args,
+			AbstractGameObject target) {
+		
+		MineProduction mineType = MineProduction.valueOf(args[0].toUpperCase());
+		if (mineType == null)
+			return;
+		
+		if (target.getObjectType() != GameObjectType.Building)
+			return;
+		Building mineBuilding = BuildingManager.getBuilding(target.getObjectUUID());
+		if (mineBuilding == null)
+			return;
+		Mine mine = Mine.getMineFromTower(mineBuilding.getObjectUUID());
+		if (mine == null)
+			return;
+		if (!DbManager.MineQueries.CHANGE_TYPE(mine, mineType))
+			return;
+		mine.setMineType(mineType.name());
+		ChatManager.chatSystemInfo(pcSender, "The mine in " + mine.getZoneName() + " is now a(n) " + mine.getMineType().name);
+		Mine.setLastChange(System.currentTimeMillis());
+	}
+
+	@Override
+	protected String _getUsageString() {
+        return "' /setminetype gold|ore|magic|lumber";
+	}
+
+	@Override
+	protected String _getHelpString() {
+        return "Changes the mine type of the current mine.";
+	}
+
+}
diff --git a/src/engine/devcmd/cmds/SetNPCSlotCmd.java b/src/engine/devcmd/cmds/SetNPCSlotCmd.java
new file mode 100644
index 00000000..5b31eb3d
--- /dev/null
+++ b/src/engine/devcmd/cmds/SetNPCSlotCmd.java
@@ -0,0 +1,80 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+
+package engine.devcmd.cmds;
+
+import engine.Enum.GameObjectType;
+import engine.InterestManagement.WorldGrid;
+import engine.devcmd.AbstractDevCmd;
+import engine.objects.AbstractGameObject;
+import engine.objects.NPC;
+import engine.objects.PlayerCharacter;
+
+public class SetNPCSlotCmd extends AbstractDevCmd {
+
+	public SetNPCSlotCmd() {
+        super("updateNPCSlot");
+        this.addCmdString("changeslot");
+    }
+
+	@Override
+	protected void _doCmd(PlayerCharacter pc, String[] words,
+			AbstractGameObject target) {
+		// Arg Count Check
+		if (words.length != 1) {
+			this.sendUsage(pc);
+			return;
+		}
+
+		if (target.getObjectType() != GameObjectType.NPC){
+			this.sendUsage(pc);
+			return;
+		}
+
+		NPC npc = (NPC)target;
+
+
+		int slot = 0;
+		try {
+			slot = Integer.parseInt(words[0]);
+
+			if (!NPC.UpdateSlot(npc, slot)){
+				this.throwbackError(pc, "Failed to Update Slot");
+				return;
+			}
+
+			npc.setParentZone(npc.getParentZone());
+			WorldGrid.updateObject(npc);
+
+			this.setTarget(pc); //for logging
+
+			// Update all surrounding clients.
+
+		} catch (NumberFormatException e) {
+			this.throwbackError(pc, "Supplied data: " + words[0]
+					+ " failed to parse to an Integer.");
+		} catch (Exception e) {
+			this.throwbackError(pc,
+					"An unknown exception occurred while attempting to setSlot to "
+							+ words[0]);
+		}
+	}
+
+	@Override
+	protected String _getHelpString() {
+		return "Sets slot position for an NPC to 'slot'";
+	}
+
+	@Override
+	protected String _getUsageString() {
+		return "' /changeslot slot'";
+	}
+
+}
diff --git a/src/engine/devcmd/cmds/SetNpcEquipSetCmd.java b/src/engine/devcmd/cmds/SetNpcEquipSetCmd.java
new file mode 100644
index 00000000..d4b2dd67
--- /dev/null
+++ b/src/engine/devcmd/cmds/SetNpcEquipSetCmd.java
@@ -0,0 +1,138 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+
+package engine.devcmd.cmds;
+
+import engine.Enum.GameObjectType;
+import engine.InterestManagement.WorldGrid;
+import engine.devcmd.AbstractDevCmd;
+import engine.objects.AbstractGameObject;
+import engine.objects.MobBase;
+import engine.objects.NPC;
+import engine.objects.PlayerCharacter;
+
+public class SetNpcEquipSetCmd extends AbstractDevCmd {
+
+	public static int lastEquipSetID = 0;
+	public SetNpcEquipSetCmd() {
+        super("setEquipSet");
+        this.addCmdString("equipset");
+    }
+
+	@Override
+	protected void _doCmd(PlayerCharacter pc, String[] words,
+			AbstractGameObject target) {
+		// Arg Count Check
+		if (words.length != 1) {
+			this.sendUsage(pc);
+			return;
+		}
+
+		if (target.getObjectType() != GameObjectType.NPC){
+			this.sendUsage(pc);
+			return;
+		}
+
+		NPC npc = (NPC)target;
+
+		if (words[0].equalsIgnoreCase("next")){
+
+			if (SetNpcEquipSetCmd.lastEquipSetID >= 1222)
+				SetNpcEquipSetCmd.lastEquipSetID = 1;
+			else
+				SetNpcEquipSetCmd.lastEquipSetID++;
+
+			boolean complete = false;
+
+			while (complete == false){
+				complete = NPC.UpdateEquipSetID(npc, SetNpcEquipSetCmd.lastEquipSetID);
+
+				if (!complete){
+					SetNpcEquipSetCmd.lastEquipSetID++;
+					if (SetNpcEquipSetCmd.lastEquipSetID >= 1222)
+						SetNpcEquipSetCmd.lastEquipSetID = 1;
+				}
+			}
+
+			if (complete){
+				npc.equip = MobBase.loadEquipmentSet(npc.getEquipmentSetID());
+				WorldGrid.updateObject(npc);
+				this.throwbackInfo(pc, "Equipment Set Changed to " + SetNpcEquipSetCmd.lastEquipSetID );
+			}
+
+
+
+			return;
+		}
+
+		if (words[0].equalsIgnoreCase("last")){
+
+			if (SetNpcEquipSetCmd.lastEquipSetID <= 1)
+				SetNpcEquipSetCmd.lastEquipSetID = 1222;
+			else
+				SetNpcEquipSetCmd.lastEquipSetID--;
+
+			boolean complete = false;
+
+			while (complete == false){
+				complete = NPC.UpdateEquipSetID(npc, SetNpcEquipSetCmd.lastEquipSetID);
+
+				if (!complete){
+					SetNpcEquipSetCmd.lastEquipSetID--;
+					if (SetNpcEquipSetCmd.lastEquipSetID <= 1)
+						SetNpcEquipSetCmd.lastEquipSetID = 1222;
+				}
+
+			}
+
+			if (complete){
+				this.throwbackInfo(pc, "Equipment Set Changed to " + SetNpcEquipSetCmd.lastEquipSetID );
+				npc.equip = MobBase.loadEquipmentSet(npc.getEquipmentSetID());
+				WorldGrid.updateObject(npc);
+			}
+
+
+
+			return;
+		}
+
+		int equipSetID = 0;
+
+		try{
+			equipSetID = Integer.parseInt(words[0]);
+		}catch(Exception e){
+			this.throwbackError(pc, e.getMessage());
+		}
+
+		if (!NPC.UpdateEquipSetID(npc, equipSetID)){
+			this.throwbackError(pc, "Unable to find Equipset for ID " + equipSetID );
+			return;
+		}
+
+		SetNpcEquipSetCmd.lastEquipSetID = equipSetID;
+		npc.equip = MobBase.loadEquipmentSet(npc.getEquipmentSetID());
+		WorldGrid.updateObject(npc);
+
+		this.throwbackInfo(pc, "Equipment Set Changed to " + equipSetID );
+
+
+	}
+
+	@Override
+	protected String _getHelpString() {
+		return "Sets slot position for an NPC to 'slot'";
+	}
+
+	@Override
+	protected String _getUsageString() {
+		return "' /changeslot slot'";
+	}
+
+}
diff --git a/src/engine/devcmd/cmds/SetNpcMobbaseCmd.java b/src/engine/devcmd/cmds/SetNpcMobbaseCmd.java
new file mode 100644
index 00000000..07002761
--- /dev/null
+++ b/src/engine/devcmd/cmds/SetNpcMobbaseCmd.java
@@ -0,0 +1,67 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+
+package engine.devcmd.cmds;
+
+import engine.Enum.GameObjectType;
+import engine.InterestManagement.WorldGrid;
+import engine.devcmd.AbstractDevCmd;
+import engine.objects.AbstractGameObject;
+import engine.objects.MobBase;
+import engine.objects.NPC;
+import engine.objects.PlayerCharacter;
+
+public class SetNpcMobbaseCmd extends AbstractDevCmd {
+
+	public SetNpcMobbaseCmd() {
+        super("setmobbase");
+        this.addCmdString("npcmobbase");
+    }
+
+	@Override
+	protected void _doCmd(PlayerCharacter player, String[] words,
+			AbstractGameObject target) {
+
+		// Arg Count Check
+		if (words.length != 1) {
+			this.sendUsage(player);
+			return;
+		}
+
+		if (target.getObjectType() != GameObjectType.NPC){
+			this.sendUsage(player);
+			return;
+		}
+		
+		NPC npc = (NPC)target;
+		
+		int mobBaseID = Integer.parseInt(words[0]);
+		
+		if (MobBase.getMobBase(mobBaseID) == null){
+			this.throwbackError(player, "Cannot find Mobbase for ID " + mobBaseID);
+			return;
+		}
+		NPC.UpdateRaceID(npc, mobBaseID);
+		
+		WorldGrid.updateObject(npc);
+
+	}
+
+	@Override
+	protected String _getHelpString() {
+		return "Sets mobbase override for an NPC";
+	}
+
+	@Override
+	protected String _getUsageString() {
+		return "' /setmobbase mobBaseID'";
+	}
+
+}
diff --git a/src/engine/devcmd/cmds/SetNpcNameCmd.java b/src/engine/devcmd/cmds/SetNpcNameCmd.java
new file mode 100644
index 00000000..2e05ae2c
--- /dev/null
+++ b/src/engine/devcmd/cmds/SetNpcNameCmd.java
@@ -0,0 +1,62 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+
+package engine.devcmd.cmds;
+
+import engine.Enum.GameObjectType;
+import engine.InterestManagement.WorldGrid;
+import engine.devcmd.AbstractDevCmd;
+import engine.objects.AbstractGameObject;
+import engine.objects.NPC;
+import engine.objects.PlayerCharacter;
+
+public class SetNpcNameCmd extends AbstractDevCmd {
+
+	public static int lastEquipSetID = 0;
+	public SetNpcNameCmd() {
+        super("setNPCName");
+        this.addCmdString("npcname");
+    }
+
+	@Override
+	protected void _doCmd(PlayerCharacter pc, String[] words,
+			AbstractGameObject target) {
+		// Arg Count Check
+		if (words.length != 1) {
+			this.sendUsage(pc);
+			return;
+		}
+
+		if (target.getObjectType() != GameObjectType.NPC){
+			this.sendUsage(pc);
+			return;
+		}
+		
+		NPC npc = (NPC)target;
+		
+		String name = words[0];
+		
+		NPC.UpdateName(npc, name);
+		
+		WorldGrid.updateObject(npc);
+
+	}
+
+	@Override
+	protected String _getHelpString() {
+		return "Sets slot position for an NPC to 'slot'";
+	}
+
+	@Override
+	protected String _getUsageString() {
+		return "' /changeslot slot'";
+	}
+
+}
diff --git a/src/engine/devcmd/cmds/SetOwnerCmd.java b/src/engine/devcmd/cmds/SetOwnerCmd.java
new file mode 100644
index 00000000..f07241bf
--- /dev/null
+++ b/src/engine/devcmd/cmds/SetOwnerCmd.java
@@ -0,0 +1,118 @@
+package engine.devcmd.cmds;
+
+import engine.Enum.DbObjectType;
+import engine.Enum.GameObjectType;
+import engine.devcmd.AbstractDevCmd;
+import engine.gameManager.DbManager;
+import engine.objects.*;
+
+
+/**
+ *
+ * @author 
+ * Dev command to set the owner of targeted building.
+ * Argument is a valid guild UID
+ */
+public class SetOwnerCmd extends AbstractDevCmd {
+
+        Building _targetBuilding = null;
+        DbObjectType _newOwnerType;
+        AbstractCharacter outOwner;
+
+	public SetOwnerCmd() {
+        super("setowner");
+    }
+
+	@Override
+	protected void _doCmd(PlayerCharacter pcSender, String[] args,
+			AbstractGameObject target) {
+                    
+        if(validateUserInput(pcSender, target, args) == false) {
+           this.sendUsage(pcSender);
+           return;
+            }
+                        
+        // Valid arguments, attempt to set owner of Building.
+        
+        _targetBuilding = getTargetAsBuilding(pcSender);
+
+        // if it's a tol change ownership of the city
+
+        if (_targetBuilding.getBlueprint() != null &&
+            _targetBuilding.getBlueprint().getBuildingGroup().equals(engine.Enum.BuildingGroup.TOL)) {
+
+            City city = _targetBuilding.getCity();
+
+            if (city != null) {
+                city.claim(outOwner);
+                return; }
+        }
+        _targetBuilding.setOwner(outOwner);
+                
+        DbManager.BuildingQueries.SET_PROPERTY(_targetBuilding, "ownerUUID", args[0]);
+        
+        _targetBuilding.refreshGuild();
+        
+	}
+
+	@Override
+	protected String _getUsageString() {
+        return "' /setowner [UID]";
+	}
+
+	@Override
+	protected String _getHelpString() {
+        return "Assigns new owner to building";
+	}
+        
+        private boolean validateUserInput(PlayerCharacter pcSender, AbstractGameObject currTarget, String[] userInput) {
+        
+        // Incorrect number of arguments
+            
+        if (userInput.length != 1)
+         return false;    
+
+        // No target
+        
+         if (currTarget == null) {
+           throwbackError(pcSender, "Requires a Building to be targeted");
+           return false;
+         }
+         
+        // Target must be an Building
+         
+         if (currTarget.getObjectType() != GameObjectType.Building) {
+             throwbackError(pcSender, "Invalid target. Must be a Building");
+            return false; 
+         }
+         
+        // Argument must parse to a long.
+         
+         try {
+         Long.parseLong(userInput[0]); }
+        catch (NumberFormatException | NullPointerException e) {
+         return false;
+        }
+         
+         // Argument must return a valid NPC or PlayerCharacter
+         
+         _newOwnerType = DbManager.BuildingQueries.GET_UID_ENUM(Long.parseLong(userInput[0]));
+
+            switch (_newOwnerType) {
+                case NPC:
+                    outOwner = (AbstractCharacter)DbManager.getObject(GameObjectType.NPC, Integer.parseInt(userInput[0]));
+                    break;
+                case CHARACTER:
+                    outOwner = (AbstractCharacter)DbManager.getObject(GameObjectType.PlayerCharacter, Integer.parseInt(userInput[0]));
+                    break;
+            }
+
+         if (outOwner == null) {
+             throwbackError(pcSender, "Invalid Owner UID");
+             return false;
+         }
+             
+        return true;
+}
+
+}
diff --git a/src/engine/devcmd/cmds/SetPromotionClassCmd.java b/src/engine/devcmd/cmds/SetPromotionClassCmd.java
new file mode 100644
index 00000000..88239ab2
--- /dev/null
+++ b/src/engine/devcmd/cmds/SetPromotionClassCmd.java
@@ -0,0 +1,72 @@
+package engine.devcmd.cmds;
+
+import engine.Enum.DispatchChannel;
+import engine.InterestManagement.InterestManager;
+import engine.devcmd.AbstractDevCmd;
+import engine.gameManager.ChatManager;
+import engine.net.DispatchMessage;
+import engine.net.client.msg.ApplyRuneMsg;
+import engine.objects.AbstractGameObject;
+import engine.objects.PlayerCharacter;
+import engine.objects.PromotionClass;
+import engine.server.MBServerStatics;
+
+public class SetPromotionClassCmd extends AbstractDevCmd {
+
+	public SetPromotionClassCmd() {
+        super("setPromotionClass");
+    }
+
+	@Override
+	protected void _doCmd(PlayerCharacter pc, String[] words,
+			AbstractGameObject target) {
+		// Arg Count Check
+		if (words.length != 1) {
+			this.sendUsage(pc);
+			return;
+		}
+
+		int classID = 0;
+		try {
+			classID = Integer.parseInt(words[0]);
+		} catch (Exception e) {
+			throwbackError(pc,
+					"Invalid setPromotionClass Command. must specify an ID between 2504 and 2526.");
+			return;
+		}
+		if (classID < 2504 || classID > 2526 || classID == 2522) {
+			throwbackError(pc,
+					"Invalid setPromotionClass Command. must specify an ID between 2504 and 2526.");
+			return;
+		}
+		pc.setPromotionClass(classID);
+		ChatManager.chatSayInfo(pc,
+				"PromotionClass changed to " + classID);
+		InterestManager.reloadCharacter(pc);
+		this.setTarget(pc); //for logging
+
+
+		// recalculate all bonuses/formulas/skills/powers
+		pc.recalculate();
+
+		// send the rune application to the clients
+
+		PromotionClass promo = pc.getPromotionClass();
+		if (promo != null) {
+			ApplyRuneMsg arm = new ApplyRuneMsg(pc.getObjectType().ordinal(), pc.getObjectUUID(), promo.getObjectUUID(), promo.getObjectType().ordinal(), promo.getObjectUUID(), true);
+			DispatchMessage.dispatchMsgToInterestArea(pc, arm, DispatchChannel.SECONDARY, MBServerStatics.CHARACTER_LOAD_RANGE, true, false);
+		}
+
+	}
+
+	@Override
+	protected String _getHelpString() {
+		return "Sets your character's PromotionClass to 'ID'";
+	}
+
+	@Override
+	protected String _getUsageString() {
+		return "' /setPromotionClass id'";
+	}
+
+}
diff --git a/src/engine/devcmd/cmds/SetRankCmd.java b/src/engine/devcmd/cmds/SetRankCmd.java
new file mode 100644
index 00000000..6e7a43a4
--- /dev/null
+++ b/src/engine/devcmd/cmds/SetRankCmd.java
@@ -0,0 +1,142 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+
+package engine.devcmd.cmds;
+
+import engine.InterestManagement.WorldGrid;
+import engine.devcmd.AbstractDevCmd;
+import engine.gameManager.BuildingManager;
+import engine.gameManager.ChatManager;
+import engine.objects.*;
+
+public class SetRankCmd extends AbstractDevCmd {
+
+	public SetRankCmd() {
+		super("setRank");
+		this.addCmdString("setrank");
+		this.addCmdString("rank");
+	}
+
+	@Override
+	protected void _doCmd(PlayerCharacter player, String[] words,
+			AbstractGameObject target) {
+
+		int targetRank;
+		int uuid = 0;
+
+		if (words.length == 2) {
+			try {
+				uuid = Integer.parseInt(words[0]);
+				targetRank = Integer.parseInt(words[1]);
+			} catch (NumberFormatException e) {
+				this.sendUsage(player);
+				return; // NaN
+			}
+		} else if (words.length == 1) {
+			try {
+				targetRank = Integer.parseInt(words[0]);
+			} catch (NumberFormatException e) {
+				this.sendUsage(player);
+				return; // NaN
+			}
+		} else {
+			this.sendUsage(player);
+			return;
+		}
+
+		if (target != null){
+			switch(target.getObjectType()){
+			case Building:
+				Building targetBuilding = (Building)target;
+				Blueprint blueprint = targetBuilding.getBlueprint();
+
+				if (blueprint == null) {
+					targetBuilding.setRank(targetRank);
+					ChatManager.chatSayInfo(player, "Building ranked without blueprint" + targetBuilding.getObjectUUID());
+					return;
+				}
+
+				if (targetRank > blueprint.getMaxRank()) {
+					throwbackError(player, "Attempt to set Invalid Rank" + targetBuilding.getObjectUUID());
+					return;
+				}
+
+				// Set the current targetRank
+				int lastMeshID = targetBuilding.getMeshUUID();
+				targetBuilding.setRank(targetRank);
+
+				ChatManager.chatSayInfo(player, "Rank set for building with ID " + targetBuilding.getObjectUUID() + " to rank " + targetRank);
+				break;
+			case NPC:
+				NPC toRank = (NPC)target;
+				toRank.setRank(targetRank * 10);
+				toRank.setUpgradeDateTime(null);
+				WorldGrid.updateObject(toRank);
+				break;
+			case Mob:
+				Mob toRankCaptain = (Mob)target;
+				if (toRankCaptain.getContract() != null){
+					toRankCaptain.setRank(targetRank * 10);
+					Mob.setUpgradeDateTime(toRankCaptain, null);
+					WorldGrid.updateObject(toRankCaptain);
+				}
+
+				break;
+			}
+
+		}else{
+			Building targetBuilding = null;
+			if (uuid != 0)
+				targetBuilding = BuildingManager.getBuilding(uuid);
+
+			if (targetBuilding == null) {
+				throwbackError(player, "Unable to find building.");
+				return;
+			}
+
+			Blueprint blueprint = targetBuilding.getBlueprint();
+
+			if (blueprint == null) {
+				throwbackError(player, "Attempt to rank building without blueprint" + targetBuilding.getObjectUUID());
+				return;
+			}
+
+			if (targetRank > blueprint.getMaxRank()) {
+				throwbackError(player, "Attempt to set Invalid Rank" + targetBuilding.getObjectUUID());
+				return;
+			}
+
+			// Set the current targetRank
+			int lastMeshID = targetBuilding.getMeshUUID();
+			targetBuilding.setRank(targetRank);
+
+			if (lastMeshID != targetBuilding.getMeshUUID())
+				targetBuilding.refresh(true);
+			else
+				targetBuilding.refresh(false);
+
+			ChatManager.chatSayInfo(player, "Rank set for building with ID " + targetBuilding.getObjectUUID() + " to rank " + targetRank);
+		}
+
+
+
+	}
+
+	@Override
+	protected String _getHelpString() {
+		return "Sets the Rank of either the targets object or the object specified by ID.";
+	}
+
+	@Override
+	protected String _getUsageString() {
+		return "' /setrank ID rank' || ' /setrank rank' || ' /rank ID rank' || ' /rank rank'";
+	}
+
+}
diff --git a/src/engine/devcmd/cmds/SetRateCmd.java b/src/engine/devcmd/cmds/SetRateCmd.java
new file mode 100644
index 00000000..b73c0d92
--- /dev/null
+++ b/src/engine/devcmd/cmds/SetRateCmd.java
@@ -0,0 +1,98 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.devcmd.cmds;
+
+import engine.devcmd.AbstractDevCmd;
+import engine.objects.AbstractGameObject;
+import engine.objects.PlayerCharacter;
+import engine.server.MBServerStatics;
+
+
+/**
+ *
+ * @author Murray
+ *
+ */
+public class SetRateCmd extends AbstractDevCmd {
+
+	public SetRateCmd() {
+        super("setrate");
+    }
+
+	@Override
+	protected void _doCmd(PlayerCharacter pc, String[] args, AbstractGameObject target) {
+
+		if (args.length != 2) {
+			this.sendUsage(pc);
+			return;
+		}
+
+		float mod = 0f;
+
+		try {
+			mod = Float.parseFloat(args[1]);
+		} catch (NumberFormatException e) {
+			throwbackError(pc, "Supplied data failed to parse to Float.");
+			return;
+		}
+
+
+		if (args[0].equals("exp")){
+
+			MBServerStatics.EXP_RATE_MOD = mod;
+			throwbackInfo(pc, "Experience Rate set to: " + mod);
+
+		} else if (args[0].equals("gold")){
+
+			MBServerStatics.GOLD_RATE_MOD = mod;
+			throwbackInfo(pc, "Gold Rate set to: " + mod);
+
+		} else if (args[0].equals("drop")){
+
+			MBServerStatics.DROP_RATE_MOD = mod;
+			throwbackInfo(pc, "Drop Multiplier Rate set to: " + mod);
+
+		} else if (args[0].equals("hotexp")){
+
+			MBServerStatics.HOT_EXP_RATE_MOD = mod;
+			throwbackInfo(pc, "HOTZONE Experience Rate set to: " + mod);
+
+		} else if (args[0].equals("hotgold")){
+
+			MBServerStatics.HOT_GOLD_RATE_MOD = mod;
+			throwbackInfo(pc, "HOTZONE Gold Rate set to: " + mod);
+
+		} else if (args[0].equals("hotdrop")){
+
+			MBServerStatics.HOT_DROP_RATE_MOD = mod;
+			throwbackInfo(pc, "HOTZONE Drop Multiplier Rate set to: " + mod);
+
+		} else if (args[0].equals("production")){
+
+			MBServerStatics.PRODUCTION_TIME_MULTIPLIER = mod;
+			throwbackInfo(pc, "Production Time Multiplier Rate set to: " + mod);
+
+		} else {
+			this.sendUsage(pc);
+		}
+
+	}
+
+	@Override
+	protected String _getUsageString() {
+        return "' /setrate {exp|gold|drop|hotexp|hotgold|hotdrop} rate'";
+	}
+
+	@Override
+	protected String _getHelpString() {
+        return "Sets the rates for exp, gold or drops. Accepts a float, defaults to 1.0";
+	}
+
+}
\ No newline at end of file
diff --git a/src/engine/devcmd/cmds/SetRuneCmd.java b/src/engine/devcmd/cmds/SetRuneCmd.java
new file mode 100644
index 00000000..99bd3b2d
--- /dev/null
+++ b/src/engine/devcmd/cmds/SetRuneCmd.java
@@ -0,0 +1,80 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.devcmd.cmds;
+
+import engine.InterestManagement.InterestManager;
+import engine.devcmd.AbstractDevCmd;
+import engine.gameManager.ChatManager;
+import engine.objects.AbstractGameObject;
+import engine.objects.CharacterRune;
+import engine.objects.PlayerCharacter;
+
+/**
+ *
+ * @author Eighty
+ *
+ */
+public class SetRuneCmd extends AbstractDevCmd {
+
+	public SetRuneCmd() {
+        super("setRune");
+    }
+
+	@Override
+	protected void _doCmd(PlayerCharacter pcSender, String[] args,
+			AbstractGameObject target) {
+		int runeID = 0;
+		boolean add = true;
+		try {
+			runeID = Integer.parseInt(args[0]);
+			if (args.length > 1)
+				add = (args[1].toLowerCase().equals("false")) ? false : true;
+		} catch (NumberFormatException e) {
+			this.sendUsage(pcSender);
+			return;
+		}
+		if (runeID < 3001 || runeID > 3049) {
+			throwbackError(pcSender,
+					"Invalid setrune Command. must specify an ID between 3001 and 3048.");
+			return;
+		}
+		boolean worked = false;
+		if (add) {
+			worked = CharacterRune.grantRune(pcSender, runeID);
+			if (worked)
+				ChatManager.chatSayInfo(pcSender,
+						"rune of ID " + runeID + " added");
+			else
+				throwbackError(pcSender, "Failed to add the rune of type "
+						+ runeID);
+		} else {
+			worked = CharacterRune.removeRune(pcSender, runeID);
+			if (worked) {
+				ChatManager.chatSayInfo(pcSender,
+						"rune of ID " + runeID + " removed");
+				InterestManager.reloadCharacter(pcSender);
+			} else
+				throwbackError(pcSender, "Failed to remove the rune of type "
+						+ runeID);
+		}
+		this.setTarget(pcSender); //for logging
+	}
+
+	@Override
+	protected String _getUsageString() {
+        return "' /setrune runeID [true/false]'";
+	}
+
+	@Override
+	protected String _getHelpString() {
+        return "Grant or remove the rune with the specified runeID";
+	}
+
+}
diff --git a/src/engine/devcmd/cmds/SetStaminaCmd.java b/src/engine/devcmd/cmds/SetStaminaCmd.java
new file mode 100644
index 00000000..e0cde8e7
--- /dev/null
+++ b/src/engine/devcmd/cmds/SetStaminaCmd.java
@@ -0,0 +1,68 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+
+package engine.devcmd.cmds;
+
+import engine.Enum.DispatchChannel;
+import engine.devcmd.AbstractDevCmd;
+import engine.net.DispatchMessage;
+import engine.net.client.msg.TargetedActionMsg;
+import engine.objects.AbstractGameObject;
+import engine.objects.PlayerCharacter;
+import engine.server.MBServerStatics;
+
+
+public class SetStaminaCmd extends AbstractDevCmd {
+
+	public SetStaminaCmd() {
+        super("setStamina");
+        this.addCmdString("stamina");
+    }
+
+	@Override
+	protected void _doCmd(PlayerCharacter pc, String[] words,
+			AbstractGameObject target) {
+		// Arg Count Check
+		if (words.length != 1) {
+			this.sendUsage(pc);
+			return;
+		}
+
+		float amount = 0.0f;
+		try {
+			amount = Float.parseFloat(words[0]);
+			pc.setStamina(amount, pc);
+			this.setTarget(pc); //for logging
+
+			// Update all surrounding clients.
+			TargetedActionMsg cmm = new TargetedActionMsg(pc);
+			DispatchMessage.dispatchMsgToInterestArea(pc, cmm, DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, true, false);
+
+		} catch (NumberFormatException e) {
+			this.throwbackError(pc, "Supplied data: " + words[0]
+					+ " failed to parse to a Float.");
+		} catch (Exception e) {
+			this.throwbackError(pc,
+					"An unknown exception occurred while attempting to setStamina to "
+							+ words[0]);
+		}
+	}
+
+	@Override
+	protected String _getHelpString() {
+		return "Sets your character's Stamina to 'amount'";
+	}
+
+	@Override
+	protected String _getUsageString() {
+		return "' /setStamina amount'";
+	}
+
+}
diff --git a/src/engine/devcmd/cmds/SetSubRaceCmd.java b/src/engine/devcmd/cmds/SetSubRaceCmd.java
new file mode 100644
index 00000000..78227780
--- /dev/null
+++ b/src/engine/devcmd/cmds/SetSubRaceCmd.java
@@ -0,0 +1,209 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+
+package engine.devcmd.cmds;
+
+
+import engine.Enum.GameObjectType;
+import engine.devcmd.AbstractDevCmd;
+import engine.net.DispatchMessage;
+import engine.net.client.msg.ApplyBuildingEffectMsg;
+import engine.net.client.msg.UpdateCharOrMobMessage;
+import engine.objects.*;
+
+public class SetSubRaceCmd extends AbstractDevCmd {
+
+	public SetSubRaceCmd() {
+        super("setSubRace");
+        this.addCmdString("subrace");
+    }
+
+	@Override
+	protected void _doCmd(PlayerCharacter pc, String[] words,
+			AbstractGameObject target) {
+		
+		if (target instanceof AbstractCharacter){
+
+			if (words[0].equals("race")){
+				if (target.getObjectType() != GameObjectType.PlayerCharacter)
+					return;
+				PlayerCharacter player = (PlayerCharacter)target;
+				int raceID = Integer.parseInt(words[1]);
+				player.setSubRaceID(raceID);
+				if (raceID == 0)
+					raceID = player.getRaceID();
+				UpdateCharOrMobMessage ucm = new UpdateCharOrMobMessage(player, 1,raceID);
+				DispatchMessage.sendToAllInRange(player, ucm);
+				return;
+			}
+			if (words[0].equals("all")){
+				for (int i = 15999; i< 16337;i++){
+					ApplyBuildingEffectMsg applyBuildingEffectMsg = new ApplyBuildingEffectMsg(4, 0, target.getObjectType().ordinal(), target.getObjectUUID(), i);
+					DispatchMessage.sendToAllInRange((AbstractWorldObject) target, applyBuildingEffectMsg);
+					try {
+						Thread.sleep(500);
+					} catch (InterruptedException e) {
+						// TODO Auto-generated catch block
+						e.printStackTrace();	
+					}
+				}
+
+			}else{
+				ApplyBuildingEffectMsg applyBuildingEffectMsg = new ApplyBuildingEffectMsg(4, 0, target.getObjectType().ordinal(), target.getObjectUUID(), Integer.parseInt(words[0]));
+				DispatchMessage.sendToAllInRange((AbstractWorldObject) target, applyBuildingEffectMsg);
+			}
+
+			return;
+		}
+
+		Building building = (Building)target;
+
+		building.removeAllVisualEffects();
+		building.addEffectBit(1<<Integer.parseInt(words[0]));
+		building.updateEffects();
+		//63535 38751
+		//		Zone zone = ZoneManager.findSmallestZone(pc.getLoc());
+		//		//CityZoneMsg czm = new CityZoneMsg(3, zone.getLoc().x, zone.getLoc().y, zone.getLoc().z, "balls", zone, 0, 0);
+		//		pc.getClientConnection().sendMsg(new DeleteItemMsg(zone.getObjectType().ordinal(), zone.getObjectUUID()));
+
+		//		UpdateInventoryMsg updateInventoryMsg = new UpdateInventoryMsg(new ArrayList<>(), new ArrayList<>(), null, true);
+		//		pc.getClientConnection().sendMsg(updateInventoryMsg);
+
+		//pc.getCharItemManager().updateInventory();
+
+
+
+		//		Mob mob = (Mob)target;
+		//
+		//		if (mob == null)
+		//			return;
+		//
+		//		MobLoot mobLoot = new MobLoot(mob, ItemBase.getItemBase(Integer.parseInt(words[0])), false);
+		//
+		//		mob.getCharItemManager().addItemToInventory(mobLoot);
+
+
+
+
+		//		if (target.getObjectType() != GameObjectType.Building)
+		//			return;
+		//
+		//		Building warehouseBuilding = (Building)target;
+		//
+		//		if (Warehouse.warehouseByBuildingUUID.get(warehouseBuilding.getObjectUUID()) == null)
+		//			return;
+		//
+		//		Warehouse warehouse = Warehouse.warehouseByBuildingUUID.get(warehouseBuilding.getObjectUUID());
+		//
+		//		for (int ibID: Warehouse.getMaxResources().keySet()){
+		//			ItemBase ib = ItemBase.getItemBase(ibID);
+		//			warehouse.depositFromMine(null, ib, Warehouse.getMaxResources().get(ibID));
+		//		}
+
+
+		//		int raceID = Integer.parseInt(words[0]);
+		//
+		//		UpdateCharOrMobMessage ucm = new UpdateCharOrMobMessage(pc, raceID);
+		//
+		//		pc.getClientConnection().sendMsg(ucm);
+
+		//		LoadCharacterMsg lcm = new LoadCharacterMsg((AbstractCharacter)null,false);
+		//		try {
+		//			DispatchMessage.sendToAllInRange(pc, lcm);
+		//		} catch (MsgSendException e) {
+		//			// TODO Auto-generated catch block
+		//			e.printStackTrace();
+		//		}
+		//		ModifyHealthMsg mhm =new ModifyHealthMsg(pc, pc, -50f, 0f, 0f, 0, null, 9999, 0);
+		//		mhm.setOmitFromChat(1);
+		//		pc.getClientConnection().sendMsg(mhm);
+		//
+		//		int temp = 0;
+		//		boolean start = false;
+		//
+		//		for (EffectsBase eb: EffectsBase.getAllEffectsBase()){
+		//
+		//
+		//
+		//			if (!pc.getClientConnection().isConnected()){
+		//				Logger.info("", "PLAYER DC!" + eb.getIDString());
+		//				break;
+		//			}
+		//
+		//			eb = PowersManager.getEffectByIDString("WLR-018A");
+		//
+		//
+		//			NoTimeJob ntj = new NoTimeJob(pc, "NONE", eb, 40);
+		//			pc.addEffect(String.valueOf(eb.getUUID()), 1000, ntj, eb, 40);
+		//			eb.sendEffectNoPower(ntj, 1000, pc.getClientConnection());
+		//
+		//			ThreadUtils.sleep(500);
+		//			pc.clearEffects();
+		//
+		//			//WorldServer.updateObject((AbstractWorldObject)target, pc);
+		//			this.throwbackInfo(pc, eb.getIDString());
+		//			break;
+		//			//ThreadUtils.sleep(500);
+		//
+		//		}
+
+
+
+
+
+
+
+
+
+		//		for (EffectsBase eb : EffectsBase.getAllEffectsBase()){
+		//			if (eb.getToken() == 0)
+		//				continue;
+		//			int token = eb.getToken();
+		//			ApplyEffectMsg pum = new ApplyEffectMsg();
+		//			pum.setEffectID(token);
+		//			pum.setSourceType(pc.getObjectType().ordinal());
+		//			pum.setSourceID(pc.getObjectUUID());
+		//			pum.setTargetType(pc.getObjectType().ordinal());
+		//			pum.setTargetID(pc.getObjectUUID());
+		//			pum.setNumTrains(40);
+		//			pum.setDuration(-1);
+		////			pum.setDuration((pb.isChant()) ? (int)pb.getChantDuration() : ab.getDurationInSeconds(trains));
+		//			pum.setPowerUsedID(0);
+		//		//	pum.setPowerUsedName("Inflict Poison");
+		//			this.throwbackInfo(pc, eb.getName() + "Token = " + eb.getToken());
+		//			pc.getClientConnection().sendMsg(pum);
+		//			ThreadUtils.sleep(200);
+		//		}
+		//
+
+		//		 UpdateObjectMsg uom = new UpdateObjectMsg(pc, 4);
+		//         try {
+		//             Location.sendToAllInRange(pc, uom);
+		//         } catch (MsgSendException e) {
+		//             // TODO Auto-generated catch block
+		//             e.printStackTrace();
+		//         }
+
+
+
+
+	}
+
+	@Override
+	protected String _getHelpString() {
+		return "Temporarily Changes SubRace";
+	}
+
+	@Override
+	protected String _getUsageString() {
+		return "' /subrace mobBaseID";
+	}
+
+}
diff --git a/src/engine/devcmd/cmds/ShowOffsetCmd.java b/src/engine/devcmd/cmds/ShowOffsetCmd.java
new file mode 100644
index 00000000..45fc2ae6
--- /dev/null
+++ b/src/engine/devcmd/cmds/ShowOffsetCmd.java
@@ -0,0 +1,57 @@
+package engine.devcmd.cmds;
+
+import engine.Enum.GameObjectType;
+import engine.devcmd.AbstractDevCmd;
+import engine.math.Vector3fImmutable;
+import engine.objects.AbstractGameObject;
+import engine.objects.Building;
+import engine.objects.PlayerCharacter;
+
+public class ShowOffsetCmd extends AbstractDevCmd {
+      
+	public ShowOffsetCmd() {
+        super("showoffset");
+    }
+
+        // AbstractDevCmd Overridden methods
+        
+	@Override
+	protected void _doCmd(PlayerCharacter pc, String[] args,
+			AbstractGameObject target)  {
+
+       
+          Building targetBuilding;
+          String outString;
+          Vector3fImmutable offset;
+          
+          String newline = "\r\n ";
+          targetBuilding = (Building)target;
+         
+            if (targetBuilding.getObjectType() != GameObjectType.Building) {
+                throwbackInfo(pc, "showgate: target must be an Building");
+                return;
+            }
+  
+           offset = pc.getLoc().subtract(targetBuilding.getLoc());
+           
+           outString = "Location: " + pc.getLoc().x + "x " + pc.getLoc().z + 'y';
+           outString += newline;     
+           outString += "Offset: " + offset.x + "x " + offset.y + 'y';
+           throwbackInfo(pc, outString);
+        }
+
+	@Override
+	protected String _getHelpString() {
+        return "Shows offset to current building";
+	}
+
+	@Override
+	protected String _getUsageString() {
+
+
+        return "Shows offset to current building";
+	}
+
+ 
+        
+}
diff --git a/src/engine/devcmd/cmds/SlotNpcCmd.java b/src/engine/devcmd/cmds/SlotNpcCmd.java
new file mode 100644
index 00000000..264c3ced
--- /dev/null
+++ b/src/engine/devcmd/cmds/SlotNpcCmd.java
@@ -0,0 +1,155 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+
+package engine.devcmd.cmds;
+
+import engine.Enum.BuildingGroup;
+import engine.Enum.GameObjectType;
+import engine.devcmd.AbstractDevCmd;
+import engine.gameManager.ChatManager;
+import engine.gameManager.DbManager;
+import engine.objects.AbstractGameObject;
+import engine.objects.Contract;
+import engine.objects.PlayerCharacter;
+import engine.util.StringUtils;
+import org.pmw.tinylog.Logger;
+
+/**
+ * Summary: Game designer utility command to add or 
+ *      remove building slot access for contracts
+ */
+ 
+public class SlotNpcCmd extends AbstractDevCmd {
+      
+	public SlotNpcCmd() {
+        super("slotnpc");
+    }
+
+        
+        // AbstractDevCmd Overridden methods
+        
+	@Override
+	protected void _doCmd(PlayerCharacter pc, String[] args,
+			AbstractGameObject target)  {
+
+          Contract contractObject;
+          BuildingGroup buildingGroup;
+          
+          long slotBitvalue;
+          String outString;
+         
+            if (target.getObjectType() != GameObjectType.NPC) {
+                throwbackInfo(pc, "NpcSlot: target must be an NPC");
+                return;
+            }
+
+            // Get the contract from the npc
+            contractObject = getTargetAsNPC(pc).getContract();
+            
+            // User requests list of current groups.
+            
+            if (args[0].toUpperCase().equals("LIST")) {
+
+                outString = "Current: " + contractObject.getAllowedBuildings();
+                
+                throwbackInfo(pc, outString);
+                return;
+            }
+
+            if(validateUserInput(args) == false) {
+                this.sendUsage(pc);
+		 return;
+            }
+                     
+         // Extract the building group flag from user input
+            
+         buildingGroup = BuildingGroup.valueOf(args[0].toUpperCase());
+            
+         switch (args[1].toUpperCase()) {
+             
+             case "ON":
+                 contractObject.getAllowedBuildings().add(buildingGroup);
+                 
+                 if (!DbManager.ContractQueries.updateAllowedBuildings(contractObject, contractObject.getAllowedBuildings().toLong())){
+                	 Logger.error( "Failed to update Database for Contract Allowed buildings");
+                	 ChatManager.chatSystemError(pc, "Failed to update Database for Contract Allowed buildings. " +
+                	 		"Contact A CCR, oh wait, you are a CCR. You're Fubared.");
+                	 return;
+                 }
+
+                 throwbackInfo(pc, "SlotNpc " + buildingGroup.name() + " added to npc");
+                 break;
+             case "OFF":
+                contractObject.getAllowedBuildings().remove(buildingGroup);
+                 if (!DbManager.ContractQueries.updateAllowedBuildings(contractObject, contractObject.getAllowedBuildings().toLong())){
+                	 Logger.error( "Failed to update Database for Contract Allowed buildings");
+                	 ChatManager.chatSystemError(pc, "Failed to update Database for Contract Allowed buildings. " +
+                	 		"Contact A CCR, oh wait, you are a CCR. You're Fubared.");
+                	 return;
+                 }
+
+                 throwbackInfo(pc, "SlotNpc " + buildingGroup.name() + " removed from npc");
+                 break;
+         }
+            
+        }
+
+	@Override
+	protected String _getHelpString() {
+        return "Sets a building slot on a targeted npc";
+	}
+
+	@Override
+	protected String _getUsageString() {
+		String usage = "/npcslot [BuildingType] on-off \n";
+                
+                for (BuildingGroup group:BuildingGroup.values()) {
+                    usage += group.name() + ' ';
+                }
+                
+                usage = StringUtils.wordWrap(usage, 30);
+
+		return usage;
+	}
+
+        // Class methods
+        
+        private static boolean validateUserInput(String[] userInput) {
+
+        int stringIndex;
+        BuildingGroup testGroup;
+        
+        testGroup = BuildingGroup.FORGE;
+        
+        String commandSet = "onoff";
+        
+        // incorrect number of arguments test
+        
+        if (userInput.length > 2)
+         return false;    
+  
+            
+        // Test of toggle argument
+        
+        stringIndex = commandSet.indexOf(userInput[1].toLowerCase());
+                
+        if (stringIndex == -1)
+         return false;
+        
+        // Validate we have a corrent building group name
+        
+        for (BuildingGroup group:BuildingGroup.values()) {
+            if (group.name().equals(userInput[0].toUpperCase()))
+                return true;
+                    }
+        return false;
+        }
+        
+}
diff --git a/src/engine/devcmd/cmds/SplatMobCmd.java b/src/engine/devcmd/cmds/SplatMobCmd.java
new file mode 100644
index 00000000..4d660692
--- /dev/null
+++ b/src/engine/devcmd/cmds/SplatMobCmd.java
@@ -0,0 +1,166 @@
+package engine.devcmd.cmds;
+
+import engine.devcmd.AbstractDevCmd;
+import engine.gameManager.ZoneManager;
+import engine.math.Vector3fImmutable;
+import engine.objects.AbstractGameObject;
+import engine.objects.Mob;
+import engine.objects.PlayerCharacter;
+import engine.objects.Zone;
+
+import java.util.concurrent.locks.ReadWriteLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+/**
+ * @author
+ * Summary: Game designer utility command to create multiple
+ *        mobiles of a given UUID within a supplied range
+ */
+
+public class SplatMobCmd extends AbstractDevCmd {
+
+	// Instance variables
+
+	private int _mobileUUID;
+	private int _mobileCount;
+	private float _targetRange;
+	private Vector3fImmutable _currentLocation;
+
+	// Concurrency support
+
+	private ReadWriteLock lock = new ReentrantReadWriteLock();
+
+	// Constructor
+
+	public SplatMobCmd() {
+        super("splatmob");
+    }
+
+
+	// AbstractDevCmd Overridden methods
+
+	@Override
+	protected void _doCmd(PlayerCharacter pc, String[] args,
+			AbstractGameObject target) {
+
+		// Member variables
+
+		Vector3fImmutable mobileLocation;
+		Mob mobile;
+		Zone serverZone;
+
+		// Concurrency write lock due to instance variable usage
+
+		lock.writeLock().lock();
+
+		try {
+
+			// Validate user input
+
+			if(validateUserInput(args) == false) {
+				this.sendUsage(pc);
+				return;
+			}
+
+			// Parse user input
+
+			parseUserInput(args);
+
+			// Arguments have been validated and parsed at this point
+			// Begin creating mobiles
+
+			_currentLocation = pc.getLoc();
+			serverZone = ZoneManager.findSmallestZone(_currentLocation);
+
+			for(int i=0;i<_mobileCount;i++) {
+
+				mobile = Mob.createMob(_mobileUUID,
+						Vector3fImmutable.getRandomPointInCircle(_currentLocation, _targetRange),
+						null, true, serverZone,null,0);
+
+				if (mobile != null) {
+					mobile.updateDatabase();
+				}
+			}
+
+		} // End Try Block
+
+		// Release Reentrant lock
+
+		finally {
+			lock.writeLock().unlock();
+		}
+	}
+
+	@Override
+	protected String _getHelpString() {
+        return "Spawns multiple mobiles with a given range";
+	}
+
+	@Override
+	protected String _getUsageString() {
+        return "/splatmob UUID [Count <= 100] [range <= 1200]";
+	}
+
+	// Class methods
+
+	private static boolean validateUserInput(String[] userInput) {
+
+		// incorrect number of arguments test
+
+		if (userInput.length != 3)
+			return false;
+
+		// Test for UUID conversion to int
+
+		try {
+			Integer.parseInt(userInput[0]); }
+		catch (NumberFormatException | NullPointerException e) {
+			return false;
+		}
+
+
+		// Test for Number of Mobs conversion to int
+
+		try {
+			Integer.parseInt(userInput[1]); }
+		catch (NumberFormatException | NullPointerException e) {
+			return false;
+		}
+
+		// Test if range argument can convert to a float
+
+		try {
+			Float.parseFloat(userInput[2]); }
+		catch (NumberFormatException | NullPointerException e) {
+			return false;
+		}
+
+		return true;
+	}
+
+	private void parseUserInput(String[] userInput) {
+
+		// Clear previous values
+
+		_mobileUUID = 0;
+		_mobileCount = 0;
+		_targetRange = 0f;
+
+		// Parse first argument into mobile UID.
+
+		_mobileUUID = Integer.parseInt(userInput[0]);
+
+		// Parse second argument into mobile count. Cap at 100 mobs.
+
+		_mobileCount = Integer.parseInt(userInput[1]);
+		_mobileCount = Math.min(_mobileCount, 100);
+
+		// Parse third argument into range. Cap at 200 units.
+
+		_targetRange = Float.parseFloat(userInput[2]);
+		_targetRange = Math.min(_targetRange, 1200f);
+
+	}
+
+}
\ No newline at end of file
diff --git a/src/engine/devcmd/cmds/SqlDebugCmd.java b/src/engine/devcmd/cmds/SqlDebugCmd.java
new file mode 100644
index 00000000..18087692
--- /dev/null
+++ b/src/engine/devcmd/cmds/SqlDebugCmd.java
@@ -0,0 +1,86 @@
+package engine.devcmd.cmds;
+
+import engine.devcmd.AbstractDevCmd;
+import engine.objects.AbstractGameObject;
+import engine.objects.PlayerCharacter;
+import engine.server.MBServerStatics;
+
+/**
+ * @author 
+ * Summary: Devcmd to toggle logging of mysql statements
+ *   
+ */
+ 
+public class SqlDebugCmd extends AbstractDevCmd {
+
+    // Instance variables
+    
+             
+	public SqlDebugCmd() {
+        super("sqldebug");
+    }
+
+        
+        // AbstractDevCmd Overridden methods
+        
+	@Override
+	protected void _doCmd(PlayerCharacter pc, String[] args,
+			AbstractGameObject target) {
+            
+            Boolean debugState = false;
+
+            if(validateUserInput(args) == false) {
+                this.sendUsage(pc);
+		 return;
+            }
+            
+        // Arguments have been validated use argument to set debug state
+            
+            switch (args[0]) {
+                case "on":
+                  debugState = true;
+                  break;
+                case "off":
+                  debugState = false;
+                  break;
+                default:
+                  break;
+            }
+            
+            MBServerStatics.DB_ENABLE_QUERY_OUTPUT = debugState;
+         
+         // Send results to user
+         throwbackInfo(pc, "SQL debug state: " + debugState.toString());
+        }
+
+	@Override
+	protected String _getHelpString() {
+        return "Toggles sending SQL statements to log";
+	}
+
+	@Override
+	protected String _getUsageString() {
+        return "/sqldebug on|off";
+	}
+
+        // Class methods
+        
+        private static boolean validateUserInput(String[] userInput) {
+
+        int stringIndex;
+        String commandSet = "onoff";
+        
+        // incorrect number of arguments test
+        
+        if (userInput.length != 1)
+         return false;    
+
+        // Validate arguments
+        
+        stringIndex = commandSet.indexOf(userInput[0].toLowerCase());
+
+            return stringIndex != -1;
+        }
+        
+  
+}
diff --git a/src/engine/devcmd/cmds/SummonCmd.java b/src/engine/devcmd/cmds/SummonCmd.java
new file mode 100644
index 00000000..c0a1c7d1
--- /dev/null
+++ b/src/engine/devcmd/cmds/SummonCmd.java
@@ -0,0 +1,114 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+
+package engine.devcmd.cmds;
+
+import engine.devcmd.AbstractDevCmd;
+import engine.gameManager.SessionManager;
+import engine.gameManager.ZoneManager;
+import engine.math.Vector3fImmutable;
+import engine.net.client.msg.RecvSummonsRequestMsg;
+import engine.objects.AbstractGameObject;
+import engine.objects.PlayerCharacter;
+import engine.objects.Zone;
+
+public class SummonCmd extends AbstractDevCmd {
+
+	public SummonCmd() {
+        super("summon");
+    }
+
+	@Override
+	protected void _doCmd(PlayerCharacter pc, String[] args,
+			AbstractGameObject target) {
+		// Arg Count Check
+		if (args.length != 1) {
+			this.sendUsage(pc);
+			return;
+		}
+		PlayerCharacter pcToSummon = null;
+
+
+		if (args[0].equalsIgnoreCase("all")){
+			for (PlayerCharacter toSummon: SessionManager.getAllActivePlayerCharacters()){
+				Zone zone = ZoneManager.findSmallestZone(pc.getLoc());
+				String location = "Somewhere";
+				if (zone != null)
+					location = zone.getName();
+				RecvSummonsRequestMsg rsrm = new RecvSummonsRequestMsg(pc.getObjectType().ordinal(), pc.getObjectUUID(), pc.getFirstName(),
+						location, false);
+				toSummon.getClientConnection().sendMsg(rsrm);
+
+			}
+			return;
+		}
+		// 1-9 numeric digits, must be playerID
+		if (args[0].matches("\\d{1,9}?")) {
+			try {
+				int playerID = Integer.parseInt(args[0]);
+				pcToSummon = SessionManager
+						.getPlayerCharacterByID(playerID);
+
+				if (pcToSummon == null) {
+					this.throwbackError(pc, "Character not found by ID: "
+							+ playerID);
+					return;
+				}
+			} catch (NumberFormatException e) {
+				this.throwbackError(pc, "Supplied ID: '" + args[0]
+						+ "' failed to parse to an INT");
+				return;
+
+			} catch (Exception e) {
+				this.throwbackError(pc,
+						"An unknown exception occurred while attempting to summon '"
+								+ args[0] + "'by ID");
+				return;
+			}
+
+		} else { // player name
+			try {
+				pcToSummon = SessionManager
+						.getPlayerCharacterByLowerCaseName(args[0]);
+				if (pcToSummon == null) {
+					this.throwbackError(pc, "Character not found by name: "
+							+ args[0]);
+					return;
+				}
+			} catch (Exception e) {
+				this.throwbackError(pc,
+						"An unknown exception occurred while attempting to summon '"
+								+ args[0] + "'by name");
+				return;
+			}
+		}
+		this.setTarget(pcToSummon); //for logging
+
+		Vector3fImmutable loc = pc.getLoc();
+		pcToSummon.teleport(loc);
+
+		this.throwbackInfo(pc, "Player " + pcToSummon.getCombinedName()
+		+ " has been summoned to your location.");
+		this.throwbackInfo(pcToSummon,
+				"You have been transported to another location.");
+	}
+
+	@Override
+	protected String _getHelpString() {
+		return "Summons 'character' TO your current position.  Can summon by character's first name or by the character's characterID.";
+
+	}
+
+	@Override
+	protected String _getUsageString() {
+		return "' /summon characterName' || ' /summon characterID'";
+	}
+
+}
diff --git a/src/engine/devcmd/cmds/SysMsgCmd.java b/src/engine/devcmd/cmds/SysMsgCmd.java
new file mode 100644
index 00000000..4004655e
--- /dev/null
+++ b/src/engine/devcmd/cmds/SysMsgCmd.java
@@ -0,0 +1,67 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.devcmd.cmds;
+
+import engine.devcmd.AbstractDevCmd;
+import engine.gameManager.ChatManager;
+import engine.objects.AbstractGameObject;
+import engine.objects.PlayerCharacter;
+
+/**
+ * 
+ */
+
+public class SysMsgCmd extends AbstractDevCmd {
+
+	public SysMsgCmd() {
+        super("sysmsg");
+    }
+
+	@Override
+	protected void _doCmd(PlayerCharacter pcSender, String[] args,
+			AbstractGameObject target) {
+		if (args.length != 1 || args[0].isEmpty()) {
+			this.sendUsage(pcSender);
+			return;
+		}
+		String text = "[System Admin Message]: " + args[0];
+		ChatManager.chatSystemChannel(text);
+	}
+
+	/**
+	 * This function is called by the DevCmdManager. Override to avoid splitting
+	 * argString into String array, since sysmsg displays full String as
+	 * message, then calls the subclass specific _doCmd method.
+	 */
+
+	@Override
+	public void doCmd(PlayerCharacter pcSender, String argString,
+			AbstractGameObject target) {
+		String[] args = new String[1];
+		args[0] = argString;
+
+		if (pcSender == null) {
+			return;
+		}
+
+		this._doCmd(pcSender, args, target);
+	}
+
+	@Override
+	protected String _getUsageString() {
+		return "' /sysmsg message'";
+	}
+
+	@Override
+	protected String _getHelpString() {
+		return "Send system message in chat window to all players";
+	}
+
+}
diff --git a/src/engine/devcmd/cmds/TeleportModeCmd.java b/src/engine/devcmd/cmds/TeleportModeCmd.java
new file mode 100644
index 00000000..630f3824
--- /dev/null
+++ b/src/engine/devcmd/cmds/TeleportModeCmd.java
@@ -0,0 +1,56 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.devcmd.cmds;
+
+import engine.devcmd.AbstractDevCmd;
+import engine.objects.AbstractGameObject;
+import engine.objects.PlayerCharacter;
+
+public class TeleportModeCmd extends AbstractDevCmd {
+
+	public TeleportModeCmd() {
+        super("teleportMode");
+    }
+
+	@Override
+	protected void _doCmd(PlayerCharacter pc, String[] words,
+			AbstractGameObject target) {
+		boolean newTeleportMode = false;
+		if (words.length == 0) { // toggle
+			newTeleportMode = !pc.isTeleportMode();
+
+		} else if (words[0].equalsIgnoreCase("on")) {
+			newTeleportMode = true;
+
+		} else if (words[0].equalsIgnoreCase("off")) {
+			newTeleportMode = false;
+
+		} else {
+			this.sendUsage(pc);
+			return;
+		}
+		pc.setTeleportMode(newTeleportMode);
+		this.setTarget(pc); //for logging
+		String output = (newTeleportMode ? "on" : "off");
+
+		throwbackInfo(pc, "Teleport mode is now '" + output + "'.");
+	}
+
+	@Override
+	protected String _getHelpString() {
+		return "'on' enables teleport mode, 'off' disables.  No arguments to the /teleportMode command will toggle the setting.";
+	}
+
+	@Override
+	protected String _getUsageString() {
+		return "' /teleportMode [on | off]'";
+	}
+
+}
diff --git a/src/engine/devcmd/cmds/UnloadFurnitureCmd.java b/src/engine/devcmd/cmds/UnloadFurnitureCmd.java
new file mode 100644
index 00000000..4197a800
--- /dev/null
+++ b/src/engine/devcmd/cmds/UnloadFurnitureCmd.java
@@ -0,0 +1,102 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.devcmd.cmds;
+
+import engine.Enum.GameObjectType;
+import engine.devcmd.AbstractDevCmd;
+import engine.net.client.msg.LoadStructureMsg;
+import engine.net.client.msg.MoveToPointMsg;
+import engine.net.client.msg.UnloadObjectsMsg;
+import engine.objects.AbstractGameObject;
+import engine.objects.AbstractWorldObject;
+import engine.objects.Building;
+import engine.objects.PlayerCharacter;
+
+/**
+ * @author
+ *
+ */
+public class UnloadFurnitureCmd extends AbstractDevCmd {
+
+	public UnloadFurnitureCmd() {
+        super("furniture");
+    }
+
+	@Override
+	protected void _doCmd(PlayerCharacter pc, String[] words,
+			AbstractGameObject target) {
+
+		if (target.getObjectType() != GameObjectType.Building){
+			this.throwbackError(pc, "Must be targeting a building to load/unload furniture.");
+			return;
+		}
+		if (words[0].equalsIgnoreCase("unload")){
+
+			UnloadObjectsMsg uom = new UnloadObjectsMsg();
+			for (AbstractWorldObject awo: pc.getLoadedStaticObjects()){
+				if (awo.getObjectType() != GameObjectType.Building)
+					continue;
+
+				Building awoBuilding = (Building)awo;
+
+                if (!awoBuilding.isFurniture)
+					continue;
+
+				if (awoBuilding.parentBuildingID != target.getObjectUUID())
+					continue;
+				
+				MoveToPointMsg msg = new MoveToPointMsg(awoBuilding);
+				pc.getClientConnection().sendMsg(msg);
+
+				uom.addObject(awoBuilding);
+
+
+			}
+
+			pc.getClientConnection().sendMsg(uom);
+
+		}else if (words[0].equalsIgnoreCase("load")){
+			LoadStructureMsg lsm = new LoadStructureMsg();
+
+			for (AbstractWorldObject awo: pc.getLoadedStaticObjects()){
+				if (awo.getObjectType() != GameObjectType.Building)
+					continue;
+
+				Building awoBuilding = (Building)awo;
+
+                if (!awoBuilding.isFurniture)
+					continue;
+
+				if (awoBuilding.parentBuildingID != target.getObjectUUID())
+					continue;
+
+				lsm.addObject(awoBuilding);
+
+
+			}
+
+			pc.getClientConnection().sendMsg(lsm);
+
+		}
+	}
+
+	@Override
+	protected String _getHelpString() {
+		String help = "Enchants an item with a prefix and suffix";
+		return help;
+	}
+
+	@Override
+	protected String _getUsageString() {
+		String usage = "' /enchant clear/Enchant1 Enchant2 Enchant3 ...'";
+		return usage;
+	}
+
+}
diff --git a/src/engine/devcmd/cmds/ZoneInfoCmd.java b/src/engine/devcmd/cmds/ZoneInfoCmd.java
new file mode 100644
index 00000000..0cc7c2f2
--- /dev/null
+++ b/src/engine/devcmd/cmds/ZoneInfoCmd.java
@@ -0,0 +1,150 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.devcmd.cmds;
+
+import engine.devcmd.AbstractDevCmd;
+import engine.gameManager.ZoneManager;
+import engine.objects.AbstractGameObject;
+import engine.objects.City;
+import engine.objects.PlayerCharacter;
+import engine.objects.Zone;
+import engine.util.StringUtils;
+
+import java.util.ArrayList;
+
+
+/**
+ * @author
+ *
+ */
+public class ZoneInfoCmd extends AbstractDevCmd {
+
+	public ZoneInfoCmd() {
+        super("zoneinfo");
+    }
+
+	@Override
+	protected void _doCmd(PlayerCharacter player, String[] words,
+			AbstractGameObject target) {
+		// Arg Count Check
+		Zone zone = null;
+
+		if (player == null) {
+			throwbackError(player, "Unable to find the pc making the request.");
+			return;
+		}
+
+		try {
+			int targetID = Integer.parseInt(words[0]);
+
+			//try get zone by objectUUID
+			zone = ZoneManager.getZoneByUUID(targetID);
+
+			//that failed, try get by zoneID
+			if (zone == null)
+				zone = ZoneManager.getZoneByZoneID(targetID);
+
+			//no zone found, so fail
+			if (zone == null) {
+				throwbackError(player, "Zone with ID " + targetID + "not found");
+				return;
+			}
+		} catch (Exception e) {
+			zone = ZoneManager.findSmallestZone(player.getLoc());
+		}
+
+		if (zone == null) {
+			throwbackError(player, "Zone not found");
+			return;
+		}
+
+		String newline = "\r\n ";
+
+
+		int objectUUID = zone.getObjectUUID();
+		String output;
+
+		output = "Target Information:" + newline;
+		output += StringUtils.addWS("UUID: " + objectUUID, 20);
+		output += newline;
+		output += "name: " + zone.getName();
+		output += newline;
+		output += "loadNum: " + zone.getLoadNum();
+		if (zone.getParent() != null) {
+			output += StringUtils.addWS(", parent: " + zone.getParent().getObjectUUID(), 30);
+			output += "Parentabs: x: " + zone.getParent().getAbsX() + ", y: " + zone.getParent().getAbsY() + ", z: " + zone.getParent().getAbsZ();
+
+		} else
+			output += StringUtils.addWS(", parent: none", 30);
+		output += newline;
+		output += "absLoc: x: " + zone.getAbsX() + ", y: " + zone.getAbsY() + ", z: " + zone.getAbsZ();
+		output += newline;
+		output += "offset: x: " + zone.getXCoord() + ", y: " + zone.getYCoord() + ", z: " + zone.getZCoord();
+		output += newline;
+		output += "radius: x: " + zone.getBounds().getHalfExtents().x + ", z: " + zone.getBounds().getHalfExtents().y;
+		output += newline;
+
+		if (zone.getHeightMap() != null){
+			output += "HeightMap ID: " + zone.getHeightMap().getHeightMapID();
+			output += newline;
+			output += "Bucket Width X : " + zone.getHeightMap().getBucketWidthX();
+			output += newline;
+			output += "Bucket Width Y : " + zone.getHeightMap().getBucketWidthY();
+
+		}
+		output += "radius: x: " + zone.getBounds().getHalfExtents().x + ", z: " + zone.getBounds().getHalfExtents().y;
+		output += newline;
+		//		output += "minLvl = " + zone.getMinLvl() + " | maxLvl = " + zone.getMaxLvl();
+		output += newline;
+		output += "Sea Level = " +zone.getSeaLevel();
+		output += newline;
+		output += "World Altitude = " + zone.getWorldAltitude();
+		throwbackInfo(player, output);
+
+		City city = ZoneManager.getCityAtLocation(player.getLoc());
+
+		output += newline;
+		output += (city == null)? "None" : city.getParent().getName();
+
+		if (city != null ) {
+
+			if (city.isLocationOnCityGrid(player.getLoc()))
+				output += " (Grid)";
+			else if (city.isLocationOnCityZone(player.getLoc()))
+				output += " (Zone)";
+			else if (city.isLocationWithinSiegeBounds(player.getLoc()))
+				output += " (Siege)";
+		} else {
+			output = "children:";
+
+			ArrayList<Zone> nodes = zone.getNodes();
+
+			if (nodes.isEmpty())
+				output += " none";
+
+			for (Zone child : nodes) {
+				output += newline;
+				output += child.getName() + " (" + child.getLoadNum() + ')';
+			}
+		}
+		throwbackInfo(player, output);
+	}
+
+	@Override
+	protected String _getHelpString() {
+		return "Gets information on an Object.";
+	}
+
+	@Override
+	protected String _getUsageString() {
+		return "' /info targetID'";
+	}
+
+}
diff --git a/src/engine/devcmd/cmds/convertLoc.java b/src/engine/devcmd/cmds/convertLoc.java
new file mode 100644
index 00000000..8190c6ee
--- /dev/null
+++ b/src/engine/devcmd/cmds/convertLoc.java
@@ -0,0 +1,62 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+
+package engine.devcmd.cmds;
+
+import engine.Enum.GameObjectType;
+import engine.devcmd.AbstractDevCmd;
+import engine.gameManager.ChatManager;
+import engine.gameManager.ZoneManager;
+import engine.math.Vector3fImmutable;
+import engine.objects.AbstractGameObject;
+import engine.objects.Building;
+import engine.objects.PlayerCharacter;
+
+public class convertLoc extends AbstractDevCmd {
+
+	public convertLoc() {
+        super("convertLoc");
+    }
+
+	@Override
+	protected void _doCmd(PlayerCharacter pc, String[] words,
+			AbstractGameObject target) {
+
+
+		if (target == null){
+			Vector3fImmutable convertLoc = ZoneManager.findSmallestZone(pc.getLoc()).getLoc().subtract(pc.getLoc());
+			ChatManager.chatSystemInfo(pc, Vector3fImmutable.toString(convertLoc));
+			return;
+		}
+
+
+		if (target.getObjectType() != GameObjectType.Building)
+			return;
+		Building toConvert = (Building)target;
+		Vector3fImmutable convertedLoc = ZoneManager.convertWorldToLocal(toConvert, pc.getLoc());
+		ChatManager.chatSystemInfo(pc, Vector3fImmutable.toString(convertedLoc));
+
+
+
+
+	}
+
+	@Override
+	protected String _getHelpString() {
+		return "Temporarily Changes SubRace";
+	}
+
+	@Override
+	protected String _getUsageString() {
+		return "' /setBuildingCollidables add/remove 'add creates a collision line.' needs 4 integers. startX, endX, startY, endY";
+
+	}
+
+}
diff --git a/src/engine/devcmd/cmds/setOpenDateCmd.java b/src/engine/devcmd/cmds/setOpenDateCmd.java
new file mode 100644
index 00000000..a74e9857
--- /dev/null
+++ b/src/engine/devcmd/cmds/setOpenDateCmd.java
@@ -0,0 +1,94 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.devcmd.cmds;
+
+import engine.Enum.BuildingGroup;
+import engine.Enum.GameObjectType;
+import engine.devcmd.AbstractDevCmd;
+import engine.gameManager.BuildingManager;
+import engine.objects.*;
+
+public class setOpenDateCmd extends AbstractDevCmd {
+
+	public setOpenDateCmd() {
+		super("minedate");
+	}
+
+	@Override
+	protected void _doCmd(PlayerCharacter pcSender, String[] words,
+			AbstractGameObject target) {
+		
+		
+		if (words[0].equalsIgnoreCase("list")){
+			for (int buildingID : Mine.towerMap.keySet()){
+				Building building = BuildingManager.getBuildingFromCache(buildingID);
+				if (building == null){
+					this.throwbackError(pcSender, "null building for ID " + buildingID);
+					continue;
+				}
+				
+				Zone zone = building.getParentZone();
+				
+				Zone parentZone = zone.getParent();
+				
+				Mine mine = Mine.towerMap.get(buildingID);
+				this.throwbackInfo(pcSender, "Mine UUID : " + mine.getObjectUUID() + " Mine Type: " + zone.getName() + " Zone : " + parentZone.getName()
+				+ " Open Date :  " + mine.openDate);
+			}
+				
+		}
+	if (target == null){
+		this.throwbackError(pcSender, "null target");
+		return;
+	}
+	if (target.getObjectType().equals(GameObjectType.Building) == false){
+		this.throwbackError(pcSender, "target must be object type building");
+		return;
+	}
+
+	Building building = (Building)target;
+	if (building.getBlueprint() == null){
+		this.throwbackError(pcSender, "null blueprint");
+		return;
+	}
+	
+	if (building.getBlueprint().getBuildingGroup().equals(BuildingGroup.MINE) == false){
+		
+			this.throwbackError(pcSender, "target not mine");
+			return;
+	}
+	
+	Mine mine = Mine.getMineFromTower(building.getObjectUUID());
+	
+	if (mine == null){
+		this.throwbackError(pcSender, "null mine");
+		return;
+	}
+	int days = Integer.parseInt(words[0]);
+	int hours = Integer.parseInt(words[1]);
+
+		mine.openDate = mine.openDate.plusDays(days).plusHours(hours);
+		
+		this.throwbackInfo(pcSender, "Mine Open Date Changed to " + mine.openDate.toString());
+	}
+
+
+	@Override
+	protected String _getUsageString() {
+		return "' /bounds'";
+	}
+
+	@Override
+	protected String _getHelpString() {
+		return "Audits all the mobs in a zone.";
+
+	}
+
+}
diff --git a/src/engine/exception/FactoryBuildException.java b/src/engine/exception/FactoryBuildException.java
new file mode 100644
index 00000000..e0293036
--- /dev/null
+++ b/src/engine/exception/FactoryBuildException.java
@@ -0,0 +1,32 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.exception;
+
+public class FactoryBuildException extends MBServerException {
+
+	private static final long serialVersionUID = 2331961867931593523L;
+
+	public FactoryBuildException() {
+		super();
+	}
+
+	public FactoryBuildException(String arg0, Throwable arg1) {
+		super(arg0, arg1);
+	}
+
+	public FactoryBuildException(String arg0) {
+		super(arg0);
+	}
+
+	public FactoryBuildException(Throwable arg0) {
+		super(arg0);
+	}
+
+}
diff --git a/src/engine/exception/MBServerException.java b/src/engine/exception/MBServerException.java
new file mode 100644
index 00000000..3ede5360
--- /dev/null
+++ b/src/engine/exception/MBServerException.java
@@ -0,0 +1,38 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.exception;
+
+import org.pmw.tinylog.Logger;
+
+
+public abstract class MBServerException extends Exception {
+	private static final long serialVersionUID = 3878845236025977250L;
+
+	public MBServerException() {
+		super();
+	}
+
+	public MBServerException(String arg0, Throwable arg1) {
+		super(arg0, arg1);
+	}
+
+	public MBServerException(String arg0) {
+		super(arg0);
+	}
+
+	public MBServerException(Throwable arg0) {
+		super(arg0);
+	}
+
+	public void logException(String origin) {
+		Logger.error(origin, this.getClass().getSimpleName() + "(1): " + this.getMessage());
+
+	}
+}
diff --git a/src/engine/exception/MsgSendException.java b/src/engine/exception/MsgSendException.java
new file mode 100644
index 00000000..b84fcd33
--- /dev/null
+++ b/src/engine/exception/MsgSendException.java
@@ -0,0 +1,31 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+ package engine.exception;
+
+public class MsgSendException extends MBServerException {
+	private static final long serialVersionUID = 6927044139998382254L;
+
+	public MsgSendException() {
+		super();
+	}
+
+	public MsgSendException(String arg0, Throwable arg1) {
+		super(arg0, arg1);
+	}
+
+	public MsgSendException(String arg0) {
+		super(arg0);
+	}
+
+	public MsgSendException(Throwable arg0) {
+		super(arg0);
+	}
+
+}
diff --git a/src/engine/exception/SerializationException.java b/src/engine/exception/SerializationException.java
new file mode 100644
index 00000000..0dd0066c
--- /dev/null
+++ b/src/engine/exception/SerializationException.java
@@ -0,0 +1,31 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+ package engine.exception;
+
+public class SerializationException extends MBServerException {
+	private static final long serialVersionUID = 6927044139998382254L;
+
+	public SerializationException() {
+		super();
+	}
+
+	public SerializationException(String arg0, Throwable arg1) {
+		super(arg0, arg1);
+	}
+
+	public SerializationException(String arg0) {
+		super(arg0);
+	}
+
+	public SerializationException(Throwable arg0) {
+		super(arg0);
+	}
+
+}
diff --git a/src/engine/gameManager/BuildingManager.java b/src/engine/gameManager/BuildingManager.java
new file mode 100644
index 00000000..79511e56
--- /dev/null
+++ b/src/engine/gameManager/BuildingManager.java
@@ -0,0 +1,636 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+
+package engine.gameManager;
+
+import engine.Enum;
+import engine.Enum.BuildingGroup;
+import engine.Enum.GameObjectType;
+import engine.InterestManagement.WorldGrid;
+import engine.job.JobContainer;
+import engine.job.JobScheduler;
+import engine.jobs.UpgradeBuildingJob;
+import engine.math.Bounds;
+import engine.math.Vector3fImmutable;
+import engine.net.client.msg.ErrorPopupMsg;
+import engine.objects.*;
+import org.pmw.tinylog.Logger;
+
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.util.concurrent.ThreadLocalRandom;
+
+public enum BuildingManager {
+
+    BUILDINGMANAGER;
+
+    public static boolean playerCanManage(PlayerCharacter player, Building building) {
+
+        if (player == null)
+            return false;
+
+        if (building == null)
+            return false;
+
+
+        if (building.getRank() == -1)
+            return false;
+
+        if (IsOwner(building, player))
+            return true;
+
+        //individual friend.
+        if (building.getFriends().get(player.getObjectUUID()) != null)
+            return true;
+
+        //Admin's can access stuff
+
+        if (player.isCSR())
+            return true;
+
+        //Guild stuff
+
+
+        if (building.getGuild() != null && building.getGuild().isGuildLeader(player.getObjectUUID()))
+            return true;
+
+        if (building.getFriends().get(player.getGuild().getObjectUUID()) != null
+                && building.getFriends().get(player.getGuild().getObjectUUID()).getFriendType() == 8)
+            return true;
+
+        if (building.getFriends().get(player.getGuild().getObjectUUID()) != null
+                && building.getFriends().get(player.getGuild().getObjectUUID()).getFriendType() == 9
+                && GuildStatusController.isInnerCouncil(player.getGuildStatus()))
+            return true;
+
+        if (Guild.sameGuild(building.getGuild(), player.getGuild()) && GuildStatusController.isInnerCouncil(player.getGuildStatus()))
+            return true;
+
+        if (Guild.sameGuild(building.getGuild(), player.getGuild()) && GuildStatusController.isGuildLeader(player.getGuildStatus()))
+            return true;
+
+        return false;
+
+        //TODO test friends list once added
+        //does not meet above criteria. Cannot access.
+    }
+
+    public static boolean playerCanManageNotFriends(PlayerCharacter player, Building building) {
+
+        //Player Can only Control Building if player is in Same Guild as Building and is higher rank than IC.
+
+        if (player == null)
+            return false;
+
+        if (building == null)
+            return false;
+
+
+        if (building.getRank() == -1)
+            return false;
+
+        if (IsOwner(building, player))
+            return true;
+
+        //Somehow guild leader check fails? lets check if Player is true Guild GL.
+        if (building.getGuild() != null && building.getGuild().isGuildLeader(player.getObjectUUID()))
+            return true;
+        if (GuildStatusController.isGuildLeader(player.getGuildStatus()) == false && GuildStatusController.isInnerCouncil(player.getGuildStatus()) == false)
+            return false;
+
+
+        return false;
+
+    }
+
+    public static synchronized boolean lootBuilding(PlayerCharacter player, Building building) {
+
+        if (building == null)
+            return false;
+
+        if (player == null)
+            return false;
+
+        if (building.getRank() != -1)
+            return false;
+
+        if (building.getBlueprintUUID() == 0)
+            return false;
+
+        switch (building.getBlueprint().getBuildingGroup()) {
+            case SHRINE:
+                Shrine shrine = Shrine.shrinesByBuildingUUID.get(building.getObjectUUID());
+                if (shrine == null)
+                    return false;
+
+                int amount = shrine.getFavors();
+                //no more favors too loot!
+                if (amount == 0) {
+
+                    try {
+                        ErrorPopupMsg.sendErrorPopup(player, 166);
+                    } catch (Exception e) {
+
+                    }
+                    return false;
+                }
+
+                ItemBase elanIB = ItemBase.getItemBase(1705032);
+
+                if (elanIB == null)
+                    return false;
+
+                if (!player.getCharItemManager().hasRoomInventory(elanIB.getWeight()))
+                    return false;
+
+                if (!Item.MakeItemForPlayer(elanIB, player, amount))
+                    return false;
+
+                shrine.setFavors(0);
+                break;
+            case WAREHOUSE:
+
+                Warehouse warehouse = Warehouse.warehouseByBuildingUUID.get(building.getObjectUUID());
+
+                if (warehouse == null)
+                    return false;
+
+                for (ItemBase resourceBase : ItemBase.getResourceList()) {
+                    if (!player.getCharItemManager().hasRoomInventory(resourceBase.getWeight())) {
+                        ChatManager.chatSystemInfo(player, "You can not carry any more of that item.");
+                        return false;
+                    }
+                    if (warehouse.getResources().get(resourceBase) == null)
+                        continue;
+
+                    int resourceAmount = warehouse.getResources().get(resourceBase);
+
+                    if (resourceAmount <= 0)
+                        continue;
+
+                    if (warehouse.loot(player, resourceBase, resourceAmount, true))
+                        ChatManager.chatInfoInfo(player, "You have looted " + resourceAmount + ' ' + resourceBase.getName());
+                }
+                break;
+
+        }
+        //Everything was looted, Maybe we should
+        return true;
+    }
+
+    //This method restarts an upgrade timer when a building is loaded from the database.
+    // Submit upgrade job for this building based upon it's current upgradeDateTime
+
+    public static void submitUpgradeJob(Building building) {
+
+        if (building == null)
+            return;
+
+
+        if (building.getUpgradeDateTime() == null) {
+            Logger.error("Attempt to submit upgrade job for non-ranking building");
+            return;
+        }
+
+        // Submit upgrade job for future date or current instant
+
+        if (building.getUpgradeDateTime().isAfter(LocalDateTime.now())) {
+            JobContainer jc = JobScheduler.getInstance().scheduleJob(new UpgradeBuildingJob(building),
+                    building.getUpgradeDateTime().atZone(ZoneId.systemDefault())
+                            .toInstant().toEpochMilli());
+        } else
+            JobScheduler.getInstance().scheduleJob(new UpgradeBuildingJob(building), 0);
+    }
+
+    public static void setUpgradeDateTime(Building building, LocalDateTime upgradeDateTime, int rankCost) {
+
+        if (building == null)
+            return;
+        if (!DbManager.BuildingQueries.updateBuildingUpgradeTime(upgradeDateTime, building, rankCost)) {
+            Logger.error("Failed to set upgradeTime for building " + building.getObjectUUID());
+            return;
+        }
+
+        building.upgradeDateTime = upgradeDateTime;
+
+    }
+
+    // Method transfers ownership of all hirelings in a building
+
+    public static void refreshHirelings(Building building) {
+
+        if (building == null)
+            return;
+
+        Guild newGuild;
+
+        if (building.getOwner() == null)
+            newGuild = Guild.getErrantGuild();
+        else
+            newGuild = building.getOwner().getGuild();
+
+        for (AbstractCharacter hireling : building.getHirelings().keySet()) {
+            hireling.setGuild(newGuild);
+            WorldGrid.updateObject(hireling);
+        }
+
+    }
+
+    public static void cleanupHirelings(Building building) {
+
+        // Early exit:  Cannot have hirelings in a building
+        // without a blueprint.
+
+        if (building.getBlueprintUUID() == 0)
+            return;
+
+        // Remove all hirelings for destroyed buildings
+
+        if (building.getRank() < 1) {
+
+            for (AbstractCharacter slottedNPC : building.getHirelings().keySet()) {
+
+                if (slottedNPC.getObjectType() == Enum.GameObjectType.NPC)
+                    ((NPC) slottedNPC).remove();
+                else if (slottedNPC.getObjectType() == Enum.GameObjectType.Mob)
+                    ((Mob) slottedNPC).remove(building);
+            }
+            return;
+        }
+
+        // Delete hireling if building has deranked.
+        for (AbstractCharacter hireling : building.getHirelings().keySet()) {
+
+            NPC npc = null;
+            Mob mob = null;
+
+            if (hireling.getObjectType() == Enum.GameObjectType.NPC)
+                npc = (NPC) hireling;
+            else if (hireling.getObjectType() == Enum.GameObjectType.Mob)
+                mob = (Mob) hireling;
+
+            if (building.getHirelings().get(hireling) > building.getBlueprint().getSlotsForRank(building.getRank()))
+
+                if (npc != null) {
+                    if (!npc.remove())
+                        Logger.error("Failed to remove npc " + npc.getObjectUUID()
+                                + "from Building " + building.getObjectUUID());
+                    else
+                        building.getHirelings().remove(npc);
+                } else if (mob != null) {
+                    if (!mob.remove(building))
+                        Logger.error("Failed to remove npc " + npc.getObjectUUID()
+                                + "from Building " + building.getObjectUUID());
+                    else
+                        building.getHirelings().remove(npc);
+                }
+
+        }
+
+        refreshHirelings(building);
+    }
+
+    public static Building getBuilding(int id) {
+
+        if (id == 0)
+            return null;
+
+        Building building;
+
+        building = (Building) DbManager.getFromCache(Enum.GameObjectType.Building, id);
+
+        if (building != null)
+            return building;
+
+        return DbManager.BuildingQueries.GET_BUILDINGBYUUID(id);
+
+    }
+
+    public static boolean PlayerCanControlNotOwner(Building building, PlayerCharacter player) {
+
+        if (player == null)
+            return false;
+
+        if (building == null)
+            return false;
+
+        if (building.getOwner() == null)
+            return false;
+
+        //lets pass true if player is owner anyway.
+        if (building.getOwner().equals(player))
+            return true;
+
+        if (player.getGuild().isErrant())
+            return false;
+
+        if (building.getGuild().isGuildLeader(player.getObjectUUID()))
+            return true;
+
+        if (!Guild.sameGuild(building.getGuild(), player.getGuild()))
+            return false;
+
+        return GuildStatusController.isGuildLeader(player.getGuildStatus()) != false || GuildStatusController.isInnerCouncil(player.getGuildStatus()) != false;
+    }
+
+    //This is mainly used for Rolling and gold sharing between building and warehouse.
+
+    public static int GetWithdrawAmountForRolling(Building building, int cost) {
+
+        //all funds are available to roll.
+
+        if (cost <= GetAvailableGold(building))
+            return cost;
+
+        // cost is more than available gold, return available gold
+
+        return GetAvailableGold(building);
+
+    }
+
+    public static int GetAvailableGold(Building building) {
+
+        if (building.getStrongboxValue() == 0)
+            return 0;
+
+        if (building.getStrongboxValue() < building.reserve)
+            return 0;
+
+        return building.getStrongboxValue() - building.reserve;
+    }
+
+    public static int GetOverdraft(Building building, int cost) {
+        int availableGold = GetWithdrawAmountForRolling(building, cost);
+        return cost - availableGold;
+    }
+
+    public static boolean IsPlayerHostile(Building building, PlayerCharacter player) {
+
+        //Nation Members and Guild members are not hostile.
+        //		if (building.getGuild() != null){
+        //			if (pc.getGuild() != null)
+        //			if (building.getGuild().getObjectUUID() == pc.getGuildUUID()
+        //			|| pc.getGuild().getNation().getObjectUUID() == building.getGuild().getNation().getObjectUUID())
+        //				return false;
+        //		}
+        if (Guild.sameNationExcludeErrant(building.getGuild(), player.getGuild()))
+            return false;
+
+        if (!building.reverseKOS) {
+
+            Condemned condemn = building.getCondemned().get(player.getObjectUUID());
+
+            if (condemn != null && condemn.isActive())
+                return true;
+
+            if (player.getGuild() != null) {
+
+                Condemned guildCondemn = building.getCondemned().get(player.getGuild().getObjectUUID());
+
+                if (guildCondemn != null && guildCondemn.isActive())
+                    return true;
+
+                Condemned nationCondemn = building.getCondemned().get(player.getGuild().getNation().getObjectUUID());
+                return nationCondemn != null && nationCondemn.isActive() && nationCondemn.getFriendType() == Condemned.NATION;
+            } else {
+                //TODO ADD ERRANT KOS CHECK
+            }
+        } else {
+
+            Condemned condemn = building.getCondemned().get(player.getObjectUUID());
+
+            if (condemn != null && condemn.isActive())
+                return false;
+
+            if (player.getGuild() != null) {
+
+                Condemned guildCondemn = building.getCondemned().get(player.getGuild().getObjectUUID());
+
+                if (guildCondemn != null && guildCondemn.isActive())
+                    return false;
+
+                Condemned nationCondemn = building.getCondemned().get(player.getGuild().getNation().getObjectUUID());
+                return nationCondemn == null || !nationCondemn.isActive() || nationCondemn.getFriendType() != Condemned.NATION;
+            } else {
+                //TODO ADD ERRANT KOS CHECK
+            }
+            return true;
+        }
+
+        //When we get to here, This means The building was not reverse KOS
+        //and passed the hostile test.
+
+        return false;
+    }
+
+    public static final synchronized boolean addHirelingForWorld(Building building, PlayerCharacter contractOwner, Vector3fImmutable NpcLoc, Zone zone, Contract NpcID, int rank) {
+
+        String pirateName = NPC.getPirateName(NpcID.getMobbaseID());
+
+        NPC npc = null;
+
+        npc = NPC.createNPC( pirateName, NpcID.getObjectUUID(), NpcLoc, null, false, zone, (short) rank, false, building);
+
+        if (npc == null)
+            return false;
+
+
+        npc.setBuilding(building);
+        npc.setParentZone(zone);
+        WorldGrid.addObject(npc, contractOwner);
+
+        return true;
+
+    }
+
+    public static synchronized boolean addHireling(Building building, PlayerCharacter contractOwner, Vector3fImmutable NpcLoc, Zone zone, Contract NpcID, Item item) {
+
+        int rank = 1;
+
+        if (building.getBlueprintUUID() == 0)
+            return false;
+
+        if (building.getBlueprint().getMaxSlots() == building.getHirelings().size())
+            return false;
+
+        String pirateName = NPC.getPirateName(NpcID.getMobbaseID());
+
+        if (item.getChargesRemaining() > 0)
+            rank = item.getChargesRemaining() * 10;
+        else rank = 10;
+
+        Mob mob = null;
+        NPC npc = null;
+
+        if (NPC.ISGuardCaptain(NpcID.getContractID())) {
+
+            mob = Mob.createMob( NpcID.getMobbaseID(), NpcLoc, contractOwner.getGuild(), true, zone, building, NpcID.getContractID());
+
+            if (mob == null)
+                return false;
+
+            mob.setRank(rank);
+            mob.setPlayerGuard(true);
+            mob.setParentZone(zone);
+            return true;
+        } else {
+            npc = NPC.createNPC( pirateName, NpcID.getObjectUUID(), NpcLoc, contractOwner.getGuild(), false, zone, (short) rank, false, building);
+
+            if (npc == null)
+                return false;
+
+
+            npc.setBindLoc(NpcLoc.x - zone.getAbsX(), NpcLoc.y - zone.getAbsY(), NpcLoc.z - zone.getAbsZ());
+
+            npc.setBuilding(building);
+            npc.setParentZone(zone);
+
+            if (NPC.GetNPCProfits(npc) == null)
+                NPCProfits.CreateProfits(npc);
+
+            WorldGrid.addObject(npc, contractOwner);
+
+            return true;
+        }
+    }
+
+    public static boolean IsWallPiece(Building building) {
+
+        if (building.getBlueprint() == null)
+            return false;
+
+        BuildingGroup buildingGroup = building.getBlueprint().getBuildingGroup();
+
+        switch (buildingGroup) {
+            case WALLSTRAIGHT:
+            case WALLCORNER:
+            case SMALLGATE:
+            case ARTYTOWER:
+            case WALLSTRAIGHTTOWER:
+            case WALLSTAIRS:
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    public static Building getBuildingFromCache(int id) {
+        return (Building) DbManager.getFromCache(GameObjectType.Building, id);
+    }
+
+    public static boolean IsOwner(Building building, PlayerCharacter player) {
+        if (building == null || player == null)
+            return false;
+        
+        if (building.getOwner() == null)
+        	return false;
+        
+
+        return building.getOwner().getObjectUUID() == player.getObjectUUID();
+
+    }
+
+    public static float GetMissingHealth(Building building) {
+        return building.healthMax - building.getCurrentHitpoints();
+    }
+
+    public static int GetRepairCost(Building building) {
+        return (int) (GetMissingHealth(building) * .10f);
+    }
+
+    public static Regions GetRegion(Building building, float x, float y, float z) {
+        if (building.getBounds() == null)
+            return null;
+
+        if (building.getBounds().getRegions() == null)
+            return null;
+
+        Regions currentRegion = null;
+        for (Regions region : building.getBounds().getRegions()) {
+
+            if (region.isPointInPolygon(new Vector3fImmutable(x, y, z))) {
+                if (y > (region.highLerp.y - 5))
+                    currentRegion = region;
+            }
+        }
+        return currentRegion;
+    }
+
+    public static Regions GetRegion(Building building, int room, int level, float x, float z) {
+        if (building.getBounds() == null)
+            return null;
+
+        if (building.getBounds().getRegions() == null)
+            return null;
+
+        for (Regions region : building.getBounds().getRegions()) {
+
+            if (region.getLevel() != level)
+                continue;
+            if (region.getRoom() != room)
+                continue;
+
+            if (region.isPointInPolygon(new Vector3fImmutable(x, 0, z))) {
+                return region;
+            }
+        }
+        return null;
+    }
+    
+    public static Vector3fImmutable GetBindLocationForBuilding(Building building){
+   
+    		Vector3fImmutable bindLoc = null;
+    		
+    		if (building == null)
+    			return Enum.Ruins.getRandomRuin().getLocation();
+    		
+    		
+    		bindLoc = building.getLoc();
+    		
+    	
+    			float radius = Bounds.meshBoundsCache.get(building.getMeshUUID()).radius;
+    			if ( building.getRank() == 8){
+    				bindLoc = building.getStuckLocation();
+    				if (bindLoc != null)
+    					return bindLoc;
+    			}
+
+        			float x = bindLoc.getX();
+        			float z = bindLoc.getZ();
+        			float offset = ((ThreadLocalRandom.current().nextFloat() * 2) - 1) * radius;
+        			int direction = ThreadLocalRandom.current().nextInt(4);
+
+        			switch (direction) {
+        			case 0:
+        				x += radius;
+        				z += offset;
+        				break;
+        			case 1:
+        				x += offset;
+        				z -= radius;
+        				break;
+        			case 2:
+        				x -= radius;
+        				z += offset;
+        				break;
+        			case 3:
+        				x += offset;
+        				z += radius;
+        				break;
+        			}
+        			bindLoc = new Vector3fImmutable(x, bindLoc.getY(), z);
+    		
+			return bindLoc;
+
+    		
+    	
+    }
+
+}
diff --git a/src/engine/gameManager/ChatManager.java b/src/engine/gameManager/ChatManager.java
new file mode 100644
index 00000000..758c12a7
--- /dev/null
+++ b/src/engine/gameManager/ChatManager.java
@@ -0,0 +1,1231 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.gameManager;
+
+import engine.Enum;
+import engine.Enum.GameObjectType;
+import engine.Enum.ModType;
+import engine.Enum.SourceType;
+import engine.InterestManagement.WorldGrid;
+import engine.db.archive.BaneRecord;
+import engine.db.archive.PvpRecord;
+import engine.net.Dispatch;
+import engine.net.DispatchMessage;
+import engine.net.MessageDispatcher;
+import engine.net.client.ClientConnection;
+import engine.net.client.Protocol;
+import engine.net.client.msg.chat.*;
+import engine.net.client.msg.commands.ClientAdminCommandMsg;
+import engine.objects.*;
+import engine.server.MBServerStatics;
+import engine.server.world.WorldServer;
+import engine.session.Session;
+import org.pmw.tinylog.Logger;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
+public enum ChatManager {
+
+    CHATMANAGER;
+
+    // Sending a quantity of (FLOOD_QTY_THRESHOLD) messages within
+    // (FLOOD_TIME_THRESHOLD) ms will flag as a flood message
+    // Example, set to 3 & 2000 to flag the 3rd message within 2000 ms.
+
+    private static final int FLOOD_QTY_THRESHOLD = 3;
+    private static final int FLOOD_TIME_THRESHOLD = 2000;
+    private static final String FLOOD_USER_ERROR = "You talk too much!";
+    private static final String SILENCED = "You find yourself mute!";
+    private static final String UNKNOWN_COMMAND = "No such command.";
+    /**
+     * This method used when handling a ChatMsg received from the network.
+     */
+    public static void handleChatMsg(Session sender, AbstractChatMsg msg) {
+
+        if (msg == null) {
+            Logger.warn(
+                    "null message: ");
+            return;
+        }
+
+        if (sender == null) {
+            Logger.warn(
+                    "null sender: ");
+            return;
+        }
+
+        PlayerCharacter pc = sender.getPlayerCharacter();
+
+        if (pc == null) {
+            Logger.warn(
+                    "invalid sender: ");
+            return;
+        }
+
+        Protocol protocolMsg = msg.getProtocolMsg();
+
+        // Flood control, implemented per channel
+
+        boolean isFlood = false;
+        long curMsgTime = System.currentTimeMillis();
+        long checkTime = pc.chatFloodTime(protocolMsg.opcode, curMsgTime, FLOOD_QTY_THRESHOLD - 1);
+
+        if ((checkTime > 0L) && (curMsgTime - checkTime < FLOOD_TIME_THRESHOLD))
+            isFlood = true;
+
+        switch (protocolMsg) {
+            case CHATSAY:
+                ChatManager.chatSay(pc, msg.getMessage(), isFlood);
+                return;
+            case CHATCSR:
+                ChatManager.chatCSR(msg);
+                return;
+            case CHATTELL:
+                ChatTellMsg ctm = (ChatTellMsg) msg;
+                ChatManager.chatTell(pc, ctm.getTargetName(), ctm.getMessage(), isFlood);
+                return;
+            case CHATSHOUT:
+                ChatManager.chatShout(pc, msg.getMessage(), isFlood);
+                return;
+            case CHATGUILD:
+                ChatManager.chatGuild(pc, (ChatGuildMsg) msg);
+                return;
+            case CHATGROUP:
+                ChatManager.chatGroup(pc, (ChatGroupMsg) msg);
+                return;
+            case CHATIC:
+                ChatManager.chatIC(pc, (ChatICMsg) msg);
+                return;
+            case LEADERCHANNELMESSAGE:
+            	ChatManager.chatGlobal(pc, msg.getMessage(), isFlood);
+            	return;
+            case GLOBALCHANNELMESSAGE:
+            case CHATPVP:
+            case CHATCITY:
+            case CHATINFO:
+            case SYSTEMBROADCASTCHANNEL:
+            default:
+        }
+
+    }
+
+    /*
+     * Channels
+     */
+    /*
+     * CSR
+     */
+    public static void chatCSR(AbstractChatMsg msg) {
+        PlayerCharacter sender = (PlayerCharacter) msg.getSource();
+        if (sender == null)
+            return;
+        //		if (promotionClass == null)
+        //			return false;
+        //		int promotionClassID = promotionClass.getUUID();
+        //		switch (promotionClassID) {
+        //		case 2901:
+        //		case 2902:
+        //		case 2903:
+        //		case 2904:
+        //		case 2910:
+        //			return true;
+        //		}
+        //		return false;
+        if (!sender.isCSR) {
+            ChatManager.chatSystemError(sender, UNKNOWN_COMMAND);
+            return;
+        }
+        ArrayList<AbstractWorldObject> distroList = new ArrayList<>();
+        for (PlayerCharacter pc : SessionManager
+                .getAllActivePlayerCharacters()) {
+            if (pc.getAccount().status.equals(Enum.AccountStatus.BANNED) == false)
+                distroList.add(pc);
+        }
+        // Send dispatch to each player
+
+        for (AbstractWorldObject abstractWorldObject : distroList) {
+            PlayerCharacter playerCharacter = (PlayerCharacter) abstractWorldObject;
+            Dispatch dispatch = Dispatch.borrow(playerCharacter, msg);
+            DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+        }
+    }
+
+    public static boolean testSilenced(PlayerCharacter pc) {
+
+        PlayerBonuses bonus = pc.getBonuses();
+
+        if (bonus != null && bonus.getBool(ModType.Silenced, SourceType.None)) {
+            ChatManager.chatSayError(pc, SILENCED);
+            return true;
+        }
+        return false;
+    }
+
+    /*
+     * Say
+     */
+    public static void chatSay(AbstractWorldObject player, String text, boolean isFlood) {
+
+        PlayerCharacter pcSender = null;
+
+        if (player.getObjectType() == GameObjectType.PlayerCharacter)
+            pcSender = (PlayerCharacter) player;
+
+        // Parser eats all dev commands
+
+        if (isFlood) {
+            ChatManager.chatSayError(pcSender, FLOOD_USER_ERROR);
+            return;
+        }
+
+        if (ChatManager.isDevCommand(text) == true) {
+            ChatManager.processDevCommand(player, text);
+            return;
+        }
+
+        if (ChatManager.isUpTimeRequest(text) == true) {
+            sendSystemMessage(pcSender, WorldServer.getUptimeString());
+            return;
+        }
+
+        if (ChatManager.isVersionRequest(text) == true) {
+            sendSystemMessage(pcSender,  ConfigManager.MB_WORLD_GREETING.getValue());
+            return;
+        }
+
+        if (ChatManager.isNetStatRequest(text) == true) {
+            sendSystemMessage(pcSender, MessageDispatcher.getNetstatString());
+            return;
+        }
+
+        // Needs to be refactored
+
+        if (ChatManager.isPopulationRequest(text) == true) {
+            sendSystemMessage(pcSender, SimulationManager.getPopulationString());
+            return;
+        }
+
+        if (ChatManager.isPvpRequest(text) == true) {
+            sendSystemMessage(pcSender, PvpRecord.getPvpHistoryString(pcSender.getObjectUUID()));
+            return;
+        }
+
+        if (ChatManager.isBaneRequest(text) == true) {
+            sendSystemMessage(pcSender, BaneRecord.getBaneHistoryString());
+            return;
+        }
+
+        if (pcSender != null && testSilenced(pcSender))
+            return;
+
+        // Make the Message
+        ChatSayMsg chatSayMsg = new ChatSayMsg(player, text);
+        DispatchMessage.dispatchMsgToInterestArea(pcSender, chatSayMsg, Enum.DispatchChannel.SECONDARY, MBServerStatics.SAY_RANGE, true, true);
+
+    }
+
+    public static void sendSystemMessage(PlayerCharacter targetPlayer, String messageString) {
+
+        ChatSystemMsg msg = new ChatSystemMsg(null, messageString);
+        msg.setMessageType(4);
+        msg.setChannel(engine.Enum.ChatChannelType.SYSTEM.getChannelID());
+        Dispatch dispatch = Dispatch.borrow(targetPlayer, msg);
+        DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+    }
+
+    /*
+     * Shout
+     */
+    public static void chatShout(AbstractWorldObject sender, String text,
+                          boolean isFlood) {
+
+        PlayerCharacter pcSender = null;
+
+        if (sender.getObjectType().equals(GameObjectType.PlayerCharacter))
+            pcSender = (PlayerCharacter) sender;
+
+        if (isFlood) {
+            ChatManager.chatSayError(pcSender, FLOOD_USER_ERROR);
+            return;
+        }
+
+        if (pcSender != null && testSilenced(pcSender))
+            return;
+
+        // Make the Message
+        ChatShoutMsg msg = new ChatShoutMsg(sender, text);
+        DispatchMessage.dispatchMsgToInterestArea(pcSender, msg, engine.Enum.DispatchChannel.SECONDARY, MBServerStatics.SHOUT_RANGE, true, true);
+
+    }
+    
+    public static void chatGlobal(PlayerCharacter sender, String text,
+            boolean isFlood) {
+
+PlayerCharacter pcSender = null;
+
+if (sender.getObjectType().equals(GameObjectType.PlayerCharacter))
+pcSender = (PlayerCharacter) sender;
+
+if (isFlood) {
+ChatManager.chatSayError(pcSender, FLOOD_USER_ERROR);
+return;
+}
+
+if (pcSender != null && testSilenced(pcSender))
+return;
+
+// Make the Message
+ChatGlobalMsg msg = new ChatGlobalMsg(sender, text);
+DispatchMessage.dispatchMsgToAll(sender, msg, true);
+
+}
+
+    /*
+     * Tell
+     */
+    public static void chatTell(AbstractWorldObject sender, String recipient,
+                         String text, boolean isFlood) {
+        if (text.isEmpty())
+            return;
+
+        PlayerCharacter pcSender = null;
+
+        if (sender.getObjectType().equals(GameObjectType.PlayerCharacter))
+            pcSender = (PlayerCharacter) sender;
+
+        if (pcSender != null && testSilenced(pcSender))
+            return;
+
+        PlayerCharacter pcRecip = SessionManager
+                .getPlayerCharacterByLowerCaseName(recipient);
+
+        if (pcRecip != null) {
+            if (isFlood) {
+                ChatManager.chatTellError(pcSender, FLOOD_USER_ERROR);
+                return;
+            }
+
+            ChatManager.chatTell(sender, pcRecip, text);
+        } else
+            ChatManager.chatTellError((PlayerCharacter) sender,
+                    "There is no such player.");
+
+    }
+
+    public static void chatTell(AbstractWorldObject sender,
+                         AbstractWorldObject recipient, String text) {
+
+        PlayerCharacter pcSender = null;
+
+        if (sender.getObjectType().equals(GameObjectType.PlayerCharacter))
+            pcSender = (PlayerCharacter) sender;
+
+        if (pcSender != null && testSilenced(pcSender))
+            return;
+
+        // Verify we are sending to a PlayerCharacter
+        PlayerCharacter pcRecip;
+        if (recipient.getObjectType().equals(GameObjectType.PlayerCharacter))
+            pcRecip = (PlayerCharacter) recipient;
+        else
+            return;
+
+        ClientConnection cc = SessionManager.getClientConnection(
+                pcRecip);
+
+        // make sure we have a good ClientConnection
+        if (cc != null) {
+
+            ChatTellMsg chatTellMsg = new ChatTellMsg(sender, pcRecip, text);
+
+            // Don't send to recipient if they're ignoring sender
+            if (!pcRecip.isIgnoringPlayer(pcSender)) {
+                // Send dispatch to each player
+
+                Dispatch dispatch = Dispatch.borrow(pcRecip, chatTellMsg);
+                DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+            }
+
+            // Also send /tell to sender
+            if (pcSender != null) {
+                Dispatch dispatch = Dispatch.borrow(pcSender, chatTellMsg);
+                DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+            }
+
+        } else
+            ChatManager.chatTellError(pcSender,
+                    "There is no such player.");
+    }
+
+    public static void chatGuild(PlayerCharacter sender, ChatGuildMsg msg) {
+
+        // Verify sender has PlayerCharacter
+        if (sender == null)
+            return;
+
+        if (testSilenced(sender))
+            return;
+
+        // Verify player is in guild and get guild
+        Guild guild = sender.getGuild();
+        if (guild == null || guild.getObjectUUID() == 0)
+            return;
+
+        // Get Distro List for guild
+        ArrayList<PlayerCharacter> distroList = SessionManager.getActivePCsInGuildID(guild.getObjectUUID());
+        if (msg.getUnknown02() == 5) {
+
+            Guild nation = guild.getNation();
+            if (nation == null)
+                return;
+            distroList = ChatManager.getNationListChat(nation, sender);
+        }
+
+        // Check the DistroList size
+        if (distroList.size() < 1) {
+            ChatManager.chatGuildError(sender,
+                    "You find yourself mute!");
+            Logger.error("Guild Chat returned a list of Players <1 in length.");
+            return;
+        }
+
+        // Make the Message
+
+        // make the ed message
+        ChatGuildMsg chatGuildMsg = new ChatGuildMsg(msg);
+
+        chatGuildMsg.setSourceType(sender.getObjectType().ordinal());
+        chatGuildMsg.setSourceID(sender.getObjectUUID());
+        chatGuildMsg.setSourceName(sender.getFirstName());
+        chatGuildMsg.setUnknown03(MBServerStatics.worldMapID); // Server ID
+        chatGuildMsg.setUnknown04(sender.getGuild() != null ? sender.getGuild()
+                .getCharter() : 0); // Charter?
+        chatGuildMsg.setUnknown05(GuildStatusController.getTitle(sender.getGuildStatus())); // Title?
+        chatGuildMsg.setUnknown06(sender.getRace().getRaceType().getCharacterSex().equals(Enum.CharacterSex.MALE) ? 1 : 2); // isMale?, seen 1 and 2
+
+        // Send dispatch to each player
+
+        for (AbstractWorldObject abstractWorldObject : distroList) {
+            PlayerCharacter playerCharacter = (PlayerCharacter) abstractWorldObject;
+            Dispatch dispatch = Dispatch.borrow(playerCharacter, chatGuildMsg);
+            DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+        }
+
+    }
+
+    public static void chatIC(PlayerCharacter sender, ChatICMsg msg) {
+
+        // Verify sender has PlayerCharacter
+        if (sender == null)
+            return;
+
+        if (testSilenced(sender))
+            return;
+
+        // Verify player is in guild and get guild
+        Guild guild = sender.getGuild();
+        if (guild == null || guild.getObjectUUID() == 0)
+            return;
+
+        //Verify player is an IC
+
+        if (GuildStatusController.isInnerCouncil(sender.getGuildStatus()) == false)
+            return;
+
+        // Get Distro List for guild
+        HashSet<AbstractWorldObject> distroList = ChatManager.getGuildICList(guild, sender);
+
+        // Check the DistroList size
+        if (distroList.size() < 1) {
+            ChatManager.chatICError(sender, "You find yourself mute!");
+            Logger.error("Guild Chat returned a list of Players <1 in length.");
+            return;
+        }
+
+        // TODO Hrm, are we modifying the incoming message or making a response?
+        // Not anymore we aren't!
+
+        // Create new outgoing message
+
+        ChatICMsg chatICMsg = new ChatICMsg(msg);
+
+        chatICMsg.setSourceType(sender.getObjectType().ordinal());
+        chatICMsg.setSourceID(sender.getObjectUUID());
+        chatICMsg.setSourceName(sender.getFirstName());
+        chatICMsg.setUnknown02(MBServerStatics.worldMapID); // Server ID
+        chatICMsg.setUnknown03(GuildStatusController.getRank(sender.getGuildStatus())); // correct?
+        chatICMsg.setUnknown04(GuildStatusController.getTitle(sender.getGuildStatus())); // correct?
+        chatICMsg.setUnknown05(2); // unknown, seen 1 and 2
+
+
+        // Send dispatch to each player
+
+        for (AbstractWorldObject abstractWorldObject : distroList) {
+            PlayerCharacter playerCharacter = (PlayerCharacter) abstractWorldObject;
+            Dispatch dispatch = Dispatch.borrow(playerCharacter, chatICMsg);
+            DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+        }
+
+    }
+
+    private static boolean isVersionRequest(String text) {
+        return text.equalsIgnoreCase("lua_version()");
+    }
+
+    private static boolean isPvpRequest(String text) {
+        return text.equalsIgnoreCase("lua_pvp()");
+    }
+
+    private static boolean isBaneRequest(String text) {
+        return text.equalsIgnoreCase("lua_banes()");
+    }
+
+    public static void chatGroup(PlayerCharacter sender, ChatGroupMsg msg) {
+
+        // Verify sender has PlayerCharacter
+        if (sender == null)
+            return;
+
+        if (testSilenced(sender))
+            return;
+
+        // Verify player is in guild and get guild
+
+        Group group = GroupManager.getGroup(sender);
+
+        if (group == null)
+            return;
+
+        // Get Distro List for guild
+        HashSet<AbstractWorldObject> distroList = ChatManager.getGroupList(group, sender);
+
+        // Check the DistroList size
+        if (distroList.size() < 1) {
+            ChatManager.chatGroupError(sender,
+                    "You find yourself mute!");
+            Logger.error("Group Chat returned a list of Players <1 in length.");
+            return;
+        }
+
+        // Make the Message
+
+        ChatGroupMsg chatGroupMsg = new ChatGroupMsg(msg);
+
+        chatGroupMsg.setSourceType(sender.getObjectType().ordinal());
+        chatGroupMsg.setSourceID(sender.getObjectUUID());
+        chatGroupMsg.setSourceName(sender.getFirstName());
+        chatGroupMsg.setUnknown02(MBServerStatics.worldMapID); // Server ID
+
+
+        // Send dispatch to each player
+
+        for (AbstractWorldObject abstractWorldObject : distroList) {
+            PlayerCharacter playerCharacter = (PlayerCharacter) abstractWorldObject;
+            Dispatch dispatch = Dispatch.borrow(playerCharacter, chatGroupMsg);
+            DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+        }
+
+    }
+
+    public static void GuildEnterWorldMsg(PlayerCharacter sender,
+                                   ClientConnection origin) {
+        // Verify sender has PlayerCharacter
+        if (sender == null)
+            return;
+        // Verify player is in guild and get guild
+        Guild guild = sender.getGuild();
+        if (guild == null || guild.getObjectUUID() == 0)
+            return;
+        // Get Distro List for guild
+        HashSet<AbstractWorldObject> distroList = ChatManager.getGuildList(guild, null);
+        // Check the DistroList size
+        if (distroList.size() < 1) {
+            Logger.error("Guild EnterWorldMsg returned a list of Players <1 in length.");
+            return;
+        }
+
+        // Make and send enter world message
+        GuildEnterWorldMsg msg = new GuildEnterWorldMsg(sender);
+        msg.setName(sender.getFirstName());
+        msg.setGuildTitle(GuildStatusController.getTitle(sender.getGuildStatus()));
+        msg.setGuildUUID(guild.getObjectUUID());
+        msg.setCharter(guild.getCharter());
+
+        // Send dispatch to each player
+
+        for (AbstractWorldObject abstractWorldObject : distroList) {
+            PlayerCharacter playerCharacter = (PlayerCharacter) abstractWorldObject;
+            Dispatch dispatch = Dispatch.borrow(playerCharacter, msg);
+            DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+        }
+    }
+
+    public static void chatPeekSteal(PlayerCharacter sender, AbstractCharacter tar, Item item, boolean success, boolean detect, int amount) {
+        if (sender == null || tar == null)
+            return;
+
+        PlayerCharacter target = null;
+
+        if (tar.getObjectType().equals(GameObjectType.PlayerCharacter))
+            target = (PlayerCharacter) tar;
+
+        // Get Distro List
+        HashSet<AbstractWorldObject> distroList = WorldGrid
+                .getObjectsInRangePartial(sender.getLoc(),
+                        MBServerStatics.SAY_RANGE, MBServerStatics.MASK_PLAYER);
+
+        // Check the DistroList size
+        if (distroList.size() < 1)
+            return;
+
+        //remove Thief and Victim from other's list
+        int size = distroList.size();
+        for (int i = size - 1; i > -1; i--) {
+            AbstractWorldObject awo = (AbstractWorldObject) distroList.toArray()[i];
+            if (awo.getObjectUUID() == sender.getObjectUUID())
+                distroList.remove(awo);
+            else if (awo.getObjectUUID() == tar.getObjectUUID())
+                distroList.remove(awo);
+
+        }
+
+        String textToThief = "";
+        String textToVictim = "";
+        String textToOthers = "";
+
+        if (item != null) //Steal
+            if (success) {
+                String name = "";
+                if (item.getItemBase() != null)
+                    if (item.getItemBase().getUUID() == 7)
+                        name = amount + " gold ";
+                    else {
+                        String vowels = "aeiou";
+                        String iName = item.getItemBase().getName();
+                        if (iName.length() > 0)
+                            if (vowels.indexOf(iName.substring(0, 1).toLowerCase()) >= 0)
+                                name = "an " + iName + ' ';
+                            else
+                                name = "a " + iName + ' ';
+                    }
+                textToThief = "You have stolen " + name + "from " + tar.getFirstName() + '!';
+                textToVictim = sender.getFirstName() + "is caught with thier hands in your pocket!";
+                //textToOthers = sender.getFirstName() + " steals " + name + "from " + target.getFirstName() + ".";
+            } else
+                textToThief = "Your attempt at stealing failed..."; //textToVictim = sender.getFirstName() + " fails to steal from you.";
+            //textToOthers = sender.getFirstName() + " fails to steal from " + target.getFirstName() + ".";
+        else //Peek
+            if (success) {
+                if (detect) {
+                    textToThief = tar.getFirstName() + " catches you peeking through their belongings!";
+                    textToVictim = "You catch " + sender.getFirstName() + " peeking through your belongings!";
+                }
+            } else {
+                textToThief = "Your attempt at peeking failed...";
+                textToVictim = sender.getFirstName() + " is seen eyeing up your backpack...";
+                textToOthers = sender.getFirstName() + " is seen eyeing up the backpack of " + tar.getFirstName() + "...";
+            }
+
+        //Send msg to thief
+        HashSet<AbstractWorldObject> senderList = new HashSet<>();
+        senderList.add(sender);
+        if (!textToThief.isEmpty())
+            ChatManager.chatSystemSend(senderList, textToThief, 1, 2);
+
+        if (target != null && !textToVictim.isEmpty()) {
+        	HashSet<AbstractWorldObject> victimList = new HashSet<>();
+            victimList.add(target);
+            ChatManager.chatSystemSend(victimList, textToVictim, 1, 2);
+        }
+
+        //Send msg to Others in range\
+        if (!textToOthers.isEmpty())
+            ChatManager.chatSystemSend(distroList, textToOthers, 1, 2);
+    }
+
+    // Send System Announcement as a flash at top of screen
+    public static void chatSystemFlash(String text) {
+
+        chatSystem(null, text, 2, 1);
+    }
+
+    public static void chatSystemChannel(String text) {
+        chatSystem(null, text, 1, 4); // Type 4 simply displays text as is
+    }
+
+    // Send Error Message to player
+    public static void chatSystemError(PlayerCharacter pc, String text) {
+        sendErrorMsgToPlayer(pc, text, 1);
+    }
+
+    public static void chatSystemInfo(PlayerCharacter pc, String text) {
+        sendInfoMsgToPlayer(pc, text, 1);
+    }
+
+    public static void chatCommanderError(PlayerCharacter pc, String text) {
+        sendErrorMsgToPlayer(pc, text, 3);
+    }
+
+    public static void chatNationError(PlayerCharacter pc, String text) {
+        sendErrorMsgToPlayer(pc, text, 5);
+    }
+
+    public static void chatLeaderError(PlayerCharacter pc, String text) {
+        sendErrorMsgToPlayer(pc, text, 6);
+    }
+
+    public static void chatShoutError(PlayerCharacter pc, String text) {
+        sendErrorMsgToPlayer(pc, text, 7);
+    }
+
+    public static void chatInfoError(PlayerCharacter pc, String text) {
+        sendErrorMsgToPlayer(pc, text, 10);
+    }
+
+    public static void chatGuildError(PlayerCharacter pc, String text) {
+        sendErrorMsgToPlayer(pc, text, 12);
+    }
+
+    public static void chatICError(PlayerCharacter pc, String text) {
+        sendErrorMsgToPlayer(pc, text, 13);
+    }
+
+    public static void chatGroupError(PlayerCharacter pc, String text) {
+        sendErrorMsgToPlayer(pc, text, 14);
+    }
+
+    public static void chatCityError(PlayerCharacter pc, String text) {
+        sendErrorMsgToPlayer(pc, text, 15);
+    }
+
+    public static void chatSayError(PlayerCharacter pc, String text) {
+        sendErrorMsgToPlayer(pc, text, 16);
+    }
+
+    public static void chatEmoteError(PlayerCharacter pc, String text) {
+        sendErrorMsgToPlayer(pc, text, 17);
+    }
+
+    public static void chatTellError(PlayerCharacter pc, String text) {
+        sendErrorMsgToPlayer(pc, text, 19);
+    }
+
+    // Send Info Message to channels
+    public static void chatCommanderInfo(PlayerCharacter pc, String text) {
+        chatSystem(pc, text, 3, 2);
+    }
+
+    public static void chatNationInfo(PlayerCharacter pc, String text) {
+        chatSystem(pc, text, 5, 2);
+    }
+
+    public static void chatNationInfo(Guild nation, String text) {
+        chatSystemGuild(nation, text, 5, 2);
+    }
+
+    public static void chatLeaderInfo(PlayerCharacter pc, String text) {
+        chatSystem(pc, text, 6, 2);
+    }
+
+    public static void chatShoutInfo(PlayerCharacter pc, String text) {
+        chatSystem(pc, text, 7, 2);
+    }
+
+    public static void chatInfoInfo(PlayerCharacter pc, String text) {
+        chatSystem(pc, text, 10, 2);
+    }
+
+    public static void chatGuildInfo(PlayerCharacter pc, String text) {
+        chatSystem(pc, text, 12, 2);
+    }
+
+    public static void chatICInfo(PlayerCharacter pc, String text) {
+        chatSystem(pc, text, 13, 2);
+    }
+
+    public static void chatGroupInfo(PlayerCharacter pc, String text) {
+        chatSystem(pc, text, 14, 2);
+    }
+
+    public static void chatCityInfo(PlayerCharacter pc, String text) {
+        chatSystem(pc, text, 15, 2);
+    }
+
+    public static void chatSayInfo(PlayerCharacter pc, String text) {
+        chatSystem(pc, text, 16, 2);
+    }
+
+    public static void chatEmoteInfo(PlayerCharacter pc, String text) {
+        chatSystem(pc, text, 17, 2);
+    }
+
+    public static void chatTellInfo(PlayerCharacter pc, String text) {
+        chatSystem(pc, text, 19, 2);
+    }
+
+    public static void chatGroupInfoCanSee(PlayerCharacter pc, String text) {
+    	HashSet<AbstractWorldObject> distroList = null;
+
+        Group group = GroupManager.getGroup(pc);
+        if (group != null) {
+            distroList = ChatManager.getGroupList(group, pc);
+            if (distroList != null) {
+                Iterator<AbstractWorldObject> it = distroList.iterator();
+                while (it.hasNext()) {
+                    AbstractWorldObject awo = it.next();
+                    if (!(awo.getObjectType().equals(GameObjectType.PlayerCharacter)))
+                        it.remove();
+                    else {
+                        PlayerCharacter pcc = (PlayerCharacter) awo;
+                        if (pcc.getSeeInvis() < pc.getHidden())
+                            it.remove();
+                    }
+                }
+            }
+        }
+        ChatManager.chatSystemSend(distroList, text, 14, 2);
+    }
+
+    // Send MOTD Message to channels
+    public static void chatNationMOTD(PlayerCharacter pc, String text) {
+        chatNationMOTD(pc, text, false);
+    }
+
+    public static void chatNationMOTD(PlayerCharacter pc, String text, boolean all) {
+        if (all) // Send to all Nation
+
+            chatSystem(pc, text, 5, 3);
+        else // Send to just pc
+
+            chatSystemMOTD(pc, text, 5, 3);
+    }
+
+    public static void chatGuildMOTD(PlayerCharacter pc, String text) {
+        chatGuildMOTD(pc, text, false);
+    }
+
+    public static void chatGuildMOTD(PlayerCharacter pc, String text, boolean all) {
+        if (all) // Send to all Guild
+
+            chatSystem(pc, text, 12, 3);
+        else // Send to just pc
+
+            chatSystemMOTD(pc, text, 12, 3);
+    }
+
+    public static void chatICMOTD(PlayerCharacter pc, String text) {
+        chatICMOTD(pc, text, false);
+    }
+
+    public static void chatICMOTD(PlayerCharacter pc, String text, boolean all) {
+        if (all) // Send to all IC's
+
+            chatSystem(pc, text, 13, 3);
+        else // Send to just pc
+
+            chatSystemMOTD(pc, text, 13, 3);
+    }
+
+    // Send Info Message to guild channel based on guild
+    public static void chatGuildInfo(Guild guild, String text) {
+    	HashSet<AbstractWorldObject> distroList = null;
+        if (guild != null)
+            distroList = ChatManager.getGuildList(guild, null);
+        ChatManager.chatSystemSend(distroList, text, 12, 2);
+    }
+
+    public static void chatSystemMOTD(PlayerCharacter sender, String text,
+                                      int channel, int messageType) {
+    	HashSet<AbstractWorldObject> distroList = ChatManager.getOwnPlayer(sender);
+        ChatManager.chatSystemSend(distroList, text, channel, messageType);
+    }
+
+    public static void chatPVP(String text) {
+        // Create message
+        ChatPvPMsg msg = new ChatPvPMsg(null, text);
+        DispatchMessage.dispatchMsgToAll(msg);
+    }
+
+    public static void chatInfo(String text) {
+    	HashSet<AbstractWorldObject> distroList = ChatManager.getAllPlayers(null);
+        chatSystemSend(distroList, text, 1, 2);
+    }
+
+    public static ChatSystemMsg CombatInfo(AbstractWorldObject source, AbstractWorldObject target) {
+        String text = "The " + target.getName() + " attacks " + source.getName();
+        ChatSystemMsg msg = new ChatSystemMsg(null, text);
+        msg.setChannel(20);
+        msg.setMessageType(2);
+        return msg;
+    }
+
+    public static void chatSystem(PlayerCharacter sender, String text, int channel,
+                                  int messageType) {
+    	HashSet<AbstractWorldObject> distroList = null;
+        if (channel == 1) // System Channel Message
+
+            distroList = ChatManager.getAllPlayers(sender);
+        else if (channel == 2) // System Flash, send to everyone
+
+            distroList = ChatManager.getAllPlayers(sender);
+        else if (channel == 3) { // Commander Channel
+        } else if (channel == 5) { // Nation Channel, get Nation list
+            Guild guild = sender.getGuild();
+            if (guild != null) {
+                Guild nation = guild.getNation();
+                if (nation != null)
+                    if (nation.getObjectUUID() != 0) // Don't /nation to errant
+                        // nation
+
+                        distroList = ChatManager.getNationList(nation, sender);
+            }
+        } else if (channel == 6) { // Leader Channel
+        } else if (channel == 7) // Shout Channel
+            distroList = ChatManager.getOwnPlayer(sender);
+        else if (channel == 10) // Info Channel
+            distroList = getOwnPlayer(sender);
+        else if (channel == 12) { // guild Channel, get Guild list
+            Guild guild = sender.getGuild();
+            if (guild != null)
+                if (guild.getObjectUUID() != 0) // Don't /guild to errant guild
+
+                    distroList = ChatManager.getGuildList(guild, sender);
+        } else if (channel == 13) { // IC Channel, get Guild IC list
+            Guild guild = sender.getGuild();
+            if (guild != null)
+                if (guild.getObjectUUID() != 0) // Don't /IC to errant guild
+
+                    distroList = ChatManager.getGuildICList(guild, sender);
+        } else if (channel == 14) { // Group Channel, get group list
+            Group group = GroupManager.getGroup(sender);
+            if (group != null)
+                distroList = ChatManager.getGroupList(group, sender);
+        } else if (channel == 15) { // City Channel, get people bound to city
+            // list
+        } else if (channel == 16) // Say Channel
+            distroList = ChatManager.getOwnPlayer(sender);
+        else if (channel == 17) { // Emote Channel, get say List
+        } else if (channel == 19) // Tell Channel
+            distroList = ChatManager.getOwnPlayer(sender);
+        else
+            return;
+        ChatManager.chatSystemSend(distroList, text, channel, messageType);
+    }
+
+    public static void chatSystemGuild(Guild sender, String text, int channel,
+                                       int messageType) {
+    	HashSet<AbstractWorldObject> distroList = null;
+
+        if (channel == 5) { // Nation Channel, get Nation list
+            if (sender != null) {
+                Guild nation = sender.getNation();
+                if (nation != null)
+                    if (nation.getObjectUUID() != 0) // Don't /nation to errant
+                        // nation
+
+                        distroList = ChatManager.getNationList(nation, null);
+            }
+        } else if (channel == 12) { // guild Channel, get Guild list
+            if (sender != null)
+                if (sender.getObjectUUID() != 0) // Don't /guild to errant guild
+
+                    distroList = ChatManager.getGuildList(sender, null);
+        } else if (channel == 13) { // IC Channel, get Guild IC list
+            if (sender != null)
+                if (sender.getObjectUUID() != 0) // Don't /IC to errant guild
+
+                    distroList = ChatManager.getGuildICList(sender, null);
+        } else
+            return;
+        ChatManager.chatSystemSend(distroList, text, channel, messageType);
+
+    }
+
+    public static void chatSystemSend(HashSet<AbstractWorldObject> distroList,
+                                      String text, int channel, int messageType) {
+        // verify someone in distroList to send message to
+        if (distroList == null)
+            return;
+        if (distroList.size() < 1)
+            return;
+
+        // Create message
+        ChatSystemMsg chatSystemMsg = new ChatSystemMsg(null, text);
+        chatSystemMsg.setChannel(channel);
+        chatSystemMsg.setMessageType(messageType);
+
+        // Send dispatch to each player
+
+        for (AbstractWorldObject abstractWorldObject : distroList) {
+            PlayerCharacter playerCharacter = (PlayerCharacter) abstractWorldObject;
+            Dispatch dispatch = Dispatch.borrow(playerCharacter, chatSystemMsg);
+            DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+        }
+
+    }
+
+    // Get distroList for guild
+    public static HashSet<AbstractWorldObject> getGuildList(Guild guild, PlayerCharacter source) {
+    	HashSet<AbstractWorldObject> distroList = new HashSet<>();
+
+        for (PlayerCharacter playerCharacter : SessionManager.getAllActivePlayerCharacters()) {
+
+            if (Guild.sameGuild(playerCharacter.getGuild(), guild)) {
+                if (source != null && playerCharacter.isIgnoringPlayer(source))
+                    continue; // dont add if recip has ignored source
+                distroList.add(playerCharacter);
+            }
+        }
+        return distroList;
+    }
+
+    // Get distroList for guild IC's
+    public static HashSet<AbstractWorldObject> getGuildICList(Guild guild, PlayerCharacter source) {
+
+    	HashSet<AbstractWorldObject> distroList = new HashSet<>();
+
+        for (PlayerCharacter pc : SessionManager.getAllActivePlayerCharacters()) {
+
+            if (Guild.sameGuild(pc.getGuild(), guild))
+                if (GuildStatusController.isInnerCouncil(pc.getGuildStatus())) {
+                    if (source != null && pc.isIgnoringPlayer(source))
+                        continue; // dont add if recip has ignored source
+                    distroList.add(pc);
+                }
+        }
+        return distroList;
+    }
+
+    // Get distroList for group
+    public static HashSet<AbstractWorldObject> getGroupList(Group group, PlayerCharacter source) {
+    	HashSet<AbstractWorldObject> distroList = new HashSet<>();
+        Set<PlayerCharacter> players = group.getMembers();
+        for (PlayerCharacter pc : players) {
+            if (source != null && pc.isIgnoringPlayer(source))
+                continue; // dont add if recip has ignored source
+            distroList.add(pc);
+        }
+        return distroList;
+    }
+
+    // Get distroList for nation
+    public static HashSet<AbstractWorldObject> getNationList(Guild nation, PlayerCharacter source) {
+    	HashSet<AbstractWorldObject> distroList = new HashSet<>();
+
+        for (PlayerCharacter pc : SessionManager.getAllActivePlayerCharacters()) {
+
+            Guild guild = pc.getGuild();
+
+            if (guild != null)
+                if (guild.getNation().getObjectUUID() == nation.getObjectUUID()) {
+                    if (source != null && pc.isIgnoringPlayer(source))
+                        continue; // dont add if recip has ignored source
+                    distroList.add(pc);
+                }
+        }
+        return distroList;
+    }
+
+    public static ArrayList<PlayerCharacter> getNationListChat(Guild nation, PlayerCharacter source) {
+        ArrayList<PlayerCharacter> distroList = new ArrayList<>();
+
+        for (PlayerCharacter pc : SessionManager.getAllActivePlayerCharacters()) {
+
+            Guild guild = pc.getGuild();
+
+            if (guild != null)
+                if (guild.getNation().getObjectUUID() == nation.getObjectUUID()) {
+                    if (source != null && pc.isIgnoringPlayer(source))
+                        continue; // dont add if recip has ignored source
+                    distroList.add(pc);
+                }
+        }
+        return distroList;
+    }
+
+    // Get distroList for all players
+    public static HashSet<AbstractWorldObject> getAllPlayers(PlayerCharacter source) {
+
+    	HashSet<AbstractWorldObject> distroList = new HashSet<>();
+        for (PlayerCharacter pc : SessionManager.getAllActivePlayerCharacters()) {
+            if (source != null && pc.isIgnoringPlayer(source))
+                continue; // dont add if recip has ignored source
+            distroList.add(pc);
+        }
+        return distroList;
+    }
+
+    // Get just self for distrList
+    public static HashSet<AbstractWorldObject> getOwnPlayer(PlayerCharacter pc) {
+        if (pc == null)
+            return null;
+        HashSet<AbstractWorldObject> distroList = new HashSet<>();
+        distroList.add(pc);
+        return distroList;
+    }
+
+    /*
+     * Utils
+     */
+    // Error Message for type channel
+    private static void sendErrorMsgToPlayer(AbstractCharacter player, String message,
+                                             int channel) {
+        if (player == null)
+            return;
+        ChatManager.sendSystemMsgToPlayer(player, message, channel, 1);
+    }
+
+    // Info Message for type channel
+    private static void sendInfoMsgToPlayer(AbstractCharacter player, String message,
+                                            int channel) {
+        ChatManager.sendSystemMsgToPlayer(player, message, channel, 2);
+    }
+
+    // Message of the Day Message for type channel
+    //	private void sendMOTDMsgToPlayer(AbstractCharacter player, String message, int channel) {
+    //		this.sendSystemMsgToPlayer(player, message, channel, 3);
+    //	}
+    private static void sendSystemMsgToPlayer(AbstractCharacter player,
+                                              String message, int channel, int messageType) {
+
+        PlayerCharacter playerCharacter;
+
+        if (player == null)
+            return;
+
+        if (player.getObjectType().equals(GameObjectType.PlayerCharacter) == false) {
+            Logger.error("Chat message sent to non player");
+            return;
+        }
+
+        // Wtf recasting this?  If we're sending chat messages to players
+        // or mobiles, then something is really wrong.
+
+        playerCharacter = (PlayerCharacter) player;
+
+        ChatSystemMsg chatSystemMsg = new ChatSystemMsg(null, message);
+        chatSystemMsg.setMessageType(messageType); // Error message
+        chatSystemMsg.setChannel(channel);
+
+        Dispatch dispatch = Dispatch.borrow(playerCharacter, chatSystemMsg);
+        DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+
+    }
+
+    private static boolean isDevCommand(String text) {
+        return text.startsWith(MBServerStatics.DEV_CMD_PREFIX);
+    }
+
+    private static boolean isUpTimeRequest(String text) {
+        return text.equalsIgnoreCase("lua_uptime()");
+    }
+
+    private static boolean isNetStatRequest(String text) {
+        return text.equalsIgnoreCase("lua_netstat()");
+    }
+
+    private static boolean isPopulationRequest(String text) {
+        return text.equalsIgnoreCase("lua_population()");
+    }
+
+    private static boolean processDevCommand(AbstractWorldObject sender, String text) {
+
+        if (sender.getObjectType().equals(GameObjectType.PlayerCharacter)) {
+
+            PlayerCharacter pcSender = (PlayerCharacter) sender;
+
+            // first remove the DEV_CMD_PREFIX
+            String[] words = text.split(MBServerStatics.DEV_CMD_PREFIX, 2);
+
+            if (words[1].length() == 0)
+                return false;
+
+            // next get the command
+            String[] commands = words[1].split(" ", 2);
+            String cmd = commands[0].toLowerCase();
+            String cmdArgument = "";
+
+            if (commands.length > 1)
+                cmdArgument = commands[1].trim();
+
+            AbstractGameObject target = pcSender.getLastTarget();
+            // return DevCmd.processDevCommand(pcSender, cmd, cmdArgument,
+            // target);
+            return DevCmdManager.handleDevCmd(pcSender, cmd,
+                    cmdArgument, target);
+        }
+        return false;
+    }
+
+    /**
+     * Process an Admin Command, which is a preset command sent from the client
+     */
+    public static void HandleClientAdminCmd(ClientAdminCommandMsg data,
+                                            ClientConnection cc) {
+
+        PlayerCharacter pcSender = SessionManager.getPlayerCharacter(cc);
+
+        if (pcSender == null)
+            return;
+
+        Account acct = SessionManager.getAccount(pcSender);
+
+        if (acct == null)
+            return;
+
+        // require minimal access to continue
+        // specific accessLevel checks performed by the DevCmdManager
+        if (acct.status.equals(Enum.AccountStatus.ADMIN) == false) {
+            Logger.warn(pcSender.getFirstName() + " Attempted to use a client admin command");
+            //wtf?  ChatManager.chatSystemInfo(pcSender, "CHEATER!!!!!!!!!!!!!");
+            return;
+        }
+
+        // First remove the initial slash
+        String d = data.getMsgCommand();
+        String[] words = d.split("/", 2);
+
+        if (words[1].length() == 0)
+            return;
+
+        // Next get the command
+        String[] commands = words[1].split(" ", 2);
+        String cmd = commands[0].toLowerCase();
+        String cmdArgument = "";
+
+        if (commands.length > 1)
+            cmdArgument = commands[1].trim();
+
+        AbstractGameObject target = data.getTarget();
+
+        // Map to a DevCmd
+        String devCmd = "";
+
+        if (cmd.compareTo("goto") == 0)
+            devCmd = "goto";
+        else if (cmd.compareTo("suspend") == 0)
+            devCmd = "suspend";
+        else if (cmd.compareTo("getinfo") == 0)
+            devCmd = "info";
+        else if (devCmd.isEmpty()) {
+            Logger.info( "Unhandled admin command was used: /"
+                    + cmd);
+            return;
+        }
+
+        DevCmdManager.handleDevCmd(pcSender, devCmd, cmdArgument,
+                target);
+    }
+
+
+}
diff --git a/src/engine/gameManager/CombatManager.java b/src/engine/gameManager/CombatManager.java
new file mode 100644
index 00000000..fffe09c8
--- /dev/null
+++ b/src/engine/gameManager/CombatManager.java
@@ -0,0 +1,1455 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+package engine.gameManager;
+
+import engine.Enum.*;
+import engine.ai.MobileFSM.STATE;
+import engine.exception.MsgSendException;
+import engine.job.JobContainer;
+import engine.job.JobScheduler;
+import engine.jobs.AttackJob;
+import engine.jobs.DeferredPowerJob;
+import engine.math.Vector3fImmutable;
+import engine.net.DispatchMessage;
+import engine.net.client.ClientConnection;
+import engine.net.client.msg.*;
+import engine.objects.*;
+import engine.powers.DamageShield;
+import engine.powers.PowersBase;
+import engine.powers.effectmodifiers.AbstractEffectModifier;
+import engine.powers.effectmodifiers.WeaponProcEffectModifier;
+import engine.server.MBServerStatics;
+import org.pmw.tinylog.Logger;
+
+import java.util.HashSet;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ThreadLocalRandom;
+
+import static engine.math.FastMath.sqr;
+
+public enum CombatManager {
+
+	COMBATMANAGER;
+
+	/**
+	 * Message sent by player to attack something.
+	 */
+	public static void setAttackTarget(AttackCmdMsg msg, ClientConnection origin) throws MsgSendException {
+
+		PlayerCharacter player;
+		int targetType;
+		AbstractWorldObject target;
+
+		if (TargetedActionMsg.un2cnt == 60 || TargetedActionMsg.un2cnt == 70) {
+			return;
+		}
+
+		player = SessionManager.getPlayerCharacter(origin);
+
+		if (player == null) {
+
+			return;
+		}
+
+		//source must match player this account belongs to
+		if (player.getObjectUUID() != msg.getSourceID() || player.getObjectType().ordinal() != msg.getSourceType()) {
+			Logger.error("Msg Source ID " + msg.getSourceID() + " Does not Match Player ID " + player.getObjectUUID() );
+
+			return;
+		}
+
+		targetType = msg.getTargetType();
+
+		if (targetType == GameObjectType.PlayerCharacter.ordinal()) {
+			target = PlayerCharacter.getFromCache(msg.getTargetID());
+		} else if (targetType == GameObjectType.Building.ordinal()) {
+			target = BuildingManager.getBuildingFromCache(msg.getTargetID());
+		} else if (targetType == GameObjectType.Mob.ordinal()) {
+			target = Mob.getFromCache(msg.getTargetID());
+		}else{
+			player.setCombatTarget(null);
+			return; //not valid type to attack
+		}
+		// quit of the combat target is already the current combat target
+		// or there is no combat target
+		if (target == null) {
+			return;
+		}
+
+		//set sources target
+		player.setCombatTarget(target);
+
+		//put in combat if not already
+		if (!player.isCombat()) {
+			toggleCombat(true, origin);
+		}
+
+		//make character stand if sitting
+		if (player.isSit()) {
+			toggleSit(false, origin);
+		}
+
+		AttackTarget(player, target);
+
+	}
+
+	public static void AttackTarget(PlayerCharacter pc, AbstractWorldObject target) {
+
+		boolean swingOffhand = false;
+
+		//check my weapon can I do an offhand attack
+		Item weaponOff = pc.getCharItemManager().getEquipped().get(MBServerStatics.SLOT_OFFHAND);
+		Item weaponMain = pc.getCharItemManager().getEquipped().get(MBServerStatics.SLOT_MAINHAND);
+
+		// if you carry something in the offhand thats a weapon you get to swing it
+		if (weaponOff != null) {
+			if (weaponOff.getItemBase().getType().equals(ItemType.WEAPON)) {
+				swingOffhand = true;
+			}
+		}
+		// if you carry  nothing in either hand you get to swing your offhand
+		if (weaponOff == null && weaponMain == null) {
+			swingOffhand = true;
+		}
+
+		//we always swing our mainhand if we are not on timer
+		JobContainer main = pc.getTimers().get("Attack" + MBServerStatics.SLOT_MAINHAND);
+		if (main == null) {
+			// no timers on the mainhand, lets submit a job to swing
+			CombatManager.createTimer(pc, MBServerStatics.SLOT_MAINHAND, 1, true); // attack in 0.1 of a second
+		}
+
+		if (swingOffhand) {
+			/*
+            only swing offhand if we have a weapon in it or are unarmed in both hands
+            and no timers running
+			 */
+			JobContainer off = pc.getTimers().get("Attack" + MBServerStatics.SLOT_OFFHAND);
+			if (off == null) {
+				CombatManager.createTimer(pc, MBServerStatics.SLOT_OFFHAND, 1, true); // attack in 0.1 of a second
+			}
+		}
+	}
+
+	public static void setAttackTarget(PetAttackMsg msg, ClientConnection origin) throws MsgSendException {
+
+		PlayerCharacter player;
+		Mob pet;
+		int targetType;
+		AbstractWorldObject target;
+
+		if (TargetedActionMsg.un2cnt == 60 || TargetedActionMsg.un2cnt == 70)
+			return;
+
+		player = SessionManager.getPlayerCharacter(origin);
+
+		if (player == null)
+			return;
+
+		pet = player.getPet();
+
+		if (pet == null)
+			return;
+
+		targetType = msg.getTargetType();
+
+		if (targetType == GameObjectType.PlayerCharacter.ordinal())
+			target = PlayerCharacter.getFromCache(msg.getTargetID());
+		else if (targetType == GameObjectType.Building.ordinal())
+			target = BuildingManager.getBuildingFromCache(msg.getTargetID());
+		else if (targetType == GameObjectType.Mob.ordinal())
+			target = Mob.getFromCache(msg.getTargetID());
+		else {
+			pet.setCombatTarget(null);
+			return; //not valid type to attack
+		}
+		
+		if (pet.equals(target))
+			return;
+
+		// quit of the combat target is already the current combat target
+		// or there is no combat target
+
+		if (target == null || target == pet.getCombatTarget())
+			return;
+
+
+
+		//set sources target
+		pet.setCombatTarget(target);
+		pet.setState(STATE.Attack);
+		//		setFirstHitCombatTarget(player,target);
+
+		//put in combat if not already
+		if (!pet.isCombat())
+			pet.setCombat(true);
+
+		//make character stand if sitting
+		if (pet.isSit())
+			toggleSit(false, origin);
+
+	}
+
+	private static void removeAttackTimers(AbstractCharacter ac) {
+
+		JobContainer main;
+		JobContainer off;
+
+		if (ac == null)
+			return;
+
+		main = ac.getTimers().get("Attack" + MBServerStatics.SLOT_MAINHAND);
+		off = ac.getTimers().get("Attack" + MBServerStatics.SLOT_OFFHAND);
+
+		if (main != null)
+			JobScheduler.getInstance().cancelScheduledJob(main);
+
+		ac.getTimers().remove("Attack" + MBServerStatics.SLOT_MAINHAND);
+
+		if (off != null)
+			JobScheduler.getInstance().cancelScheduledJob(off);
+
+		ac.getTimers().remove("Attack" + MBServerStatics.SLOT_OFFHAND);
+
+		ac.setCombatTarget(null);
+
+	}
+
+	/**
+	 * Begin Attacking
+	 */
+	public static void doCombat(AbstractCharacter ac, int slot) {
+
+		int ret = 0;
+
+		if (ac == null)
+			return;
+
+		// Attempt to eat null targets until we can clean
+		// up this unholy mess and refactor it into a thread.
+
+
+		ret = attemptCombat(ac, slot);
+
+		//handle pets
+		if (ret < 2 && ac.getObjectType().equals(GameObjectType.Mob)) {
+			Mob mob = (Mob) ac;
+			if (mob.isPet()) {
+				return;
+			}
+		}
+
+		//ret values
+		//0: not valid attack, fail attack
+		//1: cannot attack, wrong hand
+		//2: valid attack
+		//3: cannot attack currently, continue checking
+
+		if (ret == 0 || ret == 1) {
+
+			//Could not attack, clear timer
+
+			ConcurrentHashMap<String, JobContainer> timers = ac.getTimers();
+
+			if (timers != null && timers.containsKey("Attack" + slot))
+				timers.remove("Attack" + slot);
+
+			//clear combat target if not valid attack
+			if (ret == 0)
+				ac.setCombatTarget(null);
+
+		} else if (ret == 3) {
+			//Failed but continue checking. reset timer
+			createTimer(ac, slot, 5, false);
+		}
+	}
+
+	/**
+	 * Verify can attack target
+	 */
+	private static int attemptCombat(AbstractCharacter abstractCharacter, int slot) {
+
+		if (abstractCharacter == null) {
+			// debugCombat(ac, "Source is null");
+			return 0;
+		}
+
+		try {
+			//Make sure player can attack
+			PlayerBonuses bonus = abstractCharacter.getBonuses();
+
+			if (bonus != null && bonus.getBool(ModType.ImmuneToAttack, SourceType.None))
+				return 0;
+
+			AbstractWorldObject target = abstractCharacter.getCombatTarget();
+
+			if (target == null){
+				return 0;
+			}
+				
+
+			//target must be valid type
+			if (AbstractWorldObject.IsAbstractCharacter(target)) {
+				AbstractCharacter tar = (AbstractCharacter) target;
+				//must be alive, attackable and in World
+				if (!tar.isAlive()) {
+					return 0;
+				}
+				else if (tar.isSafeMode()) {
+					return 0;
+				}
+				else if (!tar.isActive()) {
+					return 0;
+				}
+
+				if (target.getObjectType().equals(GameObjectType.PlayerCharacter) && abstractCharacter.getObjectType().equals(GameObjectType.PlayerCharacter) && abstractCharacter.getTimers().get("Attack" + slot) == null) {
+					if (!((PlayerCharacter) abstractCharacter).canSee((PlayerCharacter) target)) {
+						return 0;
+					}
+				}
+
+				//must not be immune to all or immune to attack
+				Resists res = tar.getResists();
+				bonus = tar.getBonuses();
+				if (bonus != null && !bonus.getBool(ModType.NoMod, SourceType.ImmuneToAttack)) {
+					if (res != null) {
+						if (res.immuneToAll() || res.immuneToAttacks()) {
+							return 0;
+						}
+					}
+				}
+			}
+			else if (target.getObjectType().equals(GameObjectType.Building)) {
+				Building tar = (Building) target;
+
+				// Cannot attack an invuln building
+
+				if (tar.isVulnerable() == false) {
+					return 0;
+				}
+
+			}
+			else {
+				return 0; //only characters and buildings may be attacked
+			}
+
+			//source must be in world and alive
+			if (!abstractCharacter.isActive()) {
+				return 0;
+			}
+			else if (!abstractCharacter.isAlive()) {
+				return 0;
+			}
+
+			//make sure source is in combat mode
+			if (!abstractCharacter.isCombat()) {
+				return 0;
+			}
+
+			//See if either target is in safe zone
+			if (abstractCharacter.getObjectType().equals(GameObjectType.PlayerCharacter) && target.getObjectType().equals(GameObjectType.PlayerCharacter)) {
+				if (((PlayerCharacter) abstractCharacter).inSafeZone() || ((PlayerCharacter) target).inSafeZone()) {
+					return 0;
+				}
+			}
+
+			if (!(slot == MBServerStatics.SLOT_MAINHAND || slot == MBServerStatics.SLOT_OFFHAND)) {
+				return 0;
+			}
+
+			if (abstractCharacter.getCharItemManager() == null) {
+				return 0;
+			}
+
+			//get equippment
+			ConcurrentHashMap<Integer, Item> equipped = abstractCharacter.getCharItemManager().getEquipped();
+			boolean hasNoWeapon = false;
+
+			if (equipped == null) {
+				return 0;
+			}
+
+			//get Weapon
+			boolean isWeapon = true;
+			Item weapon = equipped.get(slot);
+			ItemBase wb = null;
+			if (weapon == null) {
+				isWeapon = false;
+			}
+			else {
+				ItemBase ib = weapon.getItemBase();
+				if (ib == null || !ib.getType().equals(ItemType.WEAPON)) {
+					isWeapon = false;
+				}
+				else {
+					wb = ib;
+				}
+			}
+
+			//if no weapon, see if other hand has a weapon
+			if (!isWeapon) {
+				//no weapon, see if other hand has a weapon
+				if (slot == MBServerStatics.SLOT_MAINHAND) {
+					//make sure offhand has weapon, not shield
+					Item weaponOff = equipped.get(MBServerStatics.SLOT_OFFHAND);
+					if (weaponOff != null) {
+						ItemBase ib = weaponOff.getItemBase();
+						if (ib == null || !ib.getType().equals(ItemType.WEAPON)) {
+							hasNoWeapon = true;
+						}
+						else {
+							// debugCombat(ac, "mainhand, weapon in other hand");
+							return 1; //no need to attack with this hand
+						}
+					}
+					else {
+						hasNoWeapon = true;
+					}
+				}
+				else {
+					if (equipped.get(MBServerStatics.SLOT_MAINHAND) == null) {
+						// debgCombat(ac, "offhand, weapon in other hand");
+						return 1; //no need to attack with this hand
+					}
+				}
+			}
+
+			//Source can attack.
+			//NOTE Don't 'return;' beyond this point until timer created
+			boolean attackFailure = false;
+
+			//Target can't attack on move with ranged weapons.
+			if ((wb != null) && (wb.getRange() > 35f) && abstractCharacter.isMoving()) {
+				// debugCombat(ac, "Cannot attack with throwing weapon while moving");
+				attackFailure = true;
+			}
+
+			//if not enough stamina, then skip attack
+			if (wb == null) {
+				if (abstractCharacter.getStamina() < 1) {
+					// debugCombat(ac, "Not enough stamina to attack");
+					attackFailure = true;
+				}
+			}
+			else if (abstractCharacter.getStamina() < wb.getWeight()) {
+				// debugCombat(ac, "Not enough stamina to attack");
+				attackFailure = true;
+			}
+
+			//skipping for now to test out mask casting.
+			//		//if attacker is casting, then skip this attack
+			//		if (ac.getLastPower() != null) {
+			//			debugCombat(ac, "Cannot attack, curently casting");
+			//			attackFailure = true;
+			//		}
+			//see if attacker is stunned. If so, stop here
+			bonus = abstractCharacter.getBonuses();
+			if (bonus != null && bonus.getBool(ModType.Stunned,SourceType.None)) {
+				// debugCombat(ac, "Cannot attack while stunned");
+				attackFailure = true;
+			}
+
+			//Get Range of weapon
+			float range;
+			if (hasNoWeapon) {
+				range = MBServerStatics.NO_WEAPON_RANGE;
+			}
+			else {
+				range = getWeaponRange(wb);
+				if (bonus != null){
+					float buffRange = 1;
+					buffRange += bonus.getFloat(ModType.WeaponRange, SourceType.None) *.01f;
+					range*= buffRange;
+				}
+			}
+
+			if (abstractCharacter.getObjectType() == GameObjectType.Mob) {
+				Mob minion = (Mob) abstractCharacter;
+				if (minion.isSiege()) {
+					range = 300f;
+				}
+			}
+
+			//Range check.
+			if (NotInRange(abstractCharacter, target, range)) {
+				//target is in stealth and can't be seen by source
+				if (target.getObjectType().equals(GameObjectType.PlayerCharacter) && abstractCharacter.getObjectType().equals(GameObjectType.PlayerCharacter)) {
+					if (!((PlayerCharacter) abstractCharacter).canSee((PlayerCharacter) target)) {
+						// debugCombat(ac, "cannot see target.");
+						return 0;
+					}
+				}
+				attackFailure = true;
+			}
+
+			//handle pet, skip timers (handled by AI)
+			if (abstractCharacter.getObjectType().equals(GameObjectType.Mob)) {
+				Mob mob = (Mob) abstractCharacter;
+				if (mob.isPet()) {
+					attack(abstractCharacter, target, weapon, wb, (slot == MBServerStatics.SLOT_MAINHAND) ? true : false);
+					return 2;
+				}
+			}
+
+			//TODO Verify attacker has los (if not ranged weapon).
+			if (!attackFailure) {
+				if (hasNoWeapon || abstractCharacter.getObjectType().equals(GameObjectType.Mob)) {
+					createTimer(abstractCharacter, slot, 20, true); //2 second for no weapon
+				}
+				else {
+					int wepSpeed = (int) (wb.getSpeed());
+					if (weapon != null && weapon.getBonusPercent(ModType.WeaponSpeed, SourceType.None) != 0f) //add weapon speed bonus
+					{
+						wepSpeed *= (1 + weapon.getBonus(ModType.WeaponSpeed, SourceType.None));
+					}
+					if (abstractCharacter.getBonuses() != null && abstractCharacter.getBonuses().getFloatPercentAll(ModType.AttackDelay, SourceType.None) != 0f) //add effects speed bonus
+					{
+						wepSpeed *= (1 + abstractCharacter.getBonuses().getFloatPercentAll(ModType.AttackDelay, SourceType.None));
+					}
+					if (wepSpeed < 10) {
+						wepSpeed = 10; //Old was 10, but it can be reached lower with legit buffs,effects.
+					}
+					createTimer(abstractCharacter, slot, wepSpeed, true);
+				}
+
+				if (target == null)
+					return 0;
+
+				attack(abstractCharacter, target, weapon, wb, (slot == MBServerStatics.SLOT_MAINHAND) ? true : false);
+			}
+			else {
+				// changed this to half a second to make combat attempts more aggressive than movement sync
+				createTimer(abstractCharacter, slot, 5, false); //0.5 second timer if attack fails
+				//System.out.println("Attack attempt failed");
+			}
+
+		}  catch(Exception e) {
+			return 0;
+		}
+		return 2;
+	}
+
+	private static void debugCombat(AbstractCharacter ac, String reason) {
+		if (ac == null) {
+			return;
+		}
+
+		//if DebugMeleeSync is on, then debug reason for melee failure
+		if (ac.getDebug(64)) {
+			if (ac.getObjectType().equals(GameObjectType.PlayerCharacter)) {
+				String out = "Attack Failure: " + reason;
+				ChatManager.chatSystemInfo((PlayerCharacter) ac, out);
+			}
+		}
+	}
+
+	private static void debugCombatRange(AbstractCharacter ac, Vector3fImmutable sl, Vector3fImmutable tl, float range, float distance) {
+		if (ac == null || sl == null || tl == null) {
+			return;
+		}
+
+		//if DebugMeleeSync is on, then debug reason for melee failure
+		if (ac.getDebug(64)) {
+			if (ac.getObjectType().equals(GameObjectType.PlayerCharacter)) {
+				String out = "Attack Failure: Out of Range: Range: " + distance + ", weaponRange: " + range;
+				out += ", sourceLoc: " + sl.x + ", " + sl.y + ", " + sl.z;
+				out += ", targetLoc: " + tl.x + ", " + tl.y + ", " + tl.z;
+				ChatManager.chatSystemInfo((PlayerCharacter) ac, out);
+			}
+		}
+	}
+
+	private static void createTimer(AbstractCharacter ac, int slot, int time, boolean success) {
+		ConcurrentHashMap<String, JobContainer> timers = ac.getTimers();
+		if (timers != null) {
+			AttackJob aj = new AttackJob(ac, slot, success);
+			JobContainer job;
+			job = JobScheduler.getInstance().scheduleJob(aj, (time * 100));
+			timers.put("Attack" + slot, job);
+		} else {
+			Logger.error( "Unable to find Timers for Character " + ac.getObjectUUID());
+		}
+	}
+
+	/**
+	 * Attempt to attack target
+	 */
+	private static void attack(AbstractCharacter ac, AbstractWorldObject target, Item weapon, ItemBase wb, boolean mainHand) {
+
+		float atr;
+		int minDamage, maxDamage;
+		int errorTrack = 0;
+
+		try {
+
+			if (ac == null)
+				return;
+
+			if (target == null)
+				return;
+
+			if (mainHand) {
+				atr = ac.getAtrHandOne();
+				minDamage = ac.getMinDamageHandOne();
+				maxDamage = ac.getMaxDamageHandOne();
+			}
+			else {
+				atr = ac.getAtrHandTwo();
+				minDamage = ac.getMinDamageHandTwo();
+				maxDamage = ac.getMaxDamageHandTwo();
+			}
+
+			boolean tarIsRat = false;
+
+			if (target.getObjectTypeMask() == MBServerStatics.MASK_RAT)
+				tarIsRat = true;
+			else if (target.getObjectType() == GameObjectType.PlayerCharacter){
+				PlayerCharacter pTar = (PlayerCharacter)target;
+				for (Effect eff: pTar.getEffects().values()){
+					if (eff.getPowerToken() == 429513599 || eff.getPowerToken() == 429415295){
+						tarIsRat = true;
+					}
+				}
+			}
+
+			//Dont think we need to do this anymore.
+			if (tarIsRat){
+				//strip away current % dmg buffs then add with rat %
+				if (ac.getBonuses().getFloatPercentAll(ModType.Slay, SourceType.Rat) != 0){
+					
+
+					float percent = 1 + ac.getBonuses().getFloatPercentAll(ModType.Slay, SourceType.Rat);
+
+					minDamage *= percent;
+					maxDamage *= percent;
+				}
+
+			}
+
+			errorTrack = 1;
+
+			//subtract stamina
+			if (wb == null) {
+				ac.modifyStamina(-0.5f, ac, true);
+			}
+			else {
+				float stam = wb.getWeight() / 3;
+				stam = (stam < 1) ? 1 : stam;
+				ac.modifyStamina(-(stam), ac, true);
+			}
+
+			ac.cancelOnAttackSwing();
+
+			errorTrack = 2;
+
+			//set last time this player has attacked something.
+			if (target.getObjectType().equals(GameObjectType.PlayerCharacter) && target.getObjectUUID() != ac.getObjectUUID() && ac.getObjectType() == GameObjectType.PlayerCharacter) {
+				ac.setTimeStamp("LastCombatPlayer", System.currentTimeMillis());
+				((PlayerCharacter) target).setTimeStamp("LastCombatPlayer", System.currentTimeMillis());
+			}
+			else {
+				ac.setTimeStamp("LastCombatMob", System.currentTimeMillis());
+			}
+
+			errorTrack = 3;
+
+			//Get defense for target
+			float defense;
+			if (target.getObjectType().equals(GameObjectType.Building)) {
+				
+				if (BuildingManager.getBuildingFromCache(target.getObjectUUID()) == null){
+					ac.setCombatTarget(null);
+					return;
+				}
+				defense = 0;
+
+				Building building = (Building)target;
+				if (building.getParentZone() != null && building.getParentZone().isPlayerCity()){
+
+					if (System.currentTimeMillis() > building.getTimeStamp("CallForHelp")){
+						building.getTimestamps().put("CallForHelp", System.currentTimeMillis() + 15000);
+						int count = 0;
+						for (Mob mob:building.getParentZone().zoneMobSet){
+							if (!mob.isPlayerGuard())
+								continue;
+							if (mob.getCombatTarget() != null)
+								continue;
+							if (mob.getGuild() != null && building.getGuild() != null)
+								if (!Guild.sameGuild(mob.getGuild().getNation(), building.getGuild().getNation()))
+									continue;
+
+							if (mob.getLoc().distanceSquared2D(building.getLoc()) > sqr(300))
+								continue;
+
+							if (count == 5)
+								count++;
+
+							mob.setCombatTarget(ac);
+							mob.setState(STATE.Attack);
+						}
+					}
+				}
+			}
+			else {
+				AbstractCharacter tar = (AbstractCharacter) target;
+				defense = tar.getDefenseRating();
+				//Handle target attacking back if in combat and has no other target
+				handleRetaliate(tar, ac);
+			}
+
+			errorTrack = 4;
+
+			//Get hit chance
+			int chance;
+			float dif = atr - defense;
+			if (dif > 100) {
+				chance = 94;
+			}
+			else if (dif < -100) {
+				chance = 4;
+			}
+			else {
+				chance = (int) ((0.45 * dif) + 49);
+			}
+
+			errorTrack = 5;
+
+			//calculate hit/miss
+			int roll = ThreadLocalRandom.current().nextInt(100);
+			DeferredPowerJob dpj = null;
+			if (roll < chance) {
+				if (ac.getObjectType().equals(GameObjectType.PlayerCharacter)) {
+					updateAttackTimers((PlayerCharacter) ac, target, true);
+				}
+
+				boolean skipPassives = false;
+				PlayerBonuses bonuses = ac.getBonuses();
+				if (bonuses != null && bonuses.getBool(ModType.IgnorePassiveDefense, SourceType.None)) {
+					skipPassives = true;
+				}
+
+				AbstractCharacter tarAc = null;
+				if (AbstractWorldObject.IsAbstractCharacter(target)) {
+					tarAc = (AbstractCharacter) target;
+				}
+
+				errorTrack = 6;
+
+				// Apply Weapon power effect if any. don't try to apply twice if
+				// dual wielding. Perform after passive test for sync purposes.
+
+
+				if (ac.getObjectType().equals(GameObjectType.PlayerCharacter) && (mainHand || wb.isTwoHanded())) {
+					dpj = ((PlayerCharacter) ac).getWeaponPower();
+					if (dpj != null) {
+						float attackRange = getWeaponRange(wb);
+
+						dpj.attack(target, attackRange);
+
+						if (dpj.getPower() != null && (dpj.getPowerToken() == -1851459567 || dpj.getPowerToken() == -1851489518))
+							((PlayerCharacter)ac).setWeaponPower(dpj);
+					}
+				}
+				//check to apply second backstab.
+				if (ac.getObjectType().equals(GameObjectType.PlayerCharacter) && !mainHand){
+					dpj = ((PlayerCharacter) ac).getWeaponPower();
+					if (dpj != null && dpj.getPower() != null && (dpj.getPowerToken() == -1851459567 || dpj.getPowerToken() == -1851489518)) {
+						float attackRange = getWeaponRange(wb);
+						dpj.attack(target, attackRange);
+					}
+				}
+
+				errorTrack = 7;
+
+				//Hit, check if passive kicked in
+				boolean passiveFired = false;
+				if (!skipPassives && tarAc != null) {
+					if (target.getObjectType().equals(GameObjectType.PlayerCharacter)) {
+
+						//Handle Block passive
+						if (testPassive(ac, tarAc, "Block") && canTestBlock(ac, target)) {
+
+							if (!target.isAlive())
+								return;
+
+							sendPassiveDefenseMessage(ac, wb, target, MBServerStatics.COMBAT_SEND_BLOCK, dpj,mainHand);
+							passiveFired = true;
+						}
+
+						//Handle Parry passive
+						if (!passiveFired) {
+							if (canTestParry(ac, target) && testPassive(ac, tarAc, "Parry")) {
+								if (!target.isAlive())
+									return;
+								sendPassiveDefenseMessage(ac, wb, target, MBServerStatics.COMBAT_SEND_PARRY, dpj,mainHand);
+								passiveFired = true;
+							}
+						}
+					}
+
+					errorTrack = 8;
+
+					//Handle Dodge passive
+					if (!passiveFired) {
+						if (testPassive(ac, tarAc, "Dodge")) {
+
+							if (!target.isAlive())
+								return;
+
+							sendPassiveDefenseMessage(ac, wb, target, MBServerStatics.COMBAT_SEND_DODGE, dpj,mainHand);
+							passiveFired = true;
+						}
+					}
+				}
+
+				//return if passive (Block, Parry, Dodge) fired
+
+				if (passiveFired)
+					return;
+
+				errorTrack = 9;
+
+				//Hit and no passives
+				//if target is player, set last attack timestamp
+				if (target.getObjectType().equals(GameObjectType.PlayerCharacter)) {
+					updateAttackTimers((PlayerCharacter) target, ac, false);
+				}
+
+				//Get damage Type
+				DamageType damageType;
+				if (wb != null) {
+					damageType = wb.getDamageType();
+				}
+				else if (ac.getObjectType().equals(GameObjectType.Mob) && ((Mob) ac).isSiege()) {
+					damageType = DamageType.Siege;
+				}
+				else {
+					damageType = DamageType.Crush;
+				}
+
+				errorTrack = 10;
+
+				//Get target resists
+				Resists resists = null;
+
+				if (tarAc != null) {
+					resists = tarAc.getResists();
+				}
+				else if (target.getObjectType().equals(GameObjectType.Building)) {
+					resists = ((Building) target).getResists();
+				}
+
+				//make sure target is not immune to damage type;
+				if (resists != null && resists.immuneTo(damageType)) {
+					sendCombatMessage(ac, target, 0f, wb, dpj,mainHand);
+					return;
+				}
+
+				//				PowerProjectileMsg ppm = new PowerProjectileMsg(ac,tarAc);
+				//				DispatchMessage.dispatchMsgToInterestArea(ac, ppm, DispatchChannel.SECONDARY, MBServerStatics.CHARACTER_LOAD_RANGE, false, false);
+				//
+
+				errorTrack = 11;
+
+				//Calculate Damage done
+
+				float damage;
+
+				if (wb != null) {
+					damage = calculateDamage(ac, tarAc, minDamage, maxDamage, damageType, resists);
+				}
+				else {
+					damage = calculateDamage(ac, tarAc, minDamage, maxDamage, damageType, resists);
+				}
+
+				float d = 0f;
+
+				errorTrack = 12;
+
+				//Subtract Damage from target's health
+				if (tarAc != null) {
+					if (tarAc.isSit()) {
+						damage *= 2.5f; //increase damage if sitting
+					}
+					if (tarAc.getObjectType() == GameObjectType.Mob) {
+						ac.setHateValue(damage * MBServerStatics.PLAYER_COMBAT_HATE_MODIFIER);
+						((Mob) tarAc).handleDirectAggro(ac);
+					}
+
+					if (tarAc.getHealth() > 0)
+						d = tarAc.modifyHealth(-damage, ac, false);
+
+				}
+				else if (target.getObjectType().equals(GameObjectType.Building)) {
+					
+					if (BuildingManager.getBuildingFromCache(target.getObjectUUID()) == null){
+						ac.setCombatTarget(null);
+						return;
+					}
+					if (target.getHealth() > 0)
+						d = ((Building) target).modifyHealth(-damage, ac);
+				}
+
+				errorTrack = 13;
+
+				//Test to see if any damage needs done to weapon or armor
+				testItemDamage(ac, target, weapon, wb);
+
+				// if target is dead, we got the killing blow, remove attack timers on our weapons
+				if (tarAc != null && !tarAc.isAlive()) {
+					removeAttackTimers(ac);
+				}
+
+				//test double death fix
+				if (d != 0) {
+					sendCombatMessage(ac, target, damage, wb, dpj,mainHand); //send damage message
+				}
+
+				errorTrack = 14;
+
+				//handle procs
+				if (weapon != null && tarAc != null && tarAc.isAlive()) {
+					ConcurrentHashMap<String, Effect> effects = weapon.getEffects();
+					for (Effect eff : effects.values()) {
+						if (eff == null) {
+							continue;
+						}
+						HashSet<AbstractEffectModifier> aems = eff.getEffectModifiers();
+						if (aems != null) {
+							for (AbstractEffectModifier aem : aems) {
+								if (!tarAc.isAlive()) {
+									break;
+								}
+								if (aem instanceof WeaponProcEffectModifier) {
+									int procChance = ThreadLocalRandom.current().nextInt(100);
+									if (procChance < MBServerStatics.PROC_CHANCE) {
+										((WeaponProcEffectModifier) aem).applyProc(ac, target);
+									}
+								}
+							}
+						}
+					}
+				}
+
+				errorTrack = 15;
+
+				//handle damage shields
+				if (ac.isAlive() && tarAc != null && tarAc.isAlive()) {
+					handleDamageShields(ac, tarAc, damage);
+				}
+			}
+			else {
+				int animationOverride = 0;
+				// Apply Weapon power effect if any.
+				// don't try to apply twice if dual wielding.
+				if (ac.getObjectType().equals(GameObjectType.PlayerCharacter) && (mainHand || wb.isTwoHanded())) {
+					dpj = null;
+					dpj = ((PlayerCharacter) ac).getWeaponPower();
+
+					if (dpj != null) {
+						PowersBase wp = dpj.getPower();
+						if (wp.requiresHitRoll() == false) {
+							float attackRange = getWeaponRange(wb);
+							dpj.attack(target,attackRange);
+						}
+						else {
+							((PlayerCharacter) ac).setWeaponPower(null);
+						}
+
+					}
+				}
+				if (target.getObjectType() == GameObjectType.Mob) {
+					((Mob) target).handleDirectAggro(ac);
+				}
+
+				errorTrack = 17;
+
+				//miss, Send miss message
+				sendCombatMessage(ac, target, 0f, wb, dpj,mainHand);
+
+				//if attacker is player, set last attack timestamp
+				if (ac.getObjectType().equals(GameObjectType.PlayerCharacter)) {
+					updateAttackTimers((PlayerCharacter) ac, target, true);
+				}
+			}
+
+			errorTrack = 18;
+
+			//cancel effects that break on attack or attackSwing
+			ac.cancelOnAttack();
+
+		} catch (Exception e) {
+			Logger.error(ac.getName() + ' ' + errorTrack + ' ' + e.toString());
+		}
+	}
+
+	public static boolean canTestParry(AbstractCharacter ac, AbstractWorldObject target) {
+
+		if (ac == null || target == null || !AbstractWorldObject.IsAbstractCharacter(target))
+			return false;
+
+		AbstractCharacter tar = (AbstractCharacter) target;
+
+		CharacterItemManager acItem = ac.getCharItemManager();
+		CharacterItemManager tarItem = tar.getCharItemManager();
+
+		if (acItem == null || tarItem == null)
+			return false;
+
+		Item acMain = acItem.getItemFromEquipped(1);
+		Item acOff = acItem.getItemFromEquipped(2);
+		Item tarMain = tarItem.getItemFromEquipped(1);
+		Item tarOff = tarItem.getItemFromEquipped(2);
+
+		return !isRanged(acMain) && !isRanged(acOff) && !isRanged(tarMain) && !isRanged(tarOff);
+	}
+
+	public static boolean canTestBlock(AbstractCharacter ac, AbstractWorldObject target) {
+
+		if (ac == null || target == null || !AbstractWorldObject.IsAbstractCharacter(target))
+			return false;
+
+		AbstractCharacter tar = (AbstractCharacter) target;
+
+		CharacterItemManager acItem = ac.getCharItemManager();
+		CharacterItemManager tarItem = tar.getCharItemManager();
+
+		if (acItem == null || tarItem == null)
+			return false;
+
+
+
+		Item tarOff = tarItem.getItemFromEquipped(2);
+
+
+		if (tarOff == null)
+			return false;
+
+		return tarOff.getItemBase().isShield() != false;
+	}
+
+	private static boolean isRanged(Item item) {
+
+		if (item == null)
+			return false;
+
+		ItemBase ib = item.getItemBase();
+
+		if (ib == null)
+			return false;
+
+		if (ib.getType().equals(ItemType.WEAPON) == false)
+			return false;
+
+		return ib.getRange() > MBServerStatics.RANGED_WEAPON_RANGE;
+
+
+	}
+
+	private static float calculateDamage(AbstractCharacter source, AbstractCharacter target, float minDamage, float maxDamage, DamageType damageType, Resists resists) {
+		//get range between min and max
+		float range = maxDamage - minDamage;
+
+		//Damage is calculated twice to average a more central point
+		float damage = ThreadLocalRandom.current().nextFloat() * range;
+		damage = (damage + (ThreadLocalRandom.current().nextFloat() * range)) *.5f;
+
+		//put it back between min and max
+		damage += minDamage;
+
+		//calculate resists in if any
+		if (resists != null) {
+			return resists.getResistedDamage(source, target, damageType, damage, 0);
+		} else {
+			return damage;
+		}
+	}
+
+	private static void sendPassiveDefenseMessage(AbstractCharacter source, ItemBase wb, AbstractWorldObject target, int passiveType, DeferredPowerJob dpj, boolean mainHand) {
+
+		int swingAnimation =  getSwingAnimation(wb, dpj,mainHand);
+
+		if (dpj != null){
+			if(PowersManager.AnimationOverrides.containsKey(dpj.getAction().getEffectID()))
+				swingAnimation = PowersManager.AnimationOverrides.get(dpj.getAction().getEffectID());
+		}
+		TargetedActionMsg cmm = new TargetedActionMsg(source,swingAnimation, target, passiveType);
+		DispatchMessage.sendToAllInRange(target, cmm);
+
+	}
+
+	private static void sendCombatMessage(AbstractCharacter source, AbstractWorldObject target, float damage, ItemBase wb, DeferredPowerJob dpj, boolean mainHand) {
+
+		int swingAnimation =  getSwingAnimation(wb, dpj,mainHand);
+
+		if (dpj != null){
+			if(PowersManager.AnimationOverrides.containsKey(dpj.getAction().getEffectID()))
+				swingAnimation = PowersManager.AnimationOverrides.get(dpj.getAction().getEffectID());
+		}
+
+		if (source.getObjectType() == GameObjectType.PlayerCharacter){
+			for (Effect eff: source.getEffects().values()){
+				if (eff.getPower() != null && (eff.getPower().getToken() == 429506943 || eff.getPower().getToken() == 429408639 || eff.getPower().getToken() == 429513599 ||eff.getPower().getToken() ==  429415295))
+					swingAnimation = 0;
+			}
+		}
+		TargetedActionMsg cmm = new TargetedActionMsg(source, target, damage, swingAnimation);
+		DispatchMessage.sendToAllInRange(target, cmm);
+	}
+
+	public static int animation = 0;
+
+	public static int getSwingAnimation(ItemBase wb, DeferredPowerJob dpj, boolean mainHand) {
+		int token = 0;
+		if (dpj != null) {
+			token = (dpj.getPower() != null) ? dpj.getPower().getToken() : 0;
+		}
+
+		if (token == 563721004) //kick animation
+		{
+			return 79;
+		}
+
+		if (CombatManager.animation != 0) {
+			return CombatManager.animation;
+		}
+
+		if (wb == null) {
+			return 75;
+		}
+		if (mainHand){
+			if (wb.getAnimations().size() > 0){
+				int animation = wb.getAnimations().get(0);
+				int random = ThreadLocalRandom.current().nextInt(wb.getAnimations().size());
+				try{
+					animation = wb.getAnimations().get(random);
+					return animation;
+				}catch(Exception e){
+					Logger.error( e.getMessage());
+					return wb.getAnimations().get(0);
+
+				}
+
+			}else if (wb.getOffHandAnimations().size() > 0){
+				int animation = wb.getOffHandAnimations().get(0);
+				int random = ThreadLocalRandom.current().nextInt(wb.getOffHandAnimations().size());
+				try{
+					animation = wb.getOffHandAnimations().get(random);
+					return animation;
+				}catch(Exception e){
+					Logger.error( e.getMessage());
+					return wb.getOffHandAnimations().get(0);
+
+				}
+			}
+		}else{
+			if (wb.getOffHandAnimations().size() > 0){
+				int animation = wb.getOffHandAnimations().get(0);
+				int random = ThreadLocalRandom.current().nextInt(wb.getOffHandAnimations().size());
+				try{
+					animation = wb.getOffHandAnimations().get(random);
+					return animation;
+				}catch(Exception e){
+					Logger.error( e.getMessage());
+					return wb.getOffHandAnimations().get(0);
+
+				}
+			}else
+				if (wb.getAnimations().size() > 0){
+					int animation = wb.getAnimations().get(0);
+					int random = ThreadLocalRandom.current().nextInt(wb.getAnimations().size());
+					try{
+						animation = wb.getAnimations().get(random);
+						return animation;
+					}catch(Exception e){
+						Logger.error( e.getMessage());
+						return wb.getAnimations().get(0);
+
+					}
+
+				}
+		}
+
+
+		String required = wb.getSkillRequired();
+		String mastery = wb.getMastery();
+		if (required.equals("Unarmed Combat")) {
+			return 75;
+		} else if (required.equals("Sword")) {
+			if (wb.isTwoHanded()) {
+				return 105;
+			} else {
+				return 98;
+			}
+		} else if (required.equals("Staff") || required.equals("Pole Arm")) {
+			return 85;
+		} else if (required.equals("Spear")) {
+			return 92;
+		} else if (required.equals("Hammer") || required.equals("Axe")) {
+			if (wb.isTwoHanded()) {
+				return 105;
+			} else if (mastery.equals("Throwing")) {
+				return 115;
+			} else {
+				return 100;
+			}
+		} else if (required.equals("Dagger")) {
+			if (mastery.equals("Throwing")) {
+				return 117;
+			} else {
+				return 81;
+			}
+		} else if (required.equals("Crossbow")) {
+			return 110;
+		} else if (required.equals("Bow")) {
+			return 109;
+		} else if (wb.isTwoHanded()) {
+			return 105;
+		} else {
+			return 100;
+		}
+	}
+
+	private static boolean testPassive(AbstractCharacter source, AbstractCharacter target, String type) {
+
+		float chance = target.getPassiveChance(type, source.getLevel(), true);
+
+		if (chance == 0f)
+			return false;
+
+
+		//max 75% chance of passive to fire
+		if (chance > 75f)
+			chance = 75f;
+
+		int roll = ThreadLocalRandom.current().nextInt(100);
+
+		//Passive fired
+		//Passive did not fire
+		return roll < chance;
+
+	}
+
+	private static void updateAttackTimers(PlayerCharacter pc, AbstractWorldObject target, boolean attack) {
+
+		//Set Attack Timers
+		if (target.getObjectType().equals(GameObjectType.PlayerCharacter))
+			pc.setLastPlayerAttackTime();
+		else
+			pc.setLastMobAttackTime();
+	}
+
+	public static float getWeaponRange(ItemBase weapon) {
+		if (weapon == null)
+			return 0f;
+
+		return weapon.getRange();
+	}
+
+	public static void toggleCombat(ToggleCombatMsg msg, ClientConnection origin) {
+		toggleCombat(msg.toggleCombat(), origin);
+	}
+
+	public static void toggleCombat(SetCombatModeMsg msg, ClientConnection origin) {
+		toggleCombat(msg.getToggle(), origin);
+	}
+
+	private static void toggleCombat(boolean toggle, ClientConnection origin) {
+
+		PlayerCharacter pc = SessionManager.getPlayerCharacter(origin);
+
+		if (pc == null)
+			return;
+
+		pc.setCombat(toggle);
+
+		if (!toggle) // toggle is move it to false so clear combat target
+			pc.setCombatTarget(null); //clear last combat target
+
+		UpdateStateMsg rwss = new UpdateStateMsg();
+		rwss.setPlayer(pc);
+		DispatchMessage.dispatchMsgToInterestArea(pc, rwss, DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, false, false);
+	}
+
+	private static void toggleSit(boolean toggle, ClientConnection origin) {
+
+		PlayerCharacter pc = SessionManager.getPlayerCharacter(origin);
+
+		if (pc == null)
+			return;
+
+		pc.setSit(toggle);
+
+		UpdateStateMsg rwss = new UpdateStateMsg();
+		rwss.setPlayer(pc);
+		DispatchMessage.dispatchMsgToInterestArea(pc, rwss,  DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, true,false);
+	}
+
+	public static boolean NotInRange(AbstractCharacter ac, AbstractWorldObject target, float range) {
+		Vector3fImmutable sl = ac.getLoc();
+		Vector3fImmutable tl = target.getLoc();
+		//add Hitbox's to range.
+		range += (calcHitBox(ac) + calcHitBox(target));
+
+		float magnitudeSquared = tl.distanceSquared(sl);
+
+		return magnitudeSquared > range * range;
+
+	}
+
+	//Called when character takes damage.
+	public static void handleRetaliate(AbstractCharacter tarAc, AbstractCharacter ac) {
+		if (ac == null || tarAc == null) {
+			return;
+		}
+		if (ac.equals(tarAc)) {
+			return;
+		}
+		
+		if (tarAc.isMoving() && tarAc.getObjectType().equals(GameObjectType.PlayerCharacter))
+			return;
+		
+		if (!tarAc.isAlive() || !ac.isAlive())
+			return;
+		boolean isCombat = tarAc.isCombat();
+		//If target in combat and has no target, then attack back
+		AbstractWorldObject awoCombTar = tarAc.getCombatTarget();
+		if ((tarAc.isCombat() && awoCombTar == null) || (isCombat && awoCombTar != null && (!awoCombTar.isAlive() ||tarAc.isCombat() && NotInRange(tarAc, awoCombTar, tarAc.getRange()))) || (tarAc != null && tarAc.getObjectType() == GameObjectType.Mob && ((Mob) tarAc).isSiege())) {
+			// we are in combat with no valid target
+			if (tarAc.getObjectType().equals(GameObjectType.PlayerCharacter)) {
+				PlayerCharacter pc = (PlayerCharacter) tarAc;
+				tarAc.setCombatTarget(ac);
+				pc.setLastTarget(ac.getObjectType(), ac.getObjectUUID());
+				if (tarAc.getTimers() != null) {
+					if (!tarAc.getTimers().containsKey("Attack" + MBServerStatics.SLOT_MAINHAND)) {
+						CombatManager.AttackTarget((PlayerCharacter) tarAc, tarAc.getCombatTarget());
+					}
+				}
+			}
+		}
+
+		//Handle pet retaliate if assist is on and pet doesn't have a target.
+		if (tarAc.getObjectType().equals(GameObjectType.PlayerCharacter)) {
+			Mob pet = ((PlayerCharacter) tarAc).getPet();
+			if (pet != null && pet.assist() && pet.getCombatTarget() == null) {
+				pet.setCombatTarget(ac);
+				pet.setState(STATE.Retaliate);
+			}
+		}
+
+		//Handle Mob Retaliate.
+		if (tarAc.getObjectType() == GameObjectType.Mob) {
+			Mob retaliater = (Mob) tarAc;
+			if (retaliater.getCombatTarget() != null && !retaliater.isSiege())
+				return;
+			if (ac.getObjectType() == GameObjectType.Mob && retaliater.isSiege())
+				return;
+			retaliater.setCombatTarget(ac);
+			retaliater.setState(STATE.Retaliate);
+
+		}
+	}
+
+	public static void handleDamageShields(AbstractCharacter ac, AbstractCharacter target, float damage) {
+		if (ac == null || target == null) {
+			return;
+		}
+		PlayerBonuses bonuses = target.getBonuses();
+		if (bonuses != null) {
+			ConcurrentHashMap<AbstractEffectModifier, DamageShield> damageShields = bonuses.getDamageShields();
+			float total = 0;
+			for (DamageShield ds : damageShields.values()) {
+				//get amount to damage back
+				float amount;
+				if (ds.usePercent()) {
+					amount = damage * ds.getAmount() / 100;
+				} else {
+					amount = ds.getAmount();
+				}
+
+				//get resisted damage for damagetype
+				Resists resists = ac.getResists();
+				if (resists != null) {
+					amount = resists.getResistedDamage(target, ac, ds.getDamageType(), amount, 0);
+				}
+
+				total += amount;
+			}
+			if (total > 0) {
+				//apply Damage back
+				ac.modifyHealth(-total, target, true);
+
+				TargetedActionMsg cmm = new TargetedActionMsg(ac,ac, total, 0);
+				DispatchMessage.sendToAllInRange(target, cmm);
+
+			}
+		}
+	}
+
+	public static float calcHitBox(AbstractWorldObject ac) {
+		//TODO Figure out how Str Affects HitBox
+		float hitBox = 1;
+		switch(ac.getObjectType()){
+		case PlayerCharacter:
+			PlayerCharacter pc = (PlayerCharacter)ac;
+			if (MBServerStatics.COMBAT_TARGET_HITBOX_DEBUG) {
+				Logger.info("Hit box radius for " + pc.getFirstName() + " is " + ((int) pc.statStrBase / 20f));
+			}
+			hitBox = 1.5f + (int) ((PlayerCharacter) ac).statStrBase / 20f;
+			break;
+
+		case Mob:
+			Mob mob = (Mob)ac;
+			if (MBServerStatics.COMBAT_TARGET_HITBOX_DEBUG)
+				Logger.info( "Hit box radius for " + mob.getFirstName()
+				+ " is " + ((Mob) ac).getMobBase().getHitBoxRadius());
+
+			hitBox = ((Mob) ac).getMobBase().getHitBoxRadius();
+			break;
+		case Building:
+			Building building = (Building)ac;
+			if (building.getBlueprint() == null)
+				return 32;
+			hitBox = Math.max(building.getBlueprint().getBuildingGroup().getExtents().x,
+					building.getBlueprint().getBuildingGroup().getExtents().y);
+			if (MBServerStatics.COMBAT_TARGET_HITBOX_DEBUG)
+				Logger.info( "Hit box radius for " + building.getName() + " is " + hitBox);
+			break;
+
+		}
+		return hitBox;
+	}
+
+	private static void testItemDamage(AbstractCharacter ac, AbstractWorldObject awo, Item weapon, ItemBase wb) {
+		if (ac == null) {
+			return;
+		}
+
+		//get chance to damage
+		int chance = 4500;
+		if (wb != null) {
+			if (wb.isGlass()) //glass used weighted so fast weapons don't break faster
+			{
+				chance = 9000 / wb.getWeight();
+			}
+		}
+		//test damaging attackers weapon
+		int takeDamage = ThreadLocalRandom.current().nextInt(chance);
+		if (takeDamage == 0 && wb != null && (ac.getObjectType().equals(GameObjectType.PlayerCharacter))) {
+			ac.getCharItemManager().damageItem(weapon, 1);
+		}
+
+		//test damaging targets gear
+		takeDamage = ThreadLocalRandom.current().nextInt(chance);
+		if (takeDamage == 0 && awo != null && (awo.getObjectType().equals(GameObjectType.PlayerCharacter))) {
+			((AbstractCharacter) awo).getCharItemManager().damageRandomArmor(1);
+		}
+	}
+
+}
diff --git a/src/engine/gameManager/ConfigManager.java b/src/engine/gameManager/ConfigManager.java
new file mode 100644
index 00000000..d7f5041d
--- /dev/null
+++ b/src/engine/gameManager/ConfigManager.java
@@ -0,0 +1,113 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+package engine.gameManager;
+
+/* This enumeration implements Magicbane's configuration data which
+   is loaded from environment variables.
+ */
+
+import engine.Enum;
+import engine.net.NetMsgHandler;
+import engine.server.login.LoginServer;
+import engine.server.world.WorldServer;
+import org.pmw.tinylog.Logger;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public enum ConfigManager {
+
+    // Bind address can differ from public address
+    // when running over a network bridge, etc.
+
+    MB_PUBLIC_ADDR,
+    MB_BIND_ADDR,
+
+    // Database connection config
+
+    MB_DATABASE_ADDRESS,
+    MB_DATABASE_PORT,
+    MB_DATABASE_NAME,
+    MB_DATABASE_USER,
+    MB_DATABASE_PASS,
+
+    // Data warehouse remote connection
+
+    MB_WAREHOUSE_ADDR,
+    MB_WAREHOUSE_USER,
+    MB_WAREHOUSE_PASS,
+
+    // Login server config
+
+    MB_LOGIN_PORT,
+    MB_MAJOR_VER,
+    MB_MINOR_VER,
+
+    // Worldserver configuration
+
+    MB_WORLD_NAME,
+    MB_WORLD_MAPID,
+    MB_WORLD_PORT,
+    MB_WORLD_ACCESS_LVL,
+    MB_WORLD_UUID,
+    MB_WORLD_WAREHOUSE_PUSH,
+    MB_WORLD_MAINTENANCE,
+    MB_WORLD_MAINTENANCE_HOUR,
+    MB_WORLD_GREETING,
+    MB_WORLD_KEYCLONE_MAX,
+
+    // MagicBot configuration.
+
+    MB_MAGICBOT_SERVERID,
+    MB_MAGICBOT_BOTTOKEN,
+    MB_MAGICBOT_ROLEID,
+    MB_MAGICBOT_ANNOUNCE,
+    MB_MAGICBOT_SEPTIC,
+    MB_MAGICBOT_CHANGELOG,
+    MB_MAGICBOT_POLITICAL,
+    MB_MAGICBOT_GENERAL,
+    MB_MAGICBOT_FORTOFIX,
+    MB_MAGICBOT_RECRUIT,
+    MB_MAGICBOT_BOTVERSION,
+    MB_MAGICBOT_GAMEVERSION;
+    
+    // Map to hold our config pulled in from the environment
+    // We also use the config to point to the current message pump
+    // and determine the server type at runtime.
+
+    public static Map<String, String> configMap = new HashMap(System.getenv());
+    public static Enum.ServerType serverType = Enum.ServerType.NONE;
+    public static NetMsgHandler handler;
+    public static WorldServer worldServer;
+    public static LoginServer loginServer;
+
+    // Called at bootstrap: ensures that all config values are loaded.
+
+    public static boolean init() {
+
+        Logger.info("ConfigManager: init()");
+
+        for (ConfigManager configSetting : ConfigManager.values())
+            if (configMap.containsKey(configSetting.name()))
+                Logger.info(configSetting.name() + ":" + configSetting.getValue());
+            else {
+                Logger.error("Missing Config: " + configSetting.name());
+                return false;
+            }
+
+      return true;
+    }
+
+    // Get the value associated with this enumeration
+
+    public  String getValue() {
+      return configMap.get(this.name());
+    }
+    public  void  setValue(String value) { configMap.put(this.name(), value); }
+}
diff --git a/src/engine/gameManager/DbManager.java b/src/engine/gameManager/DbManager.java
new file mode 100644
index 00000000..d99bbbc9
--- /dev/null
+++ b/src/engine/gameManager/DbManager.java
@@ -0,0 +1,314 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+package engine.gameManager;
+
+import engine.Enum;
+import engine.Enum.GameObjectType;
+import engine.db.handlers.*;
+import engine.objects.*;
+import engine.pooling.ConnectionPool;
+import engine.server.MBServerStatics;
+import engine.util.Hasher;
+import org.pmw.tinylog.Logger;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+import java.util.EnumMap;
+import java.util.concurrent.ConcurrentHashMap;
+
+public enum DbManager {
+	DBMANAGER;
+
+	private static ConnectionPool connPool;
+	public static Hasher hasher;
+
+	//Local Object Caching
+
+	private static final EnumMap<GameObjectType, ConcurrentHashMap<Integer, AbstractGameObject>> objectCache = new EnumMap<>(GameObjectType.class);
+
+	public static boolean configureDatabaseLayer() {
+
+		boolean worked = true;
+
+		try {
+			DbManager.connPool = new ConnectionPool();
+			DbManager.connPool.fill(10);
+			DBMANAGER.hasher = new Hasher();
+		} catch (Exception e ) {
+			e.printStackTrace();
+			worked = false;
+		}
+		return worked;
+	}
+
+	public static AbstractGameObject getObject(GameObjectType objectType, int objectUUID) {
+
+		AbstractGameObject outObject = null;
+
+		switch (objectType) {
+		case PlayerCharacter:
+			outObject = PlayerCharacter.getPlayerCharacter(objectUUID);
+			break;
+		case NPC:
+			outObject =  NPC.getNPC(objectUUID);
+			break;
+		case Mob:
+			outObject = Mob.getFromCache(objectUUID);
+			break;
+		case Building:
+			outObject = BuildingManager.getBuilding(objectUUID);
+			break;
+		case Guild:
+			outObject = Guild.getGuild(objectUUID);
+			break;
+		case Item:
+			outObject = Item.getFromCache(objectUUID);
+			break;
+		case MobLoot:
+			outObject = MobLoot.getFromCache(objectUUID);
+			break;
+		case City:
+			outObject = City.getCity(objectUUID);
+			break;
+			default:
+				Logger.error("Attempt to retrieve nonexistant " + objectType +
+						        " from object cache." );
+				break;
+
+		}
+
+		return outObject;
+	}
+
+	public static int getPoolSize(){
+		return connPool.getPoolSize();
+	}
+
+	public static boolean inCache(GameObjectType gameObjectType, int uuid) {
+
+		if (objectCache.get(gameObjectType) == null)
+			return false;
+
+		return (objectCache.get(gameObjectType).containsKey(uuid));
+
+	}
+
+	public static AbstractGameObject getFromCache(GameObjectType gameObjectType, int uuid) {
+
+		if (objectCache.get(gameObjectType) == null)
+			return null;
+
+		return objectCache.get(gameObjectType).get(uuid);
+
+	}
+
+	public static void removeFromCache(GameObjectType gameObjectType, int uuid) {
+
+		AbstractGameObject abstractGameObject;
+
+		if (objectCache.get(gameObjectType) == null)
+			return;
+
+		abstractGameObject = objectCache.get(gameObjectType).get(uuid);
+
+		if (abstractGameObject == null)
+			return;
+
+		removeFromCache(abstractGameObject);
+
+	}
+
+	public static void removeFromCache(AbstractGameObject abstractGameObject) {
+
+		if (abstractGameObject == null)
+			return;
+
+		if (objectCache.get(abstractGameObject.getObjectType()) == null)
+			return;
+
+		// Remove object from game cache
+
+		objectCache.get(abstractGameObject.getObjectType()).remove(abstractGameObject.getObjectUUID());
+
+		// Release bounds as we're dispensing with this object.
+
+		if (abstractGameObject instanceof AbstractWorldObject) {
+			AbstractWorldObject abstractWorldObject = (AbstractWorldObject)abstractGameObject;
+
+			if (abstractWorldObject.getBounds() != null) {
+				abstractWorldObject.getBounds().release();
+				abstractWorldObject.setBounds(null);
+			}
+		}
+
+	}
+
+	public static boolean addToCache(AbstractGameObject gameObject) {
+
+		boolean isWorldServer = ConfigManager.serverType.equals(Enum.ServerType.WORLDSERVER);
+
+		if (!isWorldServer) {
+			if (MBServerStatics.SKIP_CACHE_LOGIN)
+				return true;
+			if (MBServerStatics.SKIP_CACHE_LOGIN_PLAYER
+					&& (gameObject.getObjectType() == GameObjectType.PlayerCharacter))
+				return true;
+			if (MBServerStatics.SKIP_CACHE_LOGIN_ITEM &&
+					(gameObject.getObjectType() == GameObjectType.Item))
+				return true;
+		}
+
+		// First time this object type has been cached.  Create the hashmap.
+
+		if (objectCache.get(gameObject.getObjectType()) == null) {
+
+			int initialCapacity;
+
+			// Provide initial sizing hints
+
+			switch (gameObject.getObjectType()) {
+			case Building:
+				initialCapacity = 46900;
+				break;
+			case Mob:
+				initialCapacity = 11700;
+				break;
+			case NPC:
+				initialCapacity = 900;
+				break;
+			case Zone:
+				initialCapacity = 1070;
+				break;
+			case Account:
+				initialCapacity = 10000;
+				break;
+			case Guild:
+				initialCapacity = 100;
+				break;
+			case ItemContainer:
+				initialCapacity = 100;
+				break;
+			case Item:
+				initialCapacity = 1000;
+				break;
+			case MobLoot:
+				initialCapacity = 10000;
+				break;
+			case PlayerCharacter:
+				initialCapacity = 100;
+				break;
+			default:
+				initialCapacity = 100; // Lookup api default should be ok for small maps
+				break;
+			}
+			objectCache.put(gameObject.getObjectType(), new ConcurrentHashMap<>(initialCapacity));
+		}
+
+		// Add the object to the cache.  This will overwrite the current map entry.
+
+		objectCache.get(gameObject.getObjectType()).put(gameObject.getObjectUUID(), gameObject);
+
+		return true;
+	}
+
+	public static java.util.Collection<AbstractGameObject> getList(GameObjectType gameObjectType) {
+
+		if (objectCache.get(gameObjectType) == null)
+			return null;
+
+		return objectCache.get(gameObjectType).values();
+	}
+
+	public static PreparedStatement prepareStatement(String sql) throws SQLException {
+		return getConn().prepareStatement(sql, 1);
+	}
+
+	// Omg refactor this out, somebody!
+
+	public static ConcurrentHashMap<Integer, AbstractGameObject> getMap(
+			GameObjectType gameObjectType) {
+
+		if (objectCache.get(gameObjectType) == null)
+			return null;
+
+		return objectCache.get(gameObjectType);
+
+	}
+
+	public static void printCacheCount(PlayerCharacter pc) {
+		ChatManager.chatSystemInfo(pc, "Cache Lists");
+
+		for (GameObjectType gameObjectType : GameObjectType.values()) {
+
+			if (objectCache.get(gameObjectType) == null)
+				continue;
+
+			String ret = gameObjectType.name() + ": " + objectCache.get(gameObjectType).size();
+			ChatManager.chatSystemInfo(pc, ret + '\n');
+		}
+	}
+
+	/**
+	 * @return the conn
+	 */
+	//XXX I think we have a severe resource leak here! No one is putting the connections back!
+	public static Connection getConn() {
+		Connection conn = DbManager.connPool.get();
+		try {
+			if (!conn.isClosed())
+				DbManager.connPool.put(conn);
+		} catch (SQLException e) {
+			Logger.error( e.toString());
+		}
+		return conn;
+	}
+
+	public static final dbAccountHandler AccountQueries = new dbAccountHandler();
+	public static final dbBaneHandler BaneQueries = new dbBaneHandler();
+	public static final dbBaseClassHandler BaseClassQueries = new dbBaseClassHandler();
+	public static final dbBuildingHandler BuildingQueries = new dbBuildingHandler();
+	public static final dbBuildingLocationHandler BuildingLocationQueries = new dbBuildingLocationHandler();
+	public static final dbCharacterPowerHandler CharacterPowerQueries = new dbCharacterPowerHandler();
+	public static final dbCharacterRuneHandler CharacterRuneQueries = new dbCharacterRuneHandler();
+	public static final dbCharacterSkillHandler CharacterSkillQueries = new dbCharacterSkillHandler();
+	public static final dbCityHandler CityQueries = new dbCityHandler();
+	public static final dbContractHandler ContractQueries = new dbContractHandler();
+	public static final dbWarehouseHandler WarehouseQueries = new dbWarehouseHandler();
+	public static final dbCSSessionHandler CSSessionQueries = new dbCSSessionHandler();
+	public static final dbEnchantmentHandler EnchantmentQueries = new dbEnchantmentHandler();
+	public static final dbEffectsResourceCostHandler EffectsResourceCostsQueries = new dbEffectsResourceCostHandler();
+	public static final dbGuildHandler GuildQueries = new dbGuildHandler();
+	public static final dbItemHandler ItemQueries = new dbItemHandler();
+	public static final dbItemBaseHandler ItemBaseQueries = new dbItemBaseHandler();
+	public static final dbKitHandler KitQueries = new dbKitHandler();
+	public static final dbLootTableHandler LootQueries = new dbLootTableHandler();
+	public static final dbMenuHandler MenuQueries = new dbMenuHandler();
+	public static final dbMineHandler MineQueries = new dbMineHandler();
+	public static final dbMobHandler MobQueries = new dbMobHandler();
+	public static final dbMobBaseHandler MobBaseQueries = new dbMobBaseHandler();
+	public static final dbNPCHandler NPCQueries = new dbNPCHandler();
+	public static final dbPlayerCharacterHandler PlayerCharacterQueries = new dbPlayerCharacterHandler();
+	public static final dbPromotionClassHandler PromotionQueries = new dbPromotionClassHandler();
+	public static final dbRaceHandler RaceQueries = new dbRaceHandler();
+	public static final dbResistHandler ResistQueries = new dbResistHandler();
+	public static final dbRuneBaseAttributeHandler RuneBaseAttributeQueries = new dbRuneBaseAttributeHandler();
+	public static final dbRuneBaseEffectHandler RuneBaseEffectQueries = new dbRuneBaseEffectHandler();
+	public static final dbRuneBaseHandler RuneBaseQueries = new dbRuneBaseHandler();
+	public static final dbSkillBaseHandler SkillsBaseQueries = new dbSkillBaseHandler();
+	public static final dbSkillReqHandler SkillReqQueries = new dbSkillReqHandler();
+	public static final dbSpecialLootHandler SpecialLootQueries = new dbSpecialLootHandler();
+	public static final dbVendorDialogHandler VendorDialogQueries = new dbVendorDialogHandler();
+	public static final dbZoneHandler ZoneQueries = new dbZoneHandler();
+	public static final dbRealmHandler RealmQueries = new dbRealmHandler();
+	public static final dbBlueprintHandler BlueprintQueries = new dbBlueprintHandler();
+	public static final dbBoonHandler BoonQueries = new dbBoonHandler();
+	public static final dbShrineHandler ShrineQueries = new dbShrineHandler();
+	public static final dbHeightMapHandler HeightMapQueries = new dbHeightMapHandler();
+}
diff --git a/src/engine/gameManager/DevCmdManager.java b/src/engine/gameManager/DevCmdManager.java
new file mode 100644
index 00000000..6152e0d6
--- /dev/null
+++ b/src/engine/gameManager/DevCmdManager.java
@@ -0,0 +1,228 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+package engine.gameManager;
+
+import engine.Enum;
+import engine.devcmd.AbstractDevCmd;
+import engine.devcmd.cmds.*;
+import engine.objects.AbstractGameObject;
+import engine.objects.Account;
+import engine.objects.PlayerCharacter;
+import engine.server.MBServerStatics;
+import org.pmw.tinylog.Logger;
+
+import java.util.ArrayList;
+import java.util.Map.Entry;
+import java.util.concurrent.ConcurrentHashMap;
+
+public enum DevCmdManager {
+		DEV_CMD_MANAGER;
+
+	public static ConcurrentHashMap<String, AbstractDevCmd> devCmds;
+
+	DevCmdManager() {
+		init();
+	}
+	
+	public static void init() {
+		DevCmdManager.devCmds = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW);
+		DevCmdManager.registerCommands();
+	}
+
+	/**
+	 *
+	 */
+	private static void registerCommands() {
+	
+		// Player
+		DevCmdManager.registerDevCmd(new DistanceCmd());;
+		DevCmdManager.registerDevCmd(new HelpCmd());
+		DevCmdManager.registerDevCmd(new GetZoneCmd());
+		DevCmdManager.registerDevCmd(new GetZoneMobsCmd());
+		DevCmdManager.registerDevCmd(new PrintBankCmd());
+		DevCmdManager.registerDevCmd(new PrintEquipCmd());
+		DevCmdManager.registerDevCmd(new PrintInventoryCmd());
+		DevCmdManager.registerDevCmd(new PrintVaultCmd());
+		DevCmdManager.registerDevCmd(new PrintStatsCmd());
+		DevCmdManager.registerDevCmd(new PrintSkillsCmd());
+		DevCmdManager.registerDevCmd(new PrintPowersCmd());
+		DevCmdManager.registerDevCmd(new PrintBonusesCmd());
+		DevCmdManager.registerDevCmd(new PrintResistsCmd());
+		DevCmdManager.registerDevCmd(new PrintLocationCmd());
+		DevCmdManager.registerDevCmd(new InfoCmd());
+		DevCmdManager.registerDevCmd(new GetHeightCmd());
+
+		// Tester
+		DevCmdManager.registerDevCmd(new JumpCmd());
+		DevCmdManager.registerDevCmd(new GotoCmd());
+		DevCmdManager.registerDevCmd(new SummonCmd());
+		DevCmdManager.registerDevCmd(new SetHealthCmd());
+		DevCmdManager.registerDevCmd(new SetManaCmd());
+		DevCmdManager.registerDevCmd(new SetStaminaCmd());
+		DevCmdManager.registerDevCmd(new FindBuildingsCmd());
+		DevCmdManager.registerDevCmd(new TeleportModeCmd());
+		DevCmdManager.registerDevCmd(new SetLevelCmd());
+		DevCmdManager.registerDevCmd(new SetBaseClassCmd());
+		DevCmdManager.registerDevCmd(new SetPromotionClassCmd());
+		DevCmdManager.registerDevCmd(new EffectCmd());
+		DevCmdManager.registerDevCmd(new SetRuneCmd());
+		DevCmdManager.registerDevCmd(new GetOffsetCmd());
+		DevCmdManager.registerDevCmd(new DebugCmd());
+		DevCmdManager.registerDevCmd(new AddGoldCmd());
+		DevCmdManager.registerDevCmd(new ZoneInfoCmd());
+		DevCmdManager.registerDevCmd(new DebugMeleeSyncCmd());
+		DevCmdManager.registerDevCmd(new HotzoneCmd());
+		DevCmdManager.registerDevCmd(new SetActivateMineCmd());
+		// Dev
+		DevCmdManager.registerDevCmd(new ApplyStatModCmd());
+		DevCmdManager.registerDevCmd(new AddBuildingCmd());
+		DevCmdManager.registerDevCmd(new AddNPCCmd());
+		DevCmdManager.registerDevCmd(new AddMobCmd());
+		DevCmdManager.registerDevCmd(new CopyMobCmd());
+		DevCmdManager.registerDevCmd(new RemoveObjectCmd());
+		DevCmdManager.registerDevCmd(new RotateCmd());
+		DevCmdManager.registerDevCmd(new FlashMsgCmd());
+		DevCmdManager.registerDevCmd(new SysMsgCmd());
+		DevCmdManager.registerDevCmd(new GetBankCmd());
+		DevCmdManager.registerDevCmd(new GetVaultCmd());
+		DevCmdManager.registerDevCmd(new CombatMessageCmd());
+		DevCmdManager.registerDevCmd(new RenameMobCmd());
+		DevCmdManager.registerDevCmd(new RenameCmd());
+		DevCmdManager.registerDevCmd(new CreateItemCmd());
+		DevCmdManager.registerDevCmd(new GetMemoryCmd());
+		DevCmdManager.registerDevCmd(new SetRankCmd());
+		DevCmdManager.registerDevCmd(new MakeBaneCmd());
+		DevCmdManager.registerDevCmd(new RemoveBaneCmd());
+		DevCmdManager.registerDevCmd(new SetBaneActiveCmd());
+		DevCmdManager.registerDevCmd(new SetAdminRuneCmd());
+		DevCmdManager.registerDevCmd(new SetInvulCmd());
+		DevCmdManager.registerDevCmd(new MakeItemCmd());
+		DevCmdManager.registerDevCmd(new EnchantCmd());
+		DevCmdManager.registerDevCmd(new SetSubRaceCmd());
+		// Admin
+		DevCmdManager.registerDevCmd(new GetCacheCountCmd());
+		DevCmdManager.registerDevCmd(new GetRuneDropRateCmd());
+		DevCmdManager.registerDevCmd(new DecachePlayerCmd());
+		DevCmdManager.registerDevCmd(new SetRateCmd());
+		DevCmdManager.registerDevCmd(new AuditMobsCmd());
+		DevCmdManager.registerDevCmd(new ChangeNameCmd());
+		DevCmdManager.registerDevCmd(new GuildListCmd());
+		DevCmdManager.registerDevCmd(new SetGuildCmd());
+		DevCmdManager.registerDevCmd(new SetOwnerCmd());
+		DevCmdManager.registerDevCmd(new NetDebugCmd());
+		DevCmdManager.registerDevCmd(new SqlDebugCmd());
+		DevCmdManager.registerDevCmd(new PullCmd());
+		DevCmdManager.registerDevCmd(new PurgeObjectsCmd());
+		DevCmdManager.registerDevCmd(new SplatMobCmd());
+		DevCmdManager.registerDevCmd(new SlotNpcCmd());
+		DevCmdManager.registerDevCmd(new SetAICmd());
+		DevCmdManager.registerDevCmd(new GateInfoCmd());
+		DevCmdManager.registerDevCmd(new ShowOffsetCmd());
+		DevCmdManager.registerDevCmd(new RealmInfoCmd());
+		DevCmdManager.registerDevCmd(new RebootCmd());
+		DevCmdManager.registerDevCmd(new AddMobPowerCmd());
+		DevCmdManager.registerDevCmd(new AddMobRuneCmd());
+		DevCmdManager.registerDevCmd(new SetMineTypeCmd());
+		DevCmdManager.registerDevCmd(new SetMineExpansion());
+		DevCmdManager.registerDevCmd(new SetForceRenameCityCmd());
+		DevCmdManager.registerDevCmd(new GotoObj());
+		DevCmdManager.registerDevCmd(new convertLoc());
+		DevCmdManager.registerDevCmd(new GetMobBaseLoot());
+		DevCmdManager.registerDevCmd(new MBDropCmd());
+		DevCmdManager.registerDevCmd(new GetDisciplineLocCmd());
+		DevCmdManager.registerDevCmd(new AuditHeightMapCmd());
+		DevCmdManager.registerDevCmd(new UnloadFurnitureCmd());
+		DevCmdManager.registerDevCmd(new SetNPCSlotCmd());
+		DevCmdManager.registerDevCmd(new SetNpcEquipSetCmd());
+		DevCmdManager.registerDevCmd(new SetBuildingAltitudeCmd());
+		DevCmdManager.registerDevCmd(new ResetLevelCmd());
+		DevCmdManager.registerDevCmd(new HeartbeatCmd());
+		DevCmdManager.registerDevCmd(new SetNpcNameCmd());
+		DevCmdManager.registerDevCmd(new SetNpcMobbaseCmd());
+		DevCmdManager.registerDevCmd(new DespawnCmd());
+		DevCmdManager.registerDevCmd(new BoundsCmd());
+		DevCmdManager.registerDevCmd(new GotoBoundsCmd());
+		DevCmdManager.registerDevCmd(new RegionCmd());
+		DevCmdManager.registerDevCmd(new SetMaintCmd());
+		DevCmdManager.registerDevCmd(new ApplyBonusCmd());
+		DevCmdManager.registerDevCmd(new setOpenDateCmd());
+		DevCmdManager.registerDevCmd(new AuditFailedItemsCmd());
+
+	}
+
+	private static void registerDevCmd(AbstractDevCmd cmd) {
+		ArrayList<String> cmdStrings = cmd.getCmdStrings();
+		for (String cmdString : cmdStrings) {
+			DevCmdManager.devCmds.put(cmdString, cmd);
+		}
+	}
+
+	public static AbstractDevCmd getDevCmd(String cmd) {
+			String lowercase = cmd.toLowerCase();
+			return DevCmdManager.devCmds.get(lowercase);
+	}
+
+	public static boolean handleDevCmd(PlayerCharacter pcSender, String cmd,
+			String argString, AbstractGameObject target) {
+
+		if (pcSender == null) {
+			return false;
+		}
+
+		Account a = SessionManager.getAccount(pcSender);
+
+		if (a == null) {
+			return false;
+		}
+
+		AbstractDevCmd adc = DevCmdManager.getDevCmd(cmd);
+
+		if (adc == null) {
+			return false;
+		}
+
+		//kill any commands not available to everyone on production server
+		//only admin level can run dev commands on production
+
+		if (a.status.equals(Enum.AccountStatus.ADMIN) == false) {
+			Logger.info("Account " + a.getUname() + "attempted to use dev command " + cmd);
+			return false;
+		}
+
+		// TODO add a job here to separate calling thread form executing thread?
+		// Log
+
+		String accName = a.getUname();
+		String pcName = pcSender.getCombinedName();
+		String logString = pcName + '(' + accName
+				+ ") '";
+		logString += cmd + ' ' + argString + '\'';
+		Logger.info( logString);
+
+		// execute command;
+		try {
+			adc.doCmd(pcSender, argString, target);
+		} catch (Exception e) {
+			Logger.error(e.toString());
+			e.printStackTrace();
+		}
+
+		return true;
+	}
+
+	public static String getCmdsForAccessLevel() {
+		String out = "";
+
+		for (Entry<String, AbstractDevCmd> e : DevCmdManager.devCmds.entrySet())
+			out += e.getKey() + ", ";
+
+		return out;
+	}
+
+}
diff --git a/src/engine/gameManager/GroupManager.java b/src/engine/gameManager/GroupManager.java
new file mode 100644
index 00000000..a451d4c9
--- /dev/null
+++ b/src/engine/gameManager/GroupManager.java
@@ -0,0 +1,393 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+package engine.gameManager;
+
+import engine.Enum;
+import engine.exception.MsgSendException;
+import engine.net.Dispatch;
+import engine.net.DispatchMessage;
+import engine.net.client.ClientConnection;
+import engine.net.client.msg.UpdateGoldMsg;
+import engine.net.client.msg.group.GroupUpdateMsg;
+import engine.objects.*;
+import engine.server.MBServerStatics;
+import org.pmw.tinylog.Logger;
+
+import java.util.ArrayList;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+public enum GroupManager {
+
+    GROUPMANAGER;
+
+    // used for quick lookup of groups by the ID of the group sent in the msg
+    private static final ConcurrentHashMap<Integer, Group> groupsByID = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_HIGH);
+
+    // an index for playercharacters to group membership
+    private static final ConcurrentHashMap<AbstractCharacter, Group> groupsByAC = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_HIGH);
+    private static int groupCount = 0;
+
+    /*
+     * Class Implementation
+     */
+    public static void removeFromGroups(AbstractCharacter ac) {
+        Group gr = null;
+
+        synchronized (GroupManager.groupsByAC) {
+            gr = GroupManager.groupsByAC.remove(ac);
+        }
+
+    }
+
+    public static void LeaveGroup(ClientConnection origin) throws MsgSendException {
+        PlayerCharacter source = SessionManager.getPlayerCharacter(origin);
+        LeaveGroup(source);
+    }
+
+    public static void LeaveGroup(PlayerCharacter source) throws MsgSendException {
+
+        if (source == null)
+            return;
+
+        Group group = GroupManager.groupsByAC.get(source);
+
+        if (group == null) // source is not in a group
+            return;
+
+        // Cleanup group window for player quiting
+        GroupUpdateMsg groupUpdateMsg = new GroupUpdateMsg();
+        groupUpdateMsg.setGroup(group);
+        groupUpdateMsg.setPlayer(source);
+        groupUpdateMsg.setMessageType(3);
+
+        Set<PlayerCharacter> groupMembers = group.getMembers();
+
+        for (PlayerCharacter groupMember : groupMembers) {
+
+            if (groupMember == null)
+                continue;
+
+            groupUpdateMsg = new GroupUpdateMsg();
+            groupUpdateMsg.setGroup(group);
+            groupUpdateMsg.setPlayer(source);
+            groupUpdateMsg.setMessageType(3);
+            groupUpdateMsg.setPlayer(groupMember);
+            Dispatch dispatch = Dispatch.borrow(source, groupUpdateMsg);
+            DispatchMessage.dispatchMsgDispatch(dispatch, engine.Enum.DispatchChannel.SECONDARY);
+
+        }
+
+        // Remove from group
+        int size = group.removeGroupMember(source);
+        // remove from the group -> ac mapping list
+        GroupManager.groupsByAC.remove(source);
+
+        if (size == 0) {
+            GroupManager.deleteGroup(group);
+            return; // group empty so cleanup group and we're done
+        }
+
+        // set new group lead if needed
+        if (group.getGroupLead() == source) {
+            PlayerCharacter newLead = group.getMembers().iterator().next();
+            group.setGroupLead(newLead.getObjectUUID());
+            groupUpdateMsg = new GroupUpdateMsg();
+            groupUpdateMsg.setGroup(group);
+            groupUpdateMsg.setPlayer(newLead);
+            groupUpdateMsg.addPlayer(source);
+            groupUpdateMsg.setMessageType(2);
+            group.sendUpdate(groupUpdateMsg);
+
+            // Disable Formation
+            newLead.setFollow(false);
+            groupUpdateMsg = new GroupUpdateMsg();
+            groupUpdateMsg.setGroup(group);
+            groupUpdateMsg.setPlayer(newLead);
+            groupUpdateMsg.setMessageType(8);
+            group.sendUpdate(groupUpdateMsg);
+        }
+
+        //send message to group
+        PlayerCharacter pc = group.getGroupLead();
+        //Fixed
+        String text = source.getFirstName() + " has left the group.";
+        ChatManager.chatGroupInfo(pc, text);
+
+        // cleanup other group members screens
+        groupUpdateMsg = new GroupUpdateMsg();
+        groupUpdateMsg.setGroup(group);
+        groupUpdateMsg.setPlayer(source);
+        groupUpdateMsg.setMessageType(3);
+        group.sendUpdate(groupUpdateMsg);
+
+    }
+
+    //This updates health/stamina/mana and loc of all players in group
+
+    public static void RefreshWholeGroupList(PlayerCharacter source, ClientConnection origin, Group gexp) {
+
+        if (source == null || origin == null)
+            return;
+
+        Group group = GroupManager.groupsByAC.get(source);
+
+        if (group == null)
+            return;
+
+        if (gexp.getObjectUUID() != group.getObjectUUID())
+            return;
+
+        Set<PlayerCharacter> groupMembers = group.getMembers();
+
+        if (groupMembers.size() < 2)
+            return;
+
+        // Send all group members health/mana/stamina/loc.
+
+        for (PlayerCharacter groupMember : groupMembers) {
+
+            if (groupMember == null)
+                continue;
+
+            GroupUpdateMsg gum = new GroupUpdateMsg(5, 1, groupMembers, group);
+            gum.setPlayerUUID(groupMember.getObjectUUID());
+
+            Dispatch dispatch = Dispatch.borrow(groupMember, gum);
+            DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+
+        }
+    }
+
+    public static void RefreshMyGroupList(PlayerCharacter source, ClientConnection origin) {
+
+        if (source == null || origin == null)
+            return;
+
+        Group group = GroupManager.groupsByAC.get(source);
+
+        if (group == null)
+            return;
+
+        Set<PlayerCharacter> members = group.getMembers();
+
+
+        // Send all group members to player added
+        for (PlayerCharacter groupMember : members) {
+
+            if (groupMember == null)
+                continue;
+
+            GroupUpdateMsg gum = new GroupUpdateMsg();
+            gum.setGroup(group);
+            gum.setMessageType(1);
+            gum.setPlayer(groupMember);
+            Dispatch dispatch = Dispatch.borrow(groupMember, gum);
+            DispatchMessage.dispatchMsgDispatch(dispatch, engine.Enum.DispatchChannel.SECONDARY);
+
+        }
+    }
+
+    public static void RefreshMyGroupListSinglePlayer(PlayerCharacter source, ClientConnection origin, PlayerCharacter playerToRefresh) {
+
+        // send msg type 1 to the source player on this connection to update the group
+        // list stats for the player that has just been loaded
+
+        if (source == null || origin == null || playerToRefresh == null)
+            return;
+
+        Group group = GroupManager.groupsByAC.get(source);
+
+        if (group == null)
+            return;
+
+        // only send if the 2 players are in the same group
+        if (group != GroupManager.groupsByAC.get(playerToRefresh))
+            return;
+
+        GroupUpdateMsg gum = new GroupUpdateMsg();
+        gum.setGroup(group);
+        gum.setMessageType(1);
+        gum.setPlayer(playerToRefresh);
+
+        Dispatch dispatch = Dispatch.borrow(source, gum);
+        DispatchMessage.dispatchMsgDispatch(dispatch, engine.Enum.DispatchChannel.SECONDARY);
+    }
+
+    public static void RefreshOthersGroupList(PlayerCharacter source) {
+
+        // refresh my stats on everyone elses group list
+
+        if (source == null)
+            return;
+
+        Group group = GroupManager.groupsByAC.get(source);
+
+        if (group == null)
+            return;
+
+        //construct message
+        GroupUpdateMsg gim = new GroupUpdateMsg();
+        gim.setGroup(group);
+        gim.setMessageType(1);
+        gim.setPlayer(source);
+        group.sendUpdate(gim);
+
+    }
+
+    public static int incrGroupCount() {
+        GroupManager.groupCount++;
+        return GroupManager.groupCount;
+    }
+
+    public static boolean deleteGroup(Group g) {
+
+        // remove all players from the mapping
+        Set<PlayerCharacter> members = g.getMembers();
+
+        for (PlayerCharacter pc : members) {
+            if (pc != null) {
+                GroupManager.removeFromGroups(pc);
+            }
+        }
+        // remove the group ID from the list
+        GroupManager.groupsByID.remove(g.getObjectUUID());
+        g.clearMembers();
+
+        g.removeUpdateGroupJob();
+
+        return true;
+    }
+
+    public static Group addNewGroup(Group group) {
+
+        PlayerCharacter pc = group.getGroupLead();
+
+        GroupManager.addGroup(group);
+
+        if (pc != null) {
+            GroupManager.addPlayerGroupMapping(pc, group);
+            return group;
+        }
+        return null;
+
+    }
+
+    private static Group addGroup(Group group) {
+
+        if (GroupManager.groupsByID.containsKey(group.getObjectUUID())) {
+            return null;
+        }
+
+        GroupManager.groupsByID.put(group.getObjectUUID(), group);
+        return group;
+    }
+
+    public static Group getGroup(int groupID) {
+        return GroupManager.groupsByID.get(groupID);
+    }
+
+    public static Group getGroup(PlayerCharacter pc) {
+
+        return GroupManager.groupsByAC.get(pc);
+    }
+
+    public static void addPlayerGroupMapping(PlayerCharacter pc, Group grp) {
+        GroupManager.groupsByAC.put(pc, grp);
+    }
+
+    public static boolean goldSplit(PlayerCharacter pc, Item item, ClientConnection origin, AbstractWorldObject tar) {
+        if (item == null || pc == null || tar == null || item.getItemBase() == null) {
+            Logger.error( "null something");
+            return false;
+        }
+
+        if (item.getItemBase().getUUID() != 7) //only split goldItem
+            return false;
+
+        Group group = getGroup(pc);
+
+        if (group == null || !group.getSplitGold()) //make sure player is grouped and split is on
+            return false;
+
+
+        ArrayList<PlayerCharacter> playersSplit = new ArrayList<>();
+
+        //get group members
+
+        for (PlayerCharacter groupMember: group.getMembers()){
+            if (pc.getLoc().distanceSquared2D(groupMember.getLoc()) > MBServerStatics.CHARACTER_LOAD_RANGE * MBServerStatics.CHARACTER_LOAD_RANGE)
+                continue;
+
+            if (!groupMember.isAlive())
+                continue;
+
+            playersSplit.add(groupMember);
+        }
+
+
+        //make sure more then one group member in loot range
+        int size = playersSplit.size();
+
+        if (size < 2)
+            return false;
+
+        int total = item.getNumOfItems();
+        int amount = total / size;
+        int dif = total - (size * amount);
+
+        if (AbstractWorldObject.IsAbstractCharacter(tar)) {
+        }
+        else if (tar.getObjectType().equals(Enum.GameObjectType.Corpse)) {
+            Corpse corpse = (Corpse) tar;
+            corpse.getInventory().remove(item);
+        }
+        else {
+            Logger.error("target not corpse or character");
+            return false;
+        }
+
+        if (item.getObjectType() == Enum.GameObjectType.MobLoot){
+            if (tar.getObjectType() == Enum.GameObjectType.Mob){
+                ((Mob)tar).getCharItemManager().delete(item);
+            }else
+                item.setNumOfItems(0);
+        }else
+            item.setNumOfItems(0);
+        for (PlayerCharacter splitPlayer : playersSplit) {
+
+
+
+            int amt = (group.isGroupLead(splitPlayer)) ? (amount + dif) : amount;
+            if (amt > 0)
+                splitPlayer.getCharItemManager().addGoldToInventory(amt, false);
+        }
+
+        for (PlayerCharacter splitPlayer : playersSplit) {
+
+
+            UpdateGoldMsg ugm = new UpdateGoldMsg(splitPlayer);
+            ugm.configure();
+
+            Dispatch dispatch = Dispatch.borrow(splitPlayer, ugm);
+            DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+        }
+
+        UpdateGoldMsg updateTargetGold = new UpdateGoldMsg(tar);
+        updateTargetGold.configure();
+        DispatchMessage.dispatchMsgToInterestArea(tar, updateTargetGold, Enum.DispatchChannel.SECONDARY, MBServerStatics.CHARACTER_LOAD_RANGE, true, false);
+
+
+        //		//TODO send group split message
+        String text = "Group Split: " + amount;
+        ChatManager.chatGroupInfo(pc, text);
+
+        return true;
+    }
+}
diff --git a/src/engine/gameManager/GuildManager.java b/src/engine/gameManager/GuildManager.java
new file mode 100644
index 00000000..c69cf920
--- /dev/null
+++ b/src/engine/gameManager/GuildManager.java
@@ -0,0 +1,206 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+package engine.gameManager;
+
+import engine.Enum;
+import engine.Enum.BuildingGroup;
+import engine.Enum.GuildHistoryType;
+import engine.net.Dispatch;
+import engine.net.DispatchMessage;
+import engine.net.client.ClientConnection;
+import engine.net.client.msg.guild.AcceptInviteToGuildMsg;
+import engine.net.client.msg.guild.GuildInfoMsg;
+import engine.objects.*;
+import org.joda.time.DateTime;
+
+public enum GuildManager  {
+
+	GUILDMANAGER;
+
+	//Guild Error Message
+	public static final int FAILURE_TO_SWEAR_GUILD = 45; //45: Failure to swear guild
+	public static final int MUST_LEAVE_GUILD = 75;//75: You must leave your current guild before you can repledge
+	public static final int NO_CHARTER_FOUND = 148; //148: Unable to find a matching petition to complete guild creation
+	public static final int PROFANE_NAME = 149; //149: Guild name fails profanity check
+	public static final int PROFANE_MOTTO = 150; //150: Guild motto fails profanity check
+	public static final int UNIQUE_NAME = 151;//151: Guild name is not unique
+	public static final int UNIQUE_CREST = 152;//152: Guild crest is not unique
+	public static final int CREST_RESERVED = 153;	  //153: Guild crest is reserved
+	public static final int CREST_COLOR_ERROR = 154; //154: All three crest colors cannot be the same
+
+	public static boolean joinGuild(PlayerCharacter pc, Guild guild, GuildHistoryType historyType) {
+		return joinGuild(pc, guild, 0, historyType);
+	}
+
+	//Used when repledging
+	public static boolean joinGuild(PlayerCharacter pc, Guild guild, int cityID, GuildHistoryType historyType) {
+		return joinGuild(pc, guild, cityID, true,historyType);
+	}
+
+	public static boolean joinGuild(PlayerCharacter playerCharacter, Guild guild, int cityID, boolean fromTeleportScreen, GuildHistoryType historyType) {
+
+		// Member variable delcaration
+
+		ClientConnection origin;
+		AcceptInviteToGuildMsg msg;
+		Dispatch dispatch;
+
+		if (playerCharacter == null || guild == null)
+			return false;
+
+		// Member variable assignment
+
+		origin = SessionManager.getClientConnection(playerCharacter);
+
+		if (origin == null)
+			return false;
+
+		if (playerCharacter.getGuild().isErrant() == false && GuildStatusController.isGuildLeader(playerCharacter.getGuildStatus()))
+			return false;
+
+		if (playerCharacter.getGuild() != null && playerCharacter.getGuild().isGuildLeader(playerCharacter.getObjectUUID()))
+			return false;
+
+		if (playerCharacter.getGuild() != null && !playerCharacter.getGuild().isErrant()){
+			if (DbManager.GuildQueries.ADD_TO_GUILDHISTORY(playerCharacter.getGuildUUID(), playerCharacter, DateTime.now(), GuildHistoryType.LEAVE)){
+				GuildHistory guildHistory = new GuildHistory(playerCharacter.getGuildUUID(),playerCharacter.getGuild().getName(),DateTime.now(), GuildHistoryType.LEAVE) ;
+				playerCharacter.getGuildHistory().add(guildHistory);
+			}
+		}
+
+		playerCharacter.setInnerCouncil(false);
+		playerCharacter.setGuildLeader(false);
+		playerCharacter.setGuild(guild);
+
+		// Cleanup guild stuff
+		playerCharacter.resetGuildStatuses();
+
+		// send success message to client
+		if (fromTeleportScreen && guild.isNPCGuild())
+			playerCharacter.setFullMember(true);
+
+		msg = new AcceptInviteToGuildMsg(guild.getObjectUUID(), 1, 0);
+
+		if (fromTeleportScreen) {
+			dispatch = Dispatch.borrow(playerCharacter, msg);
+			DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+		}
+		if (DbManager.GuildQueries.ADD_TO_GUILDHISTORY(guild.getObjectUUID(), playerCharacter, DateTime.now(), historyType)){
+			GuildHistory guildHistory = new GuildHistory(guild.getObjectUUID(),guild.getName(),DateTime.now(), historyType) ;
+			playerCharacter.getGuildHistory().add(guildHistory);
+		}
+
+		DispatchMessage.sendToAllInRange(playerCharacter, new GuildInfoMsg(playerCharacter, guild, 2));
+
+		// Send guild join message
+		ChatManager.chatGuildInfo(playerCharacter,
+				playerCharacter.getFirstName() + " has joined the guild");
+
+		playerCharacter.incVer();
+
+		return true;
+		// TODO update player to world
+	}
+
+	public static void enterWorldMOTD(PlayerCharacter pc) {
+
+		Guild guild;
+		Guild nation;
+
+		if (pc == null) {
+			return;
+		}
+
+		guild = pc.getGuild();
+
+		if (guild == null || guild.getObjectUUID() == 0) // Don't send to errant
+			return;
+
+		// Send Guild MOTD
+		String motd = guild.getMOTD();
+		if (motd.length() > 0) {
+			ChatManager.chatGuildMOTD(pc, motd);
+		}
+
+		// Send Nation MOTD
+		nation = guild.getNation();
+
+		if (nation != null) {
+			if (nation.getObjectUUID() != 0) { // Don't send to errant nation
+				motd = nation.getMOTD();
+				if (motd.length() > 0) {
+					ChatManager.chatNationMOTD(pc, motd);
+				}
+			}
+		}
+
+		// Send IC MOTD if player is IC
+		if (GuildStatusController.isInnerCouncil(pc.getGuildStatus())) {
+			motd = guild.getICMOTD();
+			if (motd.length() > 0) {
+				ChatManager.chatICMOTD(pc, motd);
+			}
+		}
+	}
+
+	//Updates the bind point for everyone in guild
+
+	public static void updateAllGuildBinds(Guild guild, City city) {
+
+		if (guild == null)
+			return;
+
+		int cityID = (city != null) ? city.getObjectUUID() : 0;
+		
+	
+
+		//update binds ingame
+		
+
+		for (PlayerCharacter playerCharacter : Guild.GuildRoster(guild)) {
+			boolean updateBindBuilding = false;
+			
+			Building oldBoundBuilding = BuildingManager.getBuildingFromCache(playerCharacter.getBindBuildingID());
+			
+			if (oldBoundBuilding == null || oldBoundBuilding.getBlueprint() == null || oldBoundBuilding.getBlueprint().getBuildingGroup().equals(BuildingGroup.TOL))
+				updateBindBuilding = true;
+			
+			
+			
+			if (updateBindBuilding){
+				Building bindBuilding = null;
+				if (city != null)
+					if (city.getTOL() != null)
+						bindBuilding = city.getTOL();
+				
+				if (bindBuilding == null)
+					bindBuilding = PlayerCharacter.getBindBuildingForGuild(playerCharacter);
+				
+				playerCharacter.setBindBuildingID(bindBuilding != null ? bindBuilding.getObjectUUID() : 0);
+			}
+				
+
+		}
+	}
+
+	//This updates tags for all online players in a guild.
+	public static void updateAllGuildTags(Guild guild) {
+
+		if (guild == null)
+			return;
+
+		for (PlayerCharacter player : SessionManager.getAllActivePlayerCharacters()) {
+
+			if (player.getGuild().equals(guild))
+				DispatchMessage.sendToAllInRange(player, new GuildInfoMsg(player , guild, 2));
+
+		}
+	}
+
+}
diff --git a/src/engine/gameManager/MaintenanceManager.java b/src/engine/gameManager/MaintenanceManager.java
new file mode 100644
index 00000000..35185ebd
--- /dev/null
+++ b/src/engine/gameManager/MaintenanceManager.java
@@ -0,0 +1,359 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+package engine.gameManager;
+
+// Defines static methods which comprise the magicbane
+// building maintenance system.
+
+import engine.Enum;
+import engine.objects.*;
+import org.pmw.tinylog.Logger;
+
+import java.time.LocalDateTime;
+import java.util.ArrayList;
+
+public enum MaintenanceManager {
+
+    MAINTENANCEMANAGER;
+
+    public static void setMaintDateTime(Building building, LocalDateTime maintDate) {
+
+        building.maintDateTime = maintDate;
+        DbManager.BuildingQueries.updateMaintDate(building);
+
+    }
+
+    public static void processBuildingMaintenance() {
+
+        ArrayList<AbstractGameObject> buildingList;
+        ArrayList<Building> maintList;
+        ArrayList<Building> derankList = new ArrayList<>();
+
+        Logger.info("Starting Maintenance on Player Buildings");
+
+        // Build list of buildings to apply maintenance on.
+
+        buildingList = new ArrayList(DbManager.getList(Enum.GameObjectType.Building));
+        maintList = buildMaintList(buildingList);
+
+        // Deduct upkeep and build list of buildings
+        // which did not have funds available
+
+        for (Building building : maintList) {
+
+            if (chargeUpkeep(building) == false)
+                derankList.add(building);
+        }
+
+        // Reset maintenance dates for these buildings
+
+        for (Building building : maintList)
+            setMaintDateTime(building, building.maintDateTime.plusDays(7));
+
+        // Derak or destroy buildings that did not
+        // have funds available.
+
+        for (Building building : derankList)
+            building.destroyOrDerank(null);
+
+        Logger.info("Structures: " + buildingList.size() + " Maint: " + maintList.size() + " Derank: " + derankList.size());
+    }
+
+    // Iterate over all buildings in game and apply exclusion rules
+    // returning a list of building for which maintenance is due.
+
+    private static ArrayList<Building> buildMaintList(ArrayList<AbstractGameObject> buildingList) {
+
+        ArrayList<Building> maintList = new ArrayList<>();
+
+        for (AbstractGameObject gameObject : buildingList) {
+
+            Building building = (Building) gameObject;
+
+            // No Maintenance on fidelity structures
+
+            if (building.getProtectionState() == Enum.ProtectionState.NPC)
+                continue;
+
+            // No maintenance on constructing meshes
+
+            if (building.getRank() < 1)
+                continue;
+
+            // No Maintenance on furniture
+
+            if (building.parentBuildingID != 0)
+                continue;
+
+            // No Blueprint?
+
+            if (building.getBlueprint() == null) {
+                Logger.error("Blueprint missing for uuid: " + building.getObjectUUID());
+                continue;
+            }
+
+            // No maintenance on banestones omfg
+
+            if (building.getBlueprint().getBuildingGroup().equals(Enum.BuildingGroup.BANESTONE))
+                continue;
+
+            // no maintenance on Mines omfg
+
+            if (building.getBlueprint().getBuildingGroup().equals(Enum.BuildingGroup.MINE))
+                continue;
+
+            // Null Maintenance date?
+
+            if (building.maintDateTime == null) {
+                Logger.error("Null maint date for building UUID: " + building.getObjectUUID());
+                continue;
+            }
+
+            // Maintenance date is in the future
+
+            if (building.maintDateTime.isAfter(LocalDateTime.now()))
+                continue;
+
+            //  Add building to maintenance queue
+
+            maintList.add(building);
+        }
+
+        return maintList;
+    }
+
+    // Method removes the appropriate amount of gold/resources from
+    // a building according to it's maintenance schedule.  True/False
+    // is returned indicating if the building had enough funds to cover.
+
+    public static boolean chargeUpkeep(Building building) {
+
+        City city = null;
+        Warehouse warehouse = null;
+        int maintCost = 0;
+        int overDraft = 0;
+        boolean hasFunds = false;
+        boolean hasResources = false;
+        int resourceValue = 0;
+
+        city = building.getCity();
+
+        if (city != null)
+            warehouse = city.getWarehouse();
+
+        // Cache maintenance cost value
+
+        maintCost = building.getMaintCost();
+
+        // Something went wrong.  Missing buildinggroup from switch?
+
+        if (maintCost == 0) {
+            Logger.error("chargeUpkeep", "Error retrieving rankcost for " + building.getName() + " uuid:" + building.getObjectUUID() + "buildinggroup:" + building.getBlueprint().getBuildingGroup().name());
+            // check if there is enough gold on the building
+            return true;
+        }
+
+        if (building.getStrongboxValue() >= maintCost)
+            hasFunds = true;
+
+        // If we cannot cover with just the strongbox
+        // see if there is a warehouse that will cover
+        // the overdraft for us.
+
+
+        if (hasFunds == false && (building.assetIsProtected() || building.getBlueprint().getBuildingGroup() == Enum.BuildingGroup.WAREHOUSE)) {
+            overDraft = maintCost - building.getStrongboxValue();
+        }
+
+        if ((overDraft > 0))
+            if ((building.getBlueprint().getBuildingGroup().equals(Enum.BuildingGroup.SHRINE) == false) &&
+                    (warehouse != null) && building.assetIsProtected() == true &&
+                    (warehouse.getResources().get(ItemBase.GOLD_ITEM_BASE)) >= overDraft) {
+                hasFunds = true;
+            }
+
+        // If this is an R8 tree, validate that we can
+        // cover the resources required
+
+        if (building.getRank() == 8) {
+
+            hasResources = true;
+
+            if (warehouse == null)
+                hasResources = false;
+            else {
+
+                resourceValue = warehouse.getResources().get(Warehouse.stoneIB);
+
+                if (resourceValue < 1500)
+                    hasResources = false;
+
+                resourceValue = warehouse.getResources().get(Warehouse.lumberIB);
+
+                if (resourceValue < 1500)
+                    hasResources = false;
+
+                resourceValue = warehouse.getResources().get(Warehouse.galvorIB);
+
+                if (resourceValue < 5)
+                    hasResources = false;
+
+                resourceValue = warehouse.getResources().get(Warehouse.wormwoodIB);
+
+                if (resourceValue < 5)
+                    hasResources = false;
+
+            }
+        }
+        // Validation completed but has failed.  We can derank
+        // the target building and early exit
+
+        if ((hasFunds == false) ||
+                ((building.getRank() == 8) && !hasResources)) {
+
+            // Add cash back to strongbox for lost rank if the building isn't being destroyed
+            // and it's not an R8 deranking
+
+            if ((building.getRank() > 1) && (building.getRank() < 8)) {
+                building.setStrongboxValue(building.getStrongboxValue() + building.getBlueprint().getRankCost(Math.min(building.getRank(), 7)));
+            }
+
+            return false; // Early exit for having failed to meet maintenance
+        }
+
+        // Remove cash and resources
+
+        // withdraw what we can from the building
+
+        building.setStrongboxValue(building.getStrongboxValue() - (maintCost - overDraft));
+
+        // withdraw overdraft from the whorehouse
+
+        if (overDraft > 0) {
+
+            resourceValue = warehouse.getResources().get(Warehouse.goldIB);
+
+            if (DbManager.WarehouseQueries.updateGold(warehouse, resourceValue - overDraft) == true) {
+                warehouse.getResources().put(Warehouse.goldIB, resourceValue - overDraft);
+                warehouse.AddTransactionToWarehouse(Enum.GameObjectType.Building, building.getObjectUUID(), Enum.TransactionType.WITHDRAWL, Resource.GOLD, overDraft);
+            } else {
+                Logger.error("gold update failed for warehouse of UUID:" + warehouse.getObjectUUID());
+                return true;
+            }
+        }
+
+        // Early exit as we're done if we're not an R8 tree
+
+        if (building.getRank() < 8)
+            return true;
+
+        // Now for the resources if it's an R8 tree
+
+        // Withdraw Stone
+
+        resourceValue = warehouse.getResources().get(Warehouse.stoneIB);
+
+        if (DbManager.WarehouseQueries.updateStone(warehouse, resourceValue - 1500) == true) {
+            warehouse.getResources().put(Warehouse.stoneIB, resourceValue - 1500);
+            warehouse.AddTransactionToWarehouse(Enum.GameObjectType.Building, building.getObjectUUID(), Enum.TransactionType.WITHDRAWL, Resource.STONE, 1500);
+        } else {
+            Logger.error("stone update failed for warehouse of UUID:" + warehouse.getObjectUUID());
+            return true;
+        }
+
+        // Withdraw Lumber
+
+        resourceValue = warehouse.getResources().get(Warehouse.lumberIB);
+
+        if (DbManager.WarehouseQueries.updateLumber(warehouse, resourceValue - 1500) == true) {
+            warehouse.getResources().put(Warehouse.lumberIB, resourceValue - 1500);
+            warehouse.AddTransactionToWarehouse(Enum.GameObjectType.Building, building.getObjectUUID(), Enum.TransactionType.WITHDRAWL, Resource.LUMBER, 1500);
+        } else {
+            Logger.error("lumber update failed for warehouse of UUID:" + warehouse.getObjectUUID());
+            return true;
+        }
+
+        // Withdraw Galvor
+
+        resourceValue = warehouse.getResources().get(Warehouse.galvorIB);
+
+        if (DbManager.WarehouseQueries.updateGalvor(warehouse, resourceValue - 5) == true) {
+            warehouse.getResources().put(Warehouse.galvorIB, resourceValue - 5);
+            warehouse.AddTransactionToWarehouse(Enum.GameObjectType.Building, building.getObjectUUID(), Enum.TransactionType.WITHDRAWL, Resource.GALVOR, 5);
+        } else {
+            Logger.error("galvor update failed for warehouse of UUID:" + warehouse.getObjectUUID());
+            return true;
+        }
+
+        resourceValue = warehouse.getResources().get(Warehouse.wormwoodIB);
+
+        if (DbManager.WarehouseQueries.updateWormwood(warehouse, resourceValue - 5) == true) {
+            warehouse.getResources().put(Warehouse.wormwoodIB, resourceValue - 5);
+            warehouse.AddTransactionToWarehouse(Enum.GameObjectType.Building, building.getObjectUUID(), Enum.TransactionType.WITHDRAWL, Resource.WORMWOOD, 5);
+        } else {
+            Logger.error("wyrmwood update failed for warehouse of UUID:" + warehouse.getObjectUUID());
+        }
+
+        return true;
+    }
+
+    public static void dailyMaintenance() {
+
+        ArrayList<Shrine> shrineList = new ArrayList<>();
+
+        Logger.info("Maintenance has started");
+
+        // Update shrines to proper city owner
+
+        for (Shrine shrine : Shrine.shrinesByBuildingUUID.values()) {
+            try {
+                Building shrineBuilding = (Building) DbManager.getObject(Enum.GameObjectType.Building, shrine.getBuildingID());
+
+                if (shrineBuilding == null)
+                    continue;
+                
+
+                if (shrineBuilding.getOwner().equals(shrineBuilding.getCity().getOwner()) == false)
+                    shrineBuilding.claim(shrineBuilding.getCity().getOwner());
+            } catch (Exception e) {
+                Logger.info("Shrine " + shrine.getBuildingID() + " Error " + e);
+            }
+        }
+
+        // Grab list of top two shrines of each type
+
+        for (Shrine shrine : Shrine.shrinesByBuildingUUID.values()) {
+
+            if (shrine.getRank() == 0 || shrine.getRank() == 1)
+                shrineList.add(shrine);
+        }
+
+        Logger.info("Decaying " + shrineList.size() + " shrines...");
+
+        // Top 2 shrines decay by 10% a day
+
+        for (Shrine shrine : shrineList) {
+
+            try {
+                shrine.decay();
+            } catch (Exception e) {
+                Logger.info("Shrine " + shrine.getBuildingID() + " Error " + e);
+            }
+        }
+
+        // Run maintenance on player buildings
+
+        if ((boolean)  ConfigManager.MB_WORLD_MAINTENANCE.getValue().equals("true"))
+            processBuildingMaintenance();
+        else
+            Logger.info("Maintenance Costings: DISABLED");
+
+        Logger.info("process has completed!");
+    }
+}
diff --git a/src/engine/gameManager/MovementManager.java b/src/engine/gameManager/MovementManager.java
new file mode 100644
index 00000000..b66ba50a
--- /dev/null
+++ b/src/engine/gameManager/MovementManager.java
@@ -0,0 +1,650 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+package engine.gameManager;
+
+import engine.Enum.DispatchChannel;
+import engine.Enum.GameObjectType;
+import engine.Enum.ModType;
+import engine.Enum.SourceType;
+import engine.InterestManagement.InterestManager;
+import engine.InterestManagement.WorldGrid;
+import engine.exception.MsgSendException;
+import engine.job.JobContainer;
+import engine.job.JobScheduler;
+import engine.jobs.ChangeAltitudeJob;
+import engine.jobs.FlightJob;
+import engine.math.Bounds;
+import engine.math.Vector3f;
+import engine.math.Vector3fImmutable;
+import engine.net.DispatchMessage;
+import engine.net.client.ClientConnection;
+import engine.net.client.msg.ChangeAltitudeMsg;
+import engine.net.client.msg.MoveToPointMsg;
+import engine.net.client.msg.TeleportToPointMsg;
+import engine.net.client.msg.UpdateStateMsg;
+import engine.objects.*;
+import engine.server.MBServerStatics;
+import org.pmw.tinylog.Logger;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import static engine.math.FastMath.sqr;
+
+public enum MovementManager {
+
+	MOVEMENTMANAGER;
+
+	private static final String changeAltitudeTimerJobName = "ChangeHeight";
+	private static final String flightTimerJobName = "Flight";
+
+	public static void sendOOS(PlayerCharacter pc) {
+		pc.setWalkMode(true);
+		MovementManager.sendRWSSMsg(pc);
+	}
+
+	public static void sendRWSSMsg(AbstractCharacter ac) {
+
+		if (!ac.isAlive())
+			return;
+		UpdateStateMsg rssm = new UpdateStateMsg();
+		rssm.setPlayer(ac);
+		if (ac.getObjectType() == GameObjectType.PlayerCharacter)
+			DispatchMessage.dispatchMsgToInterestArea(ac, rssm, DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, true, false);
+		else
+			DispatchMessage.sendToAllInRange(ac, rssm);
+	}
+
+	/*
+	 * Sets the first combat target for the AbstractCharacter. Used to clear the
+	 * combat
+	 * target upon each move, unless something has set the firstHitCombatTarget
+	 * Also used to determine the size of a monster's hitbox
+	 */
+	public static void movement(MoveToPointMsg msg, AbstractCharacter toMove) throws MsgSendException {
+
+		// check for stun/root
+		if (!toMove.isAlive())
+			return;
+
+		if (toMove.getObjectType().equals(GameObjectType.PlayerCharacter)){
+			if (((PlayerCharacter)toMove).isCasting())
+				((PlayerCharacter)toMove).update();
+		}
+			
+		
+		
+		toMove.setIsCasting(false);
+		toMove.setItemCasting(false);
+
+		if (toMove.getBonuses().getBool(ModType.Stunned, SourceType.None) || toMove.getBonuses().getBool(ModType.CannotMove, SourceType.None)) {
+			return;
+		}
+		
+		if (msg.getEndLat() > MBServerStatics.MAX_WORLD_WIDTH)
+			msg.setEndLat((float) MBServerStatics.MAX_WORLD_WIDTH);
+		
+		if (msg.getEndLon() < MBServerStatics.MAX_WORLD_HEIGHT){
+			msg.setEndLon((float) MBServerStatics.MAX_WORLD_HEIGHT);
+		}
+		
+//		if (msg.getEndLat() < 0)
+//			msg.setEndLat(0);
+//		
+//		if (msg.getEndLon() > 0)
+//			msg.setEndLon(0);
+		
+		
+
+		
+		
+		if (!toMove.isMoving())
+			toMove.resetLastSetLocUpdate();
+		 else
+			toMove.update();
+
+		// Update movement for the player
+			
+			
+		//    	else if (toMove.getObjectType() == GameObjectType.Mob)
+		//    		((Mob)toMove).updateLocation();
+		// get start and end locations for the move
+		Vector3fImmutable startLocation = new Vector3fImmutable(msg.getStartLat(), msg.getStartAlt(), msg.getStartLon());
+		Vector3fImmutable endLocation = new Vector3fImmutable(msg.getEndLat(), msg.getEndAlt(), msg.getEndLon());
+
+		//		if (toMove.getObjectType() == GameObjectType.PlayerCharacter)
+		//			if (msg.getEndAlt() == 0 && msg.getTargetID() == 0){
+		//				MovementManager.sendRWSSMsg(toMove);
+		//			}
+
+		//If in Building, let's see if we need to Fix
+
+		// if inside a building, convert both locations from the building local reference frame to the world reference frame
+
+		if (msg.getTargetID() > 0) {
+			Building building = BuildingManager.getBuildingFromCache(msg.getTargetID());
+			if (building != null) {
+				
+				Vector3fImmutable convertLocEnd = new Vector3fImmutable(ZoneManager.convertLocalToWorld(building, endLocation));
+				//                if (!Bounds.collide(convertLocEnd, b) || !b.loadObjectsInside()) {
+				//                    toMove.setInBuilding(-1);
+				//                    toMove.setInFloorID(-1);
+				//                    toMove.setInBuildingID(0);
+				//                }
+				//                else {
+				toMove.setInBuilding(msg.getInBuilding());
+				toMove.setInFloorID(msg.getUnknown01());
+				toMove.setInBuildingID(msg.getTargetID());
+				msg.setStartCoord(ZoneManager.convertWorldToLocal(building, toMove.getLoc()));
+		
+				if (toMove.getObjectType() == GameObjectType.PlayerCharacter) {
+					if (convertLocEnd.distanceSquared2D(toMove.getLoc()) > 6000 * 6000) {
+
+						Logger.info( "ENDLOC:" + convertLocEnd.x + ',' + convertLocEnd.y + ',' + convertLocEnd.z +
+								',' + "GETLOC:" + toMove.getLoc().x + ',' + toMove.getLoc().y + ',' + toMove.getLoc().z + " Name " + ((PlayerCharacter) toMove).getCombinedName());
+						toMove.teleport(toMove.getLoc());
+
+						return;
+					}
+				}
+
+				startLocation = toMove.getLoc();
+				endLocation = convertLocEnd;
+
+			} else {
+
+				toMove.setInBuilding(-1);
+				toMove.setInFloorID(-1);
+				toMove.setInBuildingID(0);
+				//SYNC PLAYER
+				toMove.teleport(toMove.getLoc());
+				return;
+			}
+
+		} else {
+			toMove.setInBuildingID(0);
+			toMove.setInFloorID(-1);
+			toMove.setInBuilding(-1);
+			msg.setStartCoord(toMove.getLoc());
+		}
+		
+		//make sure we set the correct player.
+		msg.setSourceType(toMove.getObjectType().ordinal());
+		msg.setSourceID(toMove.getObjectUUID());
+		
+		//if player in region, modify location to local location of building. set target to building.
+		if (toMove.getRegion() != null){
+			Building regionBuilding = Regions.GetBuildingForRegion(toMove.getRegion());
+			if (regionBuilding != null){
+				msg.setStartCoord(ZoneManager.convertWorldToLocal(Regions.GetBuildingForRegion(toMove.getRegion()), toMove.getLoc()));
+				msg.setEndCoord(ZoneManager.convertWorldToLocal(regionBuilding, endLocation));
+				msg.setInBuilding(toMove.getRegion().level);
+				msg.setUnknown01(toMove.getRegion().room);
+				msg.setTargetType(GameObjectType.Building.ordinal());
+				msg.setTargetID(regionBuilding.getObjectUUID());
+			}
+			
+		}else{
+			toMove.setInBuildingID(0);
+			toMove.setInFloorID(-1);
+			toMove.setInBuilding(-1);
+			msg.setStartCoord(toMove.getLoc());
+			msg.setEndCoord(endLocation);
+			msg.setTargetType(0);
+			msg.setTargetID(0);
+		}
+
+		//checks sync between character and server, if out of sync, teleport player to original position and return.
+		if (toMove.getObjectType() == GameObjectType.PlayerCharacter) {
+			boolean startLocInSync = checkSync(toMove, startLocation, toMove.getLoc());
+			
+			if (!startLocInSync){
+				syncLoc(toMove, toMove.getLoc(), startLocInSync);
+				return;
+			}
+
+		}
+	
+		// set direction, based on the current location which has just been sync'd
+		// with the client and the calc'd destination
+		toMove.setFaceDir(endLocation.subtract2D(toMove.getLoc()).normalize());
+		
+		boolean collide = false;
+		if (toMove.getObjectType().equals(GameObjectType.PlayerCharacter)) {
+			Vector3fImmutable collidePoint = Bounds.PlayerBuildingCollisionPoint((PlayerCharacter)toMove, toMove.getLoc(), endLocation);
+			
+			if (collidePoint != null) {
+				msg.setEndCoord(collidePoint);
+				endLocation = collidePoint;
+				collide = true;
+			}
+						
+		}
+					
+		if (toMove.getObjectType() == GameObjectType.PlayerCharacter && ((PlayerCharacter) toMove).isTeleportMode()) {
+			toMove.teleport(endLocation);
+			return;
+		}
+
+		// move to end location, this can interrupt the current move
+		toMove.setEndLoc(endLocation);
+
+		//	ChatManager.chatSystemInfo((PlayerCharacter)toMove, "Moving to " + Vector3fImmutable.toString(endLocation));
+
+		// make sure server knows player is not sitting
+		toMove.setSit(false);
+
+		// cancel any effects that break upon movement
+		toMove.cancelOnMove();
+
+		//cancel any attacks for manual move.
+		if ((toMove.getObjectType() == GameObjectType.PlayerCharacter) && msg.getUnknown02() == 0)
+			toMove.setCombatTarget(null);
+
+
+		// If it's not a player moving just send the message
+
+		if ((toMove.getObjectType() == GameObjectType.PlayerCharacter) == false) {
+			DispatchMessage.sendToAllInRange(toMove, msg);
+			return;
+		}
+
+		// If it's a player who is moving then we need to handle characters
+		// who should see the message via group follow
+
+		PlayerCharacter player = (PlayerCharacter) toMove;
+
+		player.setTimeStamp("lastMoveGate", System.currentTimeMillis());
+
+		if (collide)
+			DispatchMessage.dispatchMsgToInterestArea(player, msg, DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, true, false);
+		else
+			DispatchMessage.dispatchMsgToInterestArea(player, msg, DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, false, false);
+
+
+		// Handle formation movement if needed
+
+		if (player.getFollow() == false)
+			return;
+
+
+		City cityObject = null;
+		Zone serverZone = null;
+
+		serverZone = ZoneManager.findSmallestZone(player.getLoc());
+		cityObject = (City) DbManager.getFromCache(GameObjectType.City, serverZone.getPlayerCityUUID());
+
+		// Do not send group messages if player is on grid
+
+		if (cityObject != null)
+			return;
+
+		// If player is not in a group we can exit here
+
+		Group group = GroupManager.getGroup(player);
+
+		if (group == null)
+			return;
+
+		// Echo group movement messages
+
+		if (group.getGroupLead().getObjectUUID() == player.getObjectUUID())
+			moveGroup(player, player.getClientConnection(), msg);
+				
+	}
+
+	/**
+	 * compare client and server location to verify that the two are in sync
+	 *
+	 * @param ac        the player character
+	 * @param clientLoc location as reported by the client
+	 * @param serverLoc location known to the server
+	 * @return true if the two are in sync
+	 */
+	private static boolean checkSync(AbstractCharacter ac, Vector3fImmutable clientLoc, Vector3fImmutable serverLoc) {
+
+		float desyncDist = clientLoc.distanceSquared2D(serverLoc);
+
+		// desync logging
+		if (MBServerStatics.MOVEMENT_SYNC_DEBUG)
+			if (desyncDist > MBServerStatics.MOVEMENT_DESYNC_TOLERANCE * MBServerStatics.MOVEMENT_DESYNC_TOLERANCE)
+				// our current location server side is a calc of last known loc + direction + speed and known time of last update
+				Logger.debug("Movement out of sync for " + ac.getFirstName()
+				+ ", Server Loc: " + serverLoc.getX() + ' ' + serverLoc.getZ()
+				+ " , Client loc: " + clientLoc.getX() + ' ' + clientLoc.getZ()
+				+ " desync distance " + desyncDist
+				+ " moving=" + ac.isMoving());
+			else
+				Logger.debug( "Movement sync is good - desyncDist = " + desyncDist);
+
+		if (ac.getDebug(1) && ac.getObjectType().equals(GameObjectType.PlayerCharacter))
+			if (desyncDist > MBServerStatics.MOVEMENT_DESYNC_TOLERANCE * MBServerStatics.MOVEMENT_DESYNC_TOLERANCE) {
+				PlayerCharacter pc = (PlayerCharacter) ac;
+				ChatManager.chatSystemInfo(pc,
+						"Movement out of sync for " + ac.getFirstName()
+						+ ", Server Loc: " + serverLoc.getX() + ' ' + serverLoc.getZ()
+						+ " , Client loc: " + clientLoc.getX() + ' ' + clientLoc.getZ()
+						+ " desync distance " + desyncDist
+						+ " moving=" + ac.isMoving());
+			}
+
+		// return indicator that the two are in sync or not
+		return (desyncDist < 100f * 100f);
+
+	}
+
+	//Update for when the character is in flight
+	public static void updateFlight(PlayerCharacter pc, ChangeAltitudeMsg msg, int duration) {
+		if (pc == null)
+			return;
+
+		// clear flight timer job as we are about to update stuff and submit a new job
+		pc.clearTimer(flightTimerJobName);
+
+		if (!pc.isActive()) {
+			pc.setAltitude(0);
+			pc.setDesiredAltitude(0);
+			pc.setTakeOffTime(0);
+			return;
+		}
+
+		// Check to see if we are mid height change
+		JobContainer cjc = pc.getTimers().get(changeAltitudeTimerJobName);
+		if (cjc != null) {
+			addFlightTimer(pc, msg, MBServerStatics.FLY_FREQUENCY_MS);
+			return;
+		}
+
+		// Altitude is zero, do nothing
+		if (pc.getAltitude() < 1)
+			return;
+
+		//make sure player is still allowed to fly
+		boolean canFly = false;
+		PlayerBonuses bonus = pc.getBonuses();
+
+		if (bonus != null && !bonus.getBool(ModType.NoMod, SourceType.Fly) && bonus.getBool(ModType.Fly, SourceType.None) && pc.isAlive())
+			canFly = true;
+
+		// if stam less that 2 - time to force a landing
+		if (pc.getStamina() < 10f || !canFly) {
+
+			// dont call stop movement here as we want to
+			// preserve endloc
+			//pc.stopMovement();
+			// sync world location
+			pc.setLoc(pc.getLoc());
+			// force a landing
+			msg.setStartAlt(pc.getAltitude());
+			msg.setTargetAlt(0);
+			msg.setAmountToMove(pc.getAltitude());
+			msg.setUp(false);
+			DispatchMessage.dispatchMsgToInterestArea(pc, msg, DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, true, false);
+			MovementManager.addChangeAltitudeTimer(pc, msg.getStartAlt(), msg.getTargetAlt(), (int) (MBServerStatics.HEIGHT_CHANGE_TIMER_MS * pc.getAltitude()));
+			pc.setAltitude(msg.getStartAlt() - 10);
+
+		} else //Add a new flight timer to check stam / force land
+			if (pc.getAltitude() > 0)
+				addFlightTimer(pc, msg, MBServerStatics.FLY_FREQUENCY_MS);
+
+	}
+
+	public static void finishChangeAltitude(AbstractCharacter ac, float targetAlt) {
+
+		if (ac.getObjectType().equals(GameObjectType.PlayerCharacter) == false)
+			return;
+
+		//reset the getLoc timer before we clear other timers
+		// otherwise the next call to getLoc will not be correct
+		ac.resetLastSetLocUpdate();
+
+		// call getLoc once as it processes loc to the ms
+		Vector3fImmutable curLoc = ac.getLoc();
+
+		if (MBServerStatics.MOVEMENT_SYNC_DEBUG)
+			Logger.info("Finished Alt change, setting the end location to "
+					+ ac.getEndLoc().getX() + ' ' + ac.getEndLoc().getZ()
+					+ " moving=" + ac.isMoving()
+					+ " and current location is " + curLoc.getX() + ' ' + curLoc.getZ());
+
+		if (ac.getDebug(1) && ac.getObjectType().equals(GameObjectType.PlayerCharacter))
+			ChatManager.chatSystemInfo((PlayerCharacter) ac, "Finished Alt change, setting the end location to " + ac.getEndLoc().getX() + ' ' + ac.getEndLoc().getZ() + " moving=" + ac.isMoving() + " and current location is " + curLoc.getX() + ' ' + curLoc.getZ());
+
+		//Send run/walk/sit/stand to tell the client we are flying / landing etc
+		ac.update();
+		ac.stopMovement(ac.getLoc());
+		if (ac.isAlive())
+			MovementManager.sendRWSSMsg(ac);
+
+		//Check collision again
+	}
+
+
+	// Handle formation movement in group
+
+	public static void moveGroup(PlayerCharacter pc, ClientConnection origin, MoveToPointMsg msg) throws MsgSendException {
+		// get forward vector
+		Vector3f faceDir = new Vector3f(pc.getFaceDir().x, 0, pc.getFaceDir().z).normalize();
+		// get perpendicular vector
+		Vector3f crossDir = new Vector3f(faceDir.z, 0, -faceDir.x);
+
+		//get source loc with altitude
+		Vector3f sLoc = new Vector3f(pc.getLoc().x, pc.getAltitude(), pc.getLoc().z);
+
+		Group group = GroupManager.getGroup(pc);
+		Set<PlayerCharacter> members = group.getMembers();
+		int pos = 0;
+		for (PlayerCharacter member : members) {
+
+			if (member == null)
+				continue;
+			if (member.getObjectUUID() == pc.getObjectUUID())
+				continue;
+
+			MoveToPointMsg groupMsg = new MoveToPointMsg(msg);
+
+			// Verify group member should be moved
+
+			pos++;
+			if (member.getFollow() != true)
+				continue;
+
+			//get member loc with altitude, then range against source loc
+			Vector3f mLoc = new Vector3f(member.getLoc().x, member.getAltitude(), member.getLoc().z);
+
+			if (sLoc.distanceSquared2D(mLoc) > sqr(MBServerStatics.FORMATION_RANGE))
+				continue;
+
+			//don't move if player has taken damage from another player in last 60 seconds
+			long lastAttacked = System.currentTimeMillis() - pc.getLastPlayerAttackTime();
+			if (lastAttacked < 60000)
+				continue;
+
+			if (!member.isAlive())
+				continue;
+
+			//don't move if player is stunned or rooted
+			PlayerBonuses bonus = member.getBonuses();
+			if (bonus.getBool(ModType.Stunned, SourceType.None) || bonus.getBool(ModType.CannotMove, SourceType.None))
+				continue;
+			
+			member.update();
+
+
+			// All checks passed, let's move the player
+			// First get the offset position
+			Vector3f offset = Formation.getOffset(group.getFormation(), pos);
+			Vector3fImmutable destination = pc.getEndLoc();
+			// offset forwards or backwards
+			destination = destination.add(faceDir.mult(offset.z));
+			// offset left or right
+			destination = destination.add(crossDir.mult(offset.x));
+			//			ArrayList<AbstractWorldObject> awoList = WorldGrid.INSTANCE.getObjectsInRangePartial(member, member.getLoc().distance2D(destination) +1000, MBServerStatics.MASK_BUILDING);
+			//
+			//			boolean skip = false;
+			//
+			//			for (AbstractWorldObject awo: awoList){
+			//				Building building = (Building)awo;
+			//
+			//				if (building.getBounds() != null){
+			//					if (Bounds.collide(building, member.getLoc(), destination)){
+			//						skip = true;
+			//						break;
+			//					}
+			//
+			//				}
+			//
+			//			}
+			//
+			//			if (skip)
+			//				continue;
+			//			if (member.isMoving())
+			//				member.stopMovement();
+
+			// Update player speed to match group lead speed and make standing
+			if (member.isSit() || (member.isWalk() != pc.isWalk())) {
+				member.setSit(false);
+				member.setWalkMode(pc.isWalk());
+				MovementManager.sendRWSSMsg(member);
+			}
+
+			//cancel any effects that break upon movement
+			member.cancelOnMove();
+
+			// send movement for other players to see
+			groupMsg.setSourceID(member.getObjectUUID());
+			groupMsg.setStartCoord(member.getLoc());
+			groupMsg.setEndCoord(destination);
+			groupMsg.clearTarget();
+			DispatchMessage.sendToAllInRange(member, groupMsg);
+
+			// update group member
+			member.setFaceDir(destination.subtract2D(member.getLoc()).normalize());
+			member.setEndLoc(destination);
+		}
+	}
+
+	//Getting rid of flgith timer.
+
+	public static void addFlightTimer(PlayerCharacter pc, ChangeAltitudeMsg msg, int duration) {
+		if (pc == null || pc.getTimers() == null)
+			return;
+		if (!pc.getTimers().containsKey(flightTimerJobName)) {
+			FlightJob ftj = new FlightJob(pc, msg, duration);
+			JobContainer jc = JobScheduler.getInstance().scheduleJob(ftj, duration);
+			pc.getTimers().put(flightTimerJobName, jc);
+		}
+	}
+
+	public static void addChangeAltitudeTimer(PlayerCharacter pc, float startAlt, float targetAlt, int duration) {
+		if (pc == null || pc.getTimers() == null)
+			return;
+		ChangeAltitudeJob catj = new ChangeAltitudeJob(pc, startAlt, targetAlt);
+		JobContainer jc = JobScheduler.getInstance().scheduleJob(catj, duration);
+		pc.getTimers().put(changeAltitudeTimerJobName, jc);
+	}
+
+	
+	public static void translocate(AbstractCharacter teleporter, Vector3fImmutable targetLoc, Regions region) {
+
+
+		if (targetLoc == null)
+			return;
+
+
+		Vector3fImmutable oldLoc = new Vector3fImmutable(teleporter.getLoc());
+			
+		
+			teleporter.stopMovement(targetLoc);
+			
+			teleporter.setRegion(region);
+		
+	
+
+			//mobs ignore region sets for now.
+			if (teleporter.getObjectType().equals(GameObjectType.Mob)){
+				teleporter.setInBuildingID(0);
+				teleporter.setInBuilding(-1);
+				teleporter.setInFloorID(-1);
+				TeleportToPointMsg msg = new TeleportToPointMsg(teleporter, targetLoc.getX(), targetLoc.getY(), targetLoc.getZ(), 0, -1, -1);
+				DispatchMessage.dispatchMsgToInterestArea(oldLoc, teleporter, msg, DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, false, false);
+				return;
+			}
+		TeleportToPointMsg msg = new TeleportToPointMsg(teleporter, targetLoc.getX(), targetLoc.getY(), targetLoc.getZ(), 0, -1, -1);
+		//we shouldnt need to send teleport message to new area, as loadjob should pick it up.
+	//	DispatchMessage.dispatchMsgToInterestArea(teleporter, msg, DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, true, false);
+		DispatchMessage.dispatchMsgToInterestArea(oldLoc, teleporter, msg, DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, true, false);
+		
+		if (teleporter.getObjectType().equals(GameObjectType.PlayerCharacter))
+		InterestManager.INTERESTMANAGER.HandleLoadForTeleport((PlayerCharacter)teleporter);
+
+	}
+	
+	public static void translocateToObject(AbstractCharacter teleporter, AbstractWorldObject worldObject) {
+
+
+		
+		Vector3fImmutable targetLoc = teleporter.getLoc();
+
+		Vector3fImmutable oldLoc = new Vector3fImmutable(teleporter.getLoc());
+
+			teleporter.stopMovement(teleporter.getLoc());
+
+		
+		
+	
+
+			//mobs ignore region sets for now.
+			if (teleporter.getObjectType().equals(GameObjectType.Mob)){
+				teleporter.setInBuildingID(0);
+				teleporter.setInBuilding(-1);
+				teleporter.setInFloorID(-1);
+				TeleportToPointMsg msg = new TeleportToPointMsg(teleporter, targetLoc.getX(), targetLoc.getY(), targetLoc.getZ(), 0, -1, -1);
+				DispatchMessage.dispatchMsgToInterestArea(oldLoc, teleporter, msg, DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, false, false);
+				return;
+			}
+		boolean collide = false;
+		int maxFloor = -1;
+		int buildingID = 0;
+		boolean isGroundLevel = false;
+		HashSet<AbstractWorldObject> buildings = WorldGrid.getObjectsInRangePartial(teleporter, 200, MBServerStatics.MASK_BUILDING);
+		for (AbstractWorldObject awo : buildings) {
+			Building building = (Building) awo;
+			if (collide)
+				break;
+		}
+		if (!collide) {
+			teleporter.setInBuildingID(0);
+			teleporter.setInBuilding(-1);
+			teleporter.setInFloorID(-1);
+		} else {
+			if (isGroundLevel) {
+				teleporter.setInBuilding(0);
+				teleporter.setInFloorID(-1);
+			} else {
+				teleporter.setInBuilding(maxFloor - 1);
+				teleporter.setInFloorID(0);
+			}
+		}
+
+
+		TeleportToPointMsg msg = new TeleportToPointMsg(teleporter, targetLoc.getX(), targetLoc.getY(), targetLoc.getZ(), 0, -1, -1);
+		//we shouldnt need to send teleport message to new area, as loadjob should pick it up.
+	//	DispatchMessage.dispatchMsgToInterestArea(teleporter, msg, DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, true, false);
+		DispatchMessage.dispatchMsgToInterestArea(oldLoc, teleporter, msg, DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, true, false);
+		
+		if (teleporter.getObjectType().equals(GameObjectType.PlayerCharacter))
+		InterestManager.INTERESTMANAGER.HandleLoadForTeleport((PlayerCharacter)teleporter);
+
+	}
+
+	private static void syncLoc(AbstractCharacter ac, Vector3fImmutable clientLoc, boolean useClientLoc) {
+			ac.teleport(ac.getLoc());
+	}
+}
diff --git a/src/engine/gameManager/PowersManager.java b/src/engine/gameManager/PowersManager.java
new file mode 100644
index 00000000..4dbca015
--- /dev/null
+++ b/src/engine/gameManager/PowersManager.java
@@ -0,0 +1,2790 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+package engine.gameManager;
+
+import engine.Enum.*;
+import engine.InterestManagement.HeightMap;
+import engine.InterestManagement.WorldGrid;
+import engine.job.AbstractJob;
+import engine.job.AbstractScheduleJob;
+import engine.job.JobContainer;
+import engine.job.JobScheduler;
+import engine.jobs.*;
+import engine.math.Vector3fImmutable;
+import engine.net.ByteBufferWriter;
+import engine.net.Dispatch;
+import engine.net.DispatchMessage;
+import engine.net.client.ClientConnection;
+import engine.net.client.msg.*;
+import engine.objects.*;
+import engine.powers.*;
+import engine.powers.effectmodifiers.AbstractEffectModifier;
+import engine.powers.poweractions.AbstractPowerAction;
+import engine.powers.poweractions.TrackPowerAction;
+import engine.server.MBServerStatics;
+import org.pmw.tinylog.Logger;
+
+import java.sql.ResultSet;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ThreadLocalRandom;
+
+import static engine.math.FastMath.sqr;
+
+public enum PowersManager {
+
+	POWERMANAGER;
+
+	public static HashMap<String, PowersBase> powersBaseByIDString = new HashMap<>();
+	public static HashMap<Integer, PowersBase> powersBaseByToken = new HashMap<>();
+	public static HashMap<String, EffectsBase> effectsBaseByIDString = new HashMap<>();
+	public static HashMap<Integer, EffectsBase> effectsBaseByToken = new HashMap<>();
+	public static HashMap<String, AbstractPowerAction> powerActionsByIDString = new HashMap<>();
+	public static HashMap<Integer, AbstractPowerAction> powerActionsByID = new HashMap<>();
+	public static HashMap<String, Integer> ActionTokenByIDString = new HashMap<>();
+	public static HashMap<Integer, AbstractEffectModifier> modifiersByToken = new HashMap<>();
+	public static HashMap<String,Integer> AnimationOverrides = new HashMap<>();
+	private static JobScheduler js;
+
+	public static void initPowersManager(boolean fullPowersLoad) {
+
+		if (fullPowersLoad)
+			PowersManager.InitializePowers();
+		else
+			PowersManager.InitializeLoginPowers();
+
+		PowersManager.js = JobScheduler.getInstance();
+
+	}
+
+	private PowersManager() {
+
+	}
+
+	public static PowersBase getPowerByToken(int token) {
+		return PowersManager.powersBaseByToken.get(token);
+	}
+
+	public static PowersBase getPowerByIDString(String IDString) {
+		return PowersManager.powersBaseByIDString.get(IDString);
+	}
+
+	public static EffectsBase getEffectByIDString(String IDString) {
+		return PowersManager.effectsBaseByIDString.get(IDString);
+	}
+
+	public static AbstractPowerAction getPowerActionByID(Integer id) {
+		return PowersManager.powerActionsByID.get(id);
+	}
+
+	public static AbstractPowerAction getPowerActionByIDString(String IDString) {
+		return PowersManager.powerActionsByIDString.get(IDString);
+	}
+
+	public static EffectsBase getEffectByToken(int token) {
+		return PowersManager.effectsBaseByToken.get(token);
+	}
+
+	// This pre-loads only PowersBase for login
+	public static void InitializeLoginPowers() {
+
+		// get all PowersBase
+		ArrayList<PowersBase> pbList = PowersBase.getAllPowersBase();
+
+		for (PowersBase pb : pbList) {
+			if (pb.getToken() != 0)
+				PowersManager.powersBaseByToken.put(pb.getToken(), pb);
+		}
+	}
+
+	// This pre-loads all powers and effects
+	public static void InitializePowers() {
+
+		// Add EffectsBase
+		ArrayList<EffectsBase> ebList = EffectsBase.getAllEffectsBase();
+
+		for (EffectsBase eb : ebList) {
+			PowersManager.effectsBaseByToken.put(eb.getToken(), eb);
+			PowersManager.effectsBaseByIDString.put(eb.getIDString(), eb);
+
+		}
+		
+		// Add Fail Conditions
+		EffectsBase.getFailConditions(PowersManager.effectsBaseByIDString);
+
+		// Add Modifiers to Effects
+		AbstractEffectModifier.getAllEffectModifiers();
+
+		// Add Source Types to Effects
+		PowersManager.addAllSourceTypes();
+		PowersManager.addAllAnimationOverrides();
+
+		// Add PowerActions
+		AbstractPowerAction.getAllPowerActions(PowersManager.powerActionsByIDString, PowersManager.powerActionsByID, PowersManager.effectsBaseByIDString);
+
+		// Load valid Item Flags
+	//	AbstractPowerAction.loadValidItemFlags(PowersManager.powerActionsByIDString);
+
+		// get all PowersBase
+		ArrayList<PowersBase> pbList = PowersBase.getAllPowersBase();
+		for (PowersBase pb : pbList) {
+			if (pb.getToken() != 0) {
+				PowersManager.powersBaseByIDString.put(pb.getIDString(), pb);
+				PowersManager.powersBaseByToken.put(pb.getToken(), pb);
+			}
+		}
+		
+		// Add Power Prereqs
+		PowerPrereq.getAllPowerPrereqs(PowersManager.powersBaseByIDString);
+		// Add Fail Conditions
+		PowersBase.getFailConditions(PowersManager.powersBaseByIDString);
+		// Add Actions Base
+		ActionsBase.getActionsBase(PowersManager.powersBaseByIDString,
+				PowersManager.powerActionsByIDString);
+		
+	}
+
+	private static void addAllSourceTypes() {
+		PreparedStatementShared ps = null;
+		try {
+			ps = new PreparedStatementShared("SELECT * FROM static_power_sourcetype");
+			ResultSet rs = ps.executeQuery();
+			String IDString, source;
+			while (rs.next()) {
+				IDString = rs.getString("IDString");
+				int token = DbManager.hasher.SBStringHash(IDString);
+				
+				
+				source = rs.getString("source").replace("-", "").trim();
+				EffectSourceType effectSourceType = EffectSourceType.GetEffectSourceType(source);
+				
+				if (EffectsBase.effectSourceTypeMap.containsKey(token) == false)
+					EffectsBase.effectSourceTypeMap.put(token, new HashSet<>());
+				
+				EffectsBase.effectSourceTypeMap.get(token).add(effectSourceType);
+			}
+			rs.close();
+		} catch (Exception e) {
+			Logger.error( e);
+		} finally {
+			ps.release();
+		}
+	}
+
+	private static void addAllAnimationOverrides() {
+		PreparedStatementShared ps = null;
+		try {
+			ps = new PreparedStatementShared("SELECT * FROM static_power_animation_override");
+			ResultSet rs = ps.executeQuery();
+			String IDString;
+			int animation;
+			while (rs.next()) {
+				IDString = rs.getString("IDString");
+
+				EffectsBase eb = PowersManager.getEffectByIDString(IDString);
+				if (eb != null)
+					IDString = eb.getIDString();
+
+				animation = rs.getInt("animation");
+				PowersManager.AnimationOverrides.put(IDString, animation);
+
+			}
+			rs.close();
+		} catch (Exception e) {
+			Logger.error( e);
+		} finally {
+			ps.release();
+		}
+	}
+
+	public static EffectsBase setEffectToken(int ID, int token) {
+		for (EffectsBase eb : PowersManager.effectsBaseByIDString.values()) {
+			if (eb.getUUID() == ID) {
+				eb.setToken(token);
+				return eb;
+			}
+		}
+		return null;
+	}
+
+	public static void usePower(final PerformActionMsg msg, ClientConnection origin,
+			boolean sendCastToSelf) {
+
+		if (usePowerA(msg, origin, sendCastToSelf)) {
+			// Cast failed for some reason, reset timer
+
+			RecyclePowerMsg recyclePowerMsg = new RecyclePowerMsg(msg.getPowerUsedID());
+			Dispatch dispatch = Dispatch.borrow(origin.getPlayerCharacter(), recyclePowerMsg);
+			DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.PRIMARY);
+
+			// Send Fail to cast message
+			PlayerCharacter pc = SessionManager
+					.getPlayerCharacter(origin);
+
+			if (pc != null) {
+				sendPowerMsg(pc, 2, msg);
+				if (pc.isCasting()){
+					pc.update();
+				}
+				
+				pc.setIsCasting(false);
+			}
+
+		}
+	}
+
+	public static void useMobPower(Mob caster, AbstractCharacter target, PowersBase pb, int rank) {
+
+		PerformActionMsg msg = createPowerMsg(pb, rank, caster, target);
+		msg.setUnknown04(1);
+
+		if (useMobPowerA(msg, caster)) {
+			//sendMobPowerMsg(caster,2,msg); //Lol wtf was i thinking sending msg's to mobs... ZZZZ
+		}
+	}
+
+	public static boolean usePowerA(final PerformActionMsg msg, ClientConnection origin,
+			boolean sendCastToSelf) {
+		PlayerCharacter playerCharacter = SessionManager.getPlayerCharacter(
+				origin);
+		if (playerCharacter == null)
+			return false;
+
+		boolean CSRCast = false;
+
+
+		if (MBServerStatics.POWERS_DEBUG) {
+			ChatManager.chatSayInfo(
+					playerCharacter,
+					"Using Power: " + Integer.toHexString(msg.getPowerUsedID())
+					+ " (" + msg.getPowerUsedID() + ')');
+			Logger.info( "Using Power: "
+					+ Integer.toHexString(msg.getPowerUsedID()) + " ("
+					+ msg.getPowerUsedID() + ')');
+		}
+
+		//Sending recycle message to player if died while casting.
+		if (!playerCharacter.isAlive() && msg.getPowerUsedID() != 428589216) { //succor
+
+			RecyclePowerMsg recyclePowerMsg = new RecyclePowerMsg(msg.getPowerUsedID());
+			Dispatch dispatch = Dispatch.borrow(playerCharacter, recyclePowerMsg);
+			DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.PRIMARY);
+
+			return false;
+		}
+		
+		
+
+
+		// if (!pc.getPowers().contains(msg.getPowerUsedID())) {
+		// sendPowerMsg(pc, 10, msg);
+		// return false;
+		// }
+		// verify recycle timer is not active for power, skip for a CSR
+		if (playerCharacter.getRecycleTimers().containsKey(msg.getPowerUsedID()) && !playerCharacter.isCSR()) {
+			// ChatManager.chatSayInfo(pc, "Recycle timer not finished!");
+
+			Logger.warn("usePowerA(): Cheat attempted? '" + msg.getPowerUsedID() + "' recycle timer not finished " + playerCharacter.getName());
+			return false;
+		}
+
+		// get power
+		PowersBase pb = PowersManager.powersBaseByToken.get(msg.getPowerUsedID());
+		if (pb == null) {
+			ChatManager.chatSayInfo(playerCharacter,
+					"This power is not implemented yet.");
+
+			// Logger.error("usePowerA(): Power '" +
+			// msg.getPowerUsedID()
+			// + "' was not found on powersBaseByToken map.");
+			return true;
+			// return false;
+		}
+
+		if (playerCharacter.getLastPower() != null)
+			return true;
+		
+		//Check if Power Target is allowed to cast.
+
+
+		// Check powers for normal users
+		if (playerCharacter.getPowers() == null || !playerCharacter.getPowers().containsKey(msg.getPowerUsedID()))
+			if (!playerCharacter.isCSR()) {
+				if (!MBServerStatics.POWERS_DEBUG) {
+					//  ChatManager.chatSayInfo(pc, "You may not cast that spell!");
+
+					 Logger.info("usePowerA(): Cheat attempted? '" + msg.getPowerUsedID() + "' was not associated with " + playerCharacter.getName());
+					return true;
+				}
+			} else
+				CSRCast = true;
+
+		// get numTrains for power
+		int trains = msg.getNumTrains();
+
+		// can't go over the max trains for the power, unless CSR
+		if (trains > pb.getMaxTrains() && !playerCharacter.isCSR()) {
+			trains = pb.getMaxTrains();
+			msg.setNumTrains(trains);
+		}
+
+		// can't go over total trains by player
+		if (playerCharacter.getPowers() != null && playerCharacter.getPowers().containsKey(msg.getPowerUsedID())) {
+			CharacterPower cp = playerCharacter.getPowers().get(msg.getPowerUsedID());
+			if (cp != null) {
+				int tot = cp.getTotalTrains();
+				if (tot == 0 && !playerCharacter.isCSR())
+					return false;
+				if (trains != tot && !playerCharacter.isCSR()) {
+					trains = tot;
+					msg.setNumTrains(trains);
+				}
+			}
+		}
+
+		// get recycle time in ms
+		int time = pb.getRecycleTime(trains);
+
+		// verify player is in correct mode (combat/nonCombat)
+		if (playerCharacter.isCombat()) {
+			if (!pb.allowedInCombat())
+				// ChatManager.chatPowerError(pc,
+				// "This power is not allowed in combat mode.");
+				return true;
+		} else if (!pb.allowedOutOfCombat())
+			// ChatManager.chatPowerError(pc,
+			// "You must be in combat mode to use this power.");
+			return true;
+
+		// verify player is not stunned or prohibited from casting
+		PlayerBonuses bonus = playerCharacter.getBonuses();
+SourceType sourceType = SourceType.GetSourceType(pb.getCategory());
+		if (bonus != null && (bonus.getBool(ModType.Stunned,SourceType.None) || bonus.getBool(ModType.CannotCast, SourceType.None) || bonus.getBool(ModType.BlockedPowerType, sourceType)))
+			return true;
+
+		// if moving make sure spell valid for movement
+		Vector3fImmutable endLoc = playerCharacter.getEndLoc();
+
+		
+			if (!pb.canCastWhileMoving())
+				if (playerCharacter.isMoving()){
+
+				// if movement left is less then 1 seconds worth then let cast
+				// go through.
+				float distanceLeftSquared = endLoc.distanceSquared2D(playerCharacter.getLoc());
+
+				if (distanceLeftSquared > sqr(playerCharacter.getSpeed()))
+					return true;
+			}
+		// if flying, make sure spell valid for flying.
+		// if (pc.getAltitude() > 0)
+		// if (!pb.canCastWhileFlying())
+		// return true;
+
+		int targetLiveCounter = -1;
+
+		// get target based on targetType;
+		if (pb.targetFromLastTarget() || pb.targetPet()) // use msg's target
+			if (pb.isAOE()) {
+				if (!pb.usePointBlank()) {
+					AbstractWorldObject target = getTarget(msg);
+					
+					
+					
+					if (target != null && target.getObjectType() == GameObjectType.Building && !pb.targetBuilding()) {
+						PowersManager.sendPowerMsg(playerCharacter, 9, new PerformActionMsg(msg));
+						return true;
+					}
+
+					if (target == null) {
+						if (playerCharacter.getLoc().distanceSquared2D(msg.getTargetLoc()) > sqr(pb
+								.getRange()))
+							return true;
+					} else if (verifyInvalidRange(playerCharacter, target, pb.getRange()))
+						// pc.getLoc().distance(target.getLoc()) >
+						// pb.getRange())
+						return true;
+
+
+				}
+			} else {
+				// get target
+				AbstractWorldObject target = getTarget(msg);
+
+				if (target == null)
+					return true;
+				
+				if (!target.isAlive() && target.getObjectType().equals(GameObjectType.Building) == false  && msg.getPowerUsedID() != 428589216)
+					return true;
+
+				float range = pb.getRange();
+				// verify target is in range
+			
+
+				if (verifyInvalidRange(playerCharacter, target, range))
+					// (pc.getLoc().distance(target.getLoc()) > pb.getRange()) {
+					// TODO send message that target is out of range
+					return true;
+
+				// verify target is valid type
+				if (!validateTarget(target, playerCharacter, pb))
+					return true;
+
+
+				if (AbstractWorldObject.IsAbstractCharacter(target))
+					targetLiveCounter = ((AbstractCharacter) target).getLiveCounter();
+			}
+
+		// verify regular player can cast spell, otherwise authenticate
+		if (!pb.regularPlayerCanCast()) {
+            Account a = SessionManager.getAccount(playerCharacter);
+            if (a.status.equals(AccountStatus.ADMIN) == false) {
+                Logger.warn("Player '" + playerCharacter.getName()
+                        + "' is attempting to cast a spell outside of their own access level.");
+                return true;
+            }
+        }
+
+		// verify player has proper effects applied to use power
+		if (pb.getEffectPrereqs().size() > 0 && playerCharacter.getEffects() != null) {
+
+			boolean passed = false;
+			for (PowerPrereq pp : pb.getEffectPrereqs()) {
+
+				EffectsBase eb = PowersManager.getEffectByIDString(pp.getEffect());
+
+				if (playerCharacter.containsEffect(eb.getToken())) {
+					passed = true;
+					break;
+				}
+			}
+
+			if (!passed)
+				return true;
+		}
+
+		//verify player has proper equipment to use power
+		if (pb.getEquipPrereqs().size() > 0) {
+
+			boolean passed = false;
+
+			for (PowerPrereq pp : pb.getEquipPrereqs()) {
+
+				int slot = pp.mainHand() ? MBServerStatics.SLOT_MAINHAND : MBServerStatics.SLOT_OFFHAND;
+
+				if (playerCharacter.validEquip(slot, pp.getMessage())) {
+					passed = true; //should have item in slot
+					break;
+				} else if (!pp.isRequired()) {
+					passed = true; //should not have item in slot
+					break;
+				}
+			}
+			if (!passed)
+				return true;
+		}
+
+		// TODO if target immune to powers, cancel unless aoe
+		// Also make sure No.ImmuneToPowers is false for target
+		// if there's a different power waiting to finish, stop here
+	
+
+		//get power cost and calculate any power cost modifiers
+		float cost = pb.getCost(trains);
+		
+		if (playerCharacter.isCSR())
+			cost = 0;
+
+		if (bonus != null)
+			cost *= (1 + bonus.getFloatPercentAll(ModType.PowerCost, SourceType.None));
+		
+		if (playerCharacter.getAltitude() > 0)
+			cost *= 1.5f;
+
+		// Verify player can afford to use power
+		//CCR toons dont use cost
+		
+		if (!playerCharacter.isCSR()){
+			if (cost > 0)
+				if ((playerCharacter.getObjectTypeMask() & MBServerStatics.MASK_UNDEAD) != 0)
+					if (playerCharacter.getHealth() <= cost)
+						return true;
+					else {
+						playerCharacter.modifyHealth(-cost, playerCharacter, true);
+						ModifyHealthMsg mhm = new ModifyHealthMsg(playerCharacter, playerCharacter, -cost,
+								0f, 0f, 0, null,
+								9999, 0);
+						mhm.setOmitFromChat(1);
+						DispatchMessage.dispatchMsgToInterestArea(playerCharacter, mhm, DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, true, false);
+					}
+				else if (pb.useMana())
+					if (playerCharacter.getMana() < cost)
+						return true;
+					else
+						playerCharacter.modifyMana(-cost, playerCharacter, true);
+				else if (pb.useStamina())
+					if (playerCharacter.getStamina() < cost)
+						return true;
+					else
+						playerCharacter.modifyStamina(-cost, playerCharacter, true);
+				else if (playerCharacter.getHealth() <= cost)
+					return true;
+				else
+					playerCharacter.modifyHealth(-cost, playerCharacter, true);
+		}
+		
+
+		// Validity checks passed, move on to casting spell
+		//get caster's live counter
+		int casterLiveCounter = playerCharacter.getLiveCounter();
+
+		// run recycle job for when cast is available again, don't bother adding the timer for CSRs
+		if (time > 0) {
+			FinishRecycleTimeJob frtj = new FinishRecycleTimeJob(playerCharacter, msg);
+			playerCharacter.getRecycleTimers().put(msg.getPowerUsedID(), js.scheduleJob(frtj, time));
+		} else {
+			// else send recycle message to unlock power
+			RecyclePowerMsg recyclePowerMsg = new RecyclePowerMsg(msg.getPowerUsedID());
+			Dispatch dispatch = Dispatch.borrow(playerCharacter, recyclePowerMsg);
+			DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.PRIMARY);
+		}
+
+		//what the fuck?
+//		// Send cast to other players
+//		if ((playerCharacter.getObjectTypeMask() & MBServerStatics.MASK_UNDEAD) != 0)
+//			msg.setUnknown04(2); // Vampire Race, use health?
+//		else
+//			msg.setUnknown04(1); // Regular Race, use mana?
+		int tr = msg.getNumTrains();
+		DispatchMessage.dispatchMsgToInterestArea(playerCharacter, msg, DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, sendCastToSelf, false);
+
+		//Make new msg..
+		PerformActionMsg copyMsg = new PerformActionMsg(msg);
+		copyMsg.setNumTrains(tr);
+
+		// make person casting stand up if spell (unless they're casting a chant which does not make them stand up)
+		if (pb.isSpell() && !pb.isChant() && playerCharacter.isSit()) {
+			playerCharacter.update();
+			playerCharacter.setSit(false);
+			UpdateStateMsg updateStateMsg = new UpdateStateMsg(playerCharacter);
+			DispatchMessage.dispatchMsgToInterestArea(playerCharacter, updateStateMsg, DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, true, false);
+
+		}
+
+		// update cast (use skill) fail condition
+		playerCharacter.cancelOnCast();
+
+		// update castSpell (use spell) fail condition if spell
+		if (pb.isSpell())
+			playerCharacter.cancelOnSpell();
+
+		// get cast time in ms.
+		time = pb.getCastTime(trains);
+
+		// set player is casting for regens
+
+		
+			if (time > 100){
+				playerCharacter.update();
+				playerCharacter.setIsCasting(true);
+			}
+		
+	
+		playerCharacter.setLastMovementState(playerCharacter.getMovementState());
+		// update used power timer
+		playerCharacter.setLastUsedPowerTime();
+
+		// run timer job to end cast
+		if (time < 1) // run immediately
+			finishUsePower(copyMsg, playerCharacter, casterLiveCounter, targetLiveCounter);
+		else {
+			UsePowerJob upj = new UsePowerJob(playerCharacter, copyMsg, copyMsg.getPowerUsedID(), pb, casterLiveCounter, targetLiveCounter);
+			JobContainer jc = js.scheduleJob(upj, time);
+
+			// make lastPower
+			playerCharacter.setLastPower(jc);	
+		}
+
+		if (CSRCast)
+			Logger.info("CSR " + playerCharacter.getName() + " cast power " + msg.getPowerUsedID() + '.');
+
+		return false;
+	}
+
+	public static void testPowers(ByteBufferWriter writer) {
+		writer.putInt(powersBaseByToken.size());
+		for (int token : powersBaseByToken.keySet()) {
+			writer.putInt(token);
+			writer.putInt(40);
+		}
+	}
+
+	public static boolean useMobPowerA(PerformActionMsg msg, Mob caster) {
+		if (caster == null)
+			return false;
+
+
+		if (!caster.isAlive() && msg.getPowerUsedID() != 428589216) //succor
+			return false;
+
+		// get power
+		PowersBase pb = PowersManager.powersBaseByToken.get(msg.getPowerUsedID());
+		if (pb == null)
+			return true;
+
+		// Check powers for normal users
+		// get numTrains for power
+		int trains = msg.getNumTrains();
+
+		// can't go over the max trains for the power, unless CSR
+		// can't go over total trains by player
+		// get recycle time in ms
+		int time = pb.getRecycleTime(trains);
+
+		// verify player is in correct mode (combat/nonCombat)
+		// verify player is not stunned or prohibited from casting
+		PlayerBonuses bonus = caster.getBonuses();
+		SourceType sourceType = SourceType.GetSourceType(pb.getCategory());
+		if (bonus != null && (bonus.getBool(ModType.Stunned, SourceType.None) || bonus.getBool(ModType.CannotCast, SourceType.None) || bonus.getBool(ModType.BlockedPowerType, sourceType)))
+			return true;
+
+		// if moving make sure spell valid for movement
+		// if flying, make sure spell valid for flying.
+		// if (pc.getAltitude() > 0)
+		// if (!pb.canCastWhileFlying())
+		// return true;
+		int targetLiveCounter = -1;
+
+		// get target based on targetType;
+		if (pb.targetFromLastTarget() || pb.targetPet()) // use msg's target
+			if (pb.isAOE()) {
+				if (!pb.usePointBlank()) {
+					AbstractWorldObject target = getTarget(msg);
+
+
+					if (target == null) {
+
+						if (caster.getLoc().distanceSquared2D(msg.getTargetLoc()) > sqr(pb
+								.getRange()))
+							return true;
+					} else if (verifyInvalidRange(caster, target, pb.getRange()))
+						// pc.getLoc().distance(target.getLoc()) >
+						// pb.getRange())
+						return true;
+				}
+			} else {
+				// get target
+				AbstractWorldObject target = getTarget(msg);
+
+				if (target == null)
+					return true;
+
+				// verify target is in range
+				if (verifyInvalidRange(caster, target, pb.getRange()))
+					// (pc.getLoc().distance(target.getLoc()) > pb.getRange()) {
+					// TODO send message that target is out of range
+					return true;
+
+				// verify target is valid type
+				if (AbstractWorldObject.IsAbstractCharacter(target))
+					targetLiveCounter = ((AbstractCharacter) target).getLiveCounter();
+			}
+
+		// TODO if target immune to powers, cancel unless aoe
+		// Also make sure No.ImmuneToPowers is false for target
+		// if there's a different power waiting to finish, stop here
+		if (caster.getLastMobPowerToken() != 0)
+			return true;
+
+		//get power cost and calculate any power cost modifiers
+		// Validity checks passed, move on to casting spell
+		//get caster's live counter
+		int casterLiveCounter = caster.getLiveCounter();
+
+		// run recycle job for when cast is available again, don't bother adding the timer for CSRs
+		// Send cast to other players
+		if (caster.getObjectTypeMask() == MBServerStatics.MASK_UNDEAD)
+			msg.setUnknown05(0); // Regular Race, use mana?
+		else
+			msg.setUnknown05(0);
+
+		int tr = msg.getNumTrains();
+
+		msg.setNumTrains(9999);
+
+		DispatchMessage.sendToAllInRange(caster, msg);
+		DispatchMessage.sendToAllInRange(caster, msg);
+
+		msg.setNumTrains(tr);
+
+		// make person casting stand up if spell (unless they're casting a chant which does not make them stand up)
+		// update cast (use skill) fail condition
+		caster.cancelOnCast();
+
+		// update castSpell (use spell) fail condition if spell
+		if (pb.isSpell())
+			caster.cancelOnSpell();
+
+		// get cast time in ms.
+		time = pb.getCastTime(trains);
+
+		// set player is casting for regens
+		caster.setIsCasting(true);
+		caster.setLastMobPowerToken(pb.getToken());
+
+		// run timer job to end cast
+		if (time < 1 || pb.getToken() == -1994153779){
+			// run immediately
+			finishUseMobPower(msg, caster, casterLiveCounter, targetLiveCounter);
+			caster.setLastMobPowerToken(0);
+		}
+			
+		else {
+			caster.setLastMobPowerToken(pb.getToken());
+			caster.setTimeStamp("FinishCast", System.currentTimeMillis() + (pb.getCastTime(trains)));
+		}
+		//			finishUseMobPower(msg, caster, casterLiveCounter, targetLiveCounter); //			UseMobPowerJob upj = new UseMobPowerJob(caster, msg, msg.getPowerUsedID(), pb, casterLiveCounter, targetLiveCounter);
+		//			 JobContainer jc = js.scheduleJob(upj, time);
+		//			// make lastPower
+
+
+		return false;
+	}
+
+	// called when a spell finishes casting. perform actions
+	public static void finishUsePower(final PerformActionMsg msg, PlayerCharacter playerCharacter, int casterLiveCounter, int targetLiveCounter) {
+
+		PerformActionMsg performActionMsg;
+		Dispatch dispatch;
+
+		if (playerCharacter == null || msg == null)
+			return;
+
+		if (playerCharacter.isCasting()){
+			playerCharacter.update();
+			playerCharacter.updateStamRegen(-100);
+		}
+		
+		playerCharacter.resetLastSetLocUpdate();
+		playerCharacter.setIsCasting(false);
+		// can't go over total trains by player
+
+
+		if (!playerCharacter.isAlive() || playerCharacter.getLiveCounter() != casterLiveCounter) {
+			playerCharacter.clearLastPower();
+			finishRecycleTime(msg.getPowerUsedID(), playerCharacter, true);
+
+
+			// Let's do good OO.  Clone message don't modify it.
+
+			performActionMsg = new PerformActionMsg(msg);
+			performActionMsg.setNumTrains(9999);
+			performActionMsg.setUnknown04(2);
+
+			dispatch = Dispatch.borrow(playerCharacter, performActionMsg);
+			DispatchMessage.dispatchMsgToInterestArea(playerCharacter, performActionMsg, DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, true, false);
+			return;
+		}
+
+		// set player is not casting for regens
+
+
+		// clear power.
+		playerCharacter.clearLastPower();
+
+		PowersBase pb = PowersManager.powersBaseByToken.get(msg.getPowerUsedID());
+
+		if (pb == null) {
+			Logger.error(
+					"finishUsePower(): Power '" + msg.getPowerUsedID()
+					+ "' was not found on powersBaseByToken map.");
+			return;
+		}
+
+		int trains = msg.getNumTrains();
+
+		// update used power timer
+		playerCharacter.setLastUsedPowerTime();
+
+		// verify player is not stunned or power type is blocked
+		PlayerBonuses bonus = playerCharacter.getBonuses();
+
+		if (bonus != null) {
+			if (bonus.getBool(ModType.Stunned, SourceType.None))
+				return;
+			
+			SourceType sourceType = SourceType.GetSourceType(pb.getCategory());
+			if (bonus.getBool(ModType.BlockedPowerType, sourceType)) {
+				finishRecycleTime(msg.getPowerUsedID(), playerCharacter, true);
+				return;
+			}
+		}
+
+		// get target loc
+		Vector3fImmutable targetLoc = msg.getTargetLoc();
+
+
+		if (pb.targetFromLastTarget() || pb.targetPet()) // use msg's target
+			if (pb.isAOE()) {
+				if (!pb.usePointBlank()) {
+					AbstractWorldObject mainTarget = getTarget(msg);
+
+					float speedRange = 0;
+					if (AbstractWorldObject.IsAbstractCharacter(mainTarget)) {
+						speedRange = ((AbstractCharacter) mainTarget).getSpeed() * (pb.getCastTime(trains) * .001f);
+					}
+
+					if (mainTarget != null && mainTarget.getObjectType() == GameObjectType.Building && !pb.targetBuilding()) {
+						PowersManager.sendPowerMsg(playerCharacter, 8, new PerformActionMsg(msg));
+						return;
+					}
+
+					if (mainTarget == null) {
+
+						if (playerCharacter.getLoc().distanceSquared2D(msg.getTargetLoc()) > sqr(pb
+								.getRange())) {
+							sendPowerMsg(playerCharacter, 8, msg);
+							return;
+						}
+					} else if (verifyInvalidRange(playerCharacter, mainTarget, speedRange + pb.getRange())) {
+
+						sendPowerMsg(playerCharacter, 8, msg);
+						return;
+					}
+				}
+			} else {
+
+				// get target
+				AbstractWorldObject mainTarget = getTarget(msg);
+
+				if (mainTarget == null)
+					return;
+
+				float speedRange = 0;
+				if (AbstractWorldObject.IsAbstractCharacter(mainTarget)) {
+					speedRange = ((AbstractCharacter) mainTarget).getSpeed() * (pb.getCastTime(trains) * .001f);
+				}
+				float range = pb.getRange() + speedRange;
+				
+
+
+				if (verifyInvalidRange(playerCharacter, mainTarget, range)) {
+
+					sendPowerMsg(playerCharacter, 8, msg);
+					return;
+				}
+				// (pc.getLoc().distance(target.getLoc()) > pb.getRange()) {
+				// TODO send message that target is out of range
+
+
+			}
+
+		if (targetLoc.x == 0f || targetLoc.z == 0f) {
+			AbstractWorldObject tar = getTarget(msg);
+			if (tar != null)
+				targetLoc = tar.getLoc();
+		}
+
+
+		// get list of targets
+		HashSet<AbstractWorldObject> allTargets = getAllTargets(
+				getTarget(msg), msg.getTargetLoc(), playerCharacter, pb);
+
+		// no targets found. send error message
+
+		if (allTargets.size() == 0) {
+			sendPowerMsg(playerCharacter, 9, msg);
+			return;
+		}
+
+		playerCharacter.setHateValue(pb.getHateValue(trains));
+
+		//Send Cast Message.
+//		PerformActionMsg castMsg = new PerformActionMsg(msg);
+//		castMsg.setNumTrains(9999);
+//		castMsg.setUnknown04(3);
+//		DispatchMessage.dispatchMsgToInterestArea(playerCharacter, castMsg, DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, true, false);
+//		
+		boolean msgCasted = false;
+		
+		for (AbstractWorldObject target : allTargets) {
+
+			if (target == null)
+				continue;
+
+			//Hacky Pyschic healing cross heal
+
+			//make sure target hasn't respawned since we began casting
+			//skip this if liveCounter = -1 (from aoe)
+
+			if (targetLiveCounter != -1)
+				if (AbstractWorldObject.IsAbstractCharacter(target))
+					if (targetLiveCounter != ((AbstractCharacter) target).getLiveCounter())
+						continue;
+
+			if (!target.isAlive() && target.getObjectType() != GameObjectType.Building && pb.getToken() != 428589216 && pb.getToken() != 429425915)
+				continue;
+
+			//make sure mob is awake to respond.
+			//if (target instanceof AbstractIntelligenceAgent)
+			//((AbstractIntelligenceAgent)target).enableIntelligence();
+			// If Hit roll required, test hit
+
+			boolean miss = false;
+			if (pb.requiresHitRoll() && !pb.isWeaponPower() && testAttack(playerCharacter, target, pb, msg)) {
+				miss = true;
+				//aggro mob even on a miss
+				if (target.getObjectType() == GameObjectType.Mob) {
+					Mob mobTarget = (Mob) target;
+					if (pb.isHarmful())
+						mobTarget.handleDirectAggro(playerCharacter);
+				}
+				continue;
+			}
+			if (target.getObjectType() == GameObjectType.Mob) {
+				Mob mobTarget = (Mob) target;
+				if (pb.isHarmful())
+					mobTarget.handleDirectAggro(playerCharacter);
+			}
+			//Power is aiding a target, handle aggro if combat target is a Mob.
+			if (!pb.isHarmful() && target.getObjectType() == GameObjectType.PlayerCharacter) {
+				PlayerCharacter pcTarget = (PlayerCharacter) target;
+				if (!pb.isHarmful())
+					Mob.HandleAssistedAggro(playerCharacter, pcTarget);
+			}
+
+			// update target of used power timer
+
+			if (pb.isHarmful())
+				if (target.getObjectType().equals(GameObjectType.PlayerCharacter) && target.getObjectUUID() != playerCharacter.getObjectUUID()) {
+
+					((PlayerCharacter) target).setLastTargetOfUsedPowerTime();
+					((PlayerCharacter) target).setTimeStamp("LastCombatPlayer", System.currentTimeMillis());
+					playerCharacter.setTimeStamp("LastCombatPlayer", System.currentTimeMillis());
+				}
+
+
+			//Player didn't miss power, send finish cast. Miss cast already sent.
+			
+
+			// finally Apply actions
+			for (ActionsBase ab : pb.getActions()) {
+				// get numTrains for power, skip action if invalid
+
+				if (trains < ab.getMinTrains() || trains > ab.getMaxTrains())
+					continue;
+				// If something blocks the action, then stop
+
+				if (ab.blocked(target, pb, trains)) {
+
+					PowersManager.sendEffectMsg(playerCharacter, 5, ab, pb);
+					continue;
+				}
+
+				// TODO handle overwrite stack order here
+				String stackType = ab.getStackType();
+				stackType = (stackType.equals("IgnoreStack")) ? Integer.toString(ab.getUUID()) : stackType;
+				//				if (!stackType.equals("IgnoreStack")) {
+				if (target.getEffects().containsKey(stackType)) {
+					// remove any existing power that overrides
+					Effect ef = target.getEffects().get(stackType);
+					AbstractEffectJob effect = null;
+					if (ef != null) {
+						JobContainer jc = ef.getJobContainer();
+						if (jc != null)
+							effect = (AbstractEffectJob) jc.getJob();
+					}
+					ActionsBase overwrite = effect.getAction();
+					
+					if (overwrite == null) {
+						Logger.error("NULL ACTION FOR POWER " + effect.getPowerToken());
+						continue;
+					}
+					
+						if (ab.getStackOrder() < overwrite.getStackOrder())
+							continue; // not high enough to overwrite
+						else if (ab.getStackOrder() > overwrite.getStackOrder()) {
+							effect.setNoOverwrite(true);
+							removeEffect(target, overwrite, true, false);
+						} else if (ab.getStackOrder() == overwrite.getStackOrder())
+							if (ab.greaterThanEqual()
+									&& (trains >= effect.getTrains())) {
+								effect.setNoOverwrite(true);
+								removeEffect(target, overwrite, true, false);
+							}
+					
+					 else if (ab.always())
+							removeEffect(target, overwrite, true, false);
+						else if (ab.greaterThan()
+								&& (trains > effect.getTrains())) {
+							effect.setNoOverwrite(true);
+							removeEffect(target, overwrite, true, false);
+						} else if (ab.greaterThan() && pb.getToken() == effect.getPowerToken())
+							removeEffect(target, overwrite, true, false);
+
+						else
+							continue; // trains not high enough to overwrite
+						
+					}
+					
+				//				}
+				
+
+				runPowerAction(playerCharacter, target, targetLoc, ab, trains, pb);
+				if (!miss && !msgCasted){
+					PerformActionMsg castMsg = new PerformActionMsg(msg);
+					castMsg.setNumTrains(9999);
+					castMsg.setUnknown04(2);
+					DispatchMessage.dispatchMsgToInterestArea(playerCharacter, castMsg, DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, true, false);
+					msgCasted = true;
+				}
+			}
+		}
+		
+		if ( !msgCasted){
+			PerformActionMsg castMsg = new PerformActionMsg(msg);
+			castMsg.setNumTrains(9999);
+			castMsg.setUnknown04(2);
+			DispatchMessage.dispatchMsgToInterestArea(playerCharacter, castMsg, DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, true, false);
+			msgCasted = true;
+		}
+
+		//Handle chant
+		if (pb != null && pb.isChant())
+			for (ActionsBase ab : pb.getActions()) {
+				AbstractPowerAction pa = ab.getPowerAction();
+				if (pa != null)
+					if (pb.getToken() != 428950414 && pb.getToken() != 428884878)
+						pa.handleChant(playerCharacter, playerCharacter, targetLoc, trains, ab, pb);
+					else if (PowersManager.getTarget(msg) != null && PowersManager.getTarget(msg).isAlive())
+						pa.handleChant(playerCharacter, PowersManager.getTarget(msg), targetLoc, trains, ab, pb);
+					else
+						pa.handleChant(playerCharacter, null, targetLoc, trains, ab, pb);
+			}
+		
+		
+
+		
+		
+		//DispatchMessage.dispatchMsgToInterestArea(playerCharacter, msg, DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, true, false);
+
+
+	}
+
+	public static void finishUseMobPower(PerformActionMsg msg, Mob caster, int casterLiveCounter, int targetLiveCounter) {
+
+		if (caster == null || msg == null)
+			return;
+
+		if (!caster.isAlive() || caster.getLiveCounter() != casterLiveCounter)
+			return;
+
+		// set player is not casting for regens
+		caster.setIsCasting(false);
+
+		
+		PowersBase pb = PowersManager.powersBaseByToken.get(msg.getPowerUsedID());
+		// clear power.
+				caster.setLastMobPowerToken(0);
+
+		if (pb == null) {
+			Logger.error(
+					"finishUsePower(): Power '" + msg.getPowerUsedID()
+					+ "' was not found on powersBaseByToken map.");
+			return;
+		}
+		
+		int trains = msg.getNumTrains();
+
+		// update used power timer
+		// verify player is not stunned or power type is blocked
+		PlayerBonuses bonus = caster.getBonuses();
+		if (bonus != null) {
+			if (bonus.getBool(ModType.Stunned,SourceType.None))
+				return;
+			SourceType sourceType = SourceType.GetSourceType(pb.getCategory());
+			if (bonus.getBool(ModType.BlockedPowerType, sourceType))
+				return;
+		}
+
+		msg.setNumTrains(9999);
+		msg.setUnknown04(2);
+		DispatchMessage.sendToAllInRange(caster, msg);
+
+		// get target loc
+		Vector3fImmutable targetLoc = msg.getTargetLoc();
+		if (targetLoc.x == 0f || targetLoc.z == 0f) {
+			AbstractWorldObject tar = getTarget(msg);
+			if (tar != null)
+				targetLoc = tar.getLoc();
+		}
+
+		// get list of targets
+		HashSet<AbstractWorldObject> allTargets = getAllTargets(
+				getTarget(msg), msg.getTargetLoc(), caster, pb);
+		for (AbstractWorldObject target : allTargets) {
+
+			if (target == null)
+				continue;
+
+
+			//make sure target hasn't respawned since we began casting
+			//skip this if liveCounter = -1 (from aoe)
+			if (targetLiveCounter != -1)
+				if (AbstractWorldObject.IsAbstractCharacter(target))
+					if (targetLiveCounter != ((AbstractCharacter) target).getLiveCounter())
+						continue;
+
+			//make sure mob is awake to respond.
+			//if (target instanceof AbstractIntelligenceAgent)
+			//((AbstractIntelligenceAgent)target).enableIntelligence();
+			// If Hit roll required, test hit
+			if (pb.requiresHitRoll() && !pb.isWeaponPower() && testAttack(caster, target, pb, msg))
+				//aggro mob even on a miss
+				continue;
+
+			// update target of used power timer
+
+			if (target.getObjectType().equals(GameObjectType.PlayerCharacter)) {
+
+				((PlayerCharacter) target).setLastTargetOfUsedPowerTime();
+				((PlayerCharacter) target).setTimeStamp("LastCombatPlayer", System.currentTimeMillis());
+			}
+
+
+			// finally Apply actions
+			for (ActionsBase ab : pb.getActions()) {
+				// get numTrains for power, skip action if invalid
+
+				if (trains < ab.getMinTrains() || trains > ab.getMaxTrains())
+					continue;
+				// If something blocks the action, then stop
+
+				if (ab.blocked(target, pb, trains))
+					continue;
+				// TODO handle overwrite stack order here
+				String stackType = ab.getStackType();
+				stackType = (stackType.equals("IgnoreStack")) ? Integer.toString(ab.getUUID()) : stackType;
+				//				if (!stackType.equals("IgnoreStack")) {
+				if (target.getEffects().containsKey(stackType)) {
+					// remove any existing power that overrides
+					Effect ef = target.getEffects().get(stackType);
+					AbstractEffectJob effect = null;
+					if (ef != null) {
+						JobContainer jc = ef.getJobContainer();
+						if (jc != null)
+							effect = (AbstractEffectJob) jc.getJob();
+					}
+					ActionsBase overwrite = effect.getAction();
+					
+					if (overwrite == null){
+						Logger.error("NULL ACTION FOR EFFECT " + effect.getPowerToken());
+						continue;
+					}
+					if (ab.getStackOrder() < overwrite.getStackOrder())
+						continue; // not high enough to overwrite
+					else if (ab.getStackOrder() > overwrite.getStackOrder()) {
+						effect.setNoOverwrite(true);
+						removeEffect(target, overwrite, true, false);
+					} else if (ab.getStackOrder() == overwrite.getStackOrder())
+						if (ab.greaterThanEqual()
+								&& (trains >= effect.getTrains())) {
+							effect.setNoOverwrite(true);
+							removeEffect(target, overwrite, true, false);
+						} else if (ab.always())
+							removeEffect(target, overwrite, true, false);
+						else if (ab.greaterThan()
+								&& (trains > effect.getTrains())) {
+							effect.setNoOverwrite(true);
+							removeEffect(target, overwrite, true, false);
+						} else if (ab.greaterThan() && pb.getToken() == effect.getPowerToken())
+							removeEffect(target, overwrite, true, false);
+
+						else
+							continue; // trains not high enough to overwrite
+				}
+
+				//				}
+				runPowerAction(caster, target, targetLoc, ab, trains, pb);
+			}
+		}
+
+		//Handle chant
+		if (pb != null && pb.isChant())
+			for (ActionsBase ab : pb.getActions()) {
+				AbstractPowerAction pa = ab.getPowerAction();
+				if (pa != null)
+					pa.handleChant(caster, caster, targetLoc, trains, ab, pb);
+			}
+
+		// TODO echo power use to everyone else
+		msg.setNumTrains(9999);
+		msg.setUnknown04(2);
+		DispatchMessage.sendToAllInRange(caster, msg);
+
+	}
+
+	// *** Refactor : Wtf is this mess?
+
+	private static boolean validMonsterType(AbstractWorldObject target, PowersBase pb) {
+
+		if (pb == null || target == null)
+			return false;
+
+		String mtp = pb.getMonsterTypePrereq();
+
+		if (mtp.length() == 0)
+			return true;
+
+		if (target.getObjectType().equals(GameObjectType.PlayerCharacter)) {
+
+			PlayerCharacter pc = (PlayerCharacter) target;
+			int raceID = 0;
+
+			if (pc.getRace() != null)
+				raceID = pc.getRace().getRaceRuneID();
+
+			switch (mtp) {
+			case "Shade":
+				return raceID == 2015 || raceID == 2016;
+			case "Elf":
+				return raceID == 2008 || raceID == 2009;
+			case "Dwarf":
+				return raceID == 2006;
+			case "Aracoix":
+				return raceID == 2002 || raceID == 2003;
+			case "Irekei":
+				return raceID == 2013 || raceID == 2014;
+			case "Vampire":
+				return raceID == 2028 || raceID == 2029;
+			}
+		} else if (target.getObjectType().equals(GameObjectType.Mob)) {
+			Mob mob = (Mob) target;
+
+			if (pb.targetMob() && !mob.isMob() && !mob.isSiege())
+				return false;
+			else if (pb.targetPet() && !mob.isPet() && !mob.isSiege())
+				return false;
+
+			switch (mtp) {
+			case "Animal":
+				if ((mob.getObjectTypeMask() & MBServerStatics.MASK_BEAST) == 0)
+					return false;
+				break;
+			case "NPC":
+				if ((mob.getObjectTypeMask() & MBServerStatics.MASK_HUMANOID) == 0)
+					return false;
+				break;
+			case "Rat":
+				if ((mob.getObjectTypeMask() & MBServerStatics.MASK_RAT) == 0)
+					return false;
+				break;
+			case "Siege":
+				if (!mob.isSiege())
+					return false;
+				break;
+			case "Undead":
+				if ((mob.getObjectTypeMask() & MBServerStatics.MASK_UNDEAD) == 0)
+					return false;
+				break;
+			}
+			return true;
+		} else return target.getObjectType().equals(GameObjectType.Building) && mtp.equals("Siege");
+		return false;
+	}
+
+	public static void summon(SendSummonsRequestMsg msg, ClientConnection origin) {
+		PlayerCharacter pc = SessionManager.getPlayerCharacter(
+				origin);
+		if (pc == null)
+			return;
+
+		PlayerCharacter target = SessionManager
+				.getPlayerCharacterByLowerCaseName(msg.getTargetName());
+		if (target == null || target.equals(pc) || target.isCombat()) {
+
+			if (target == null) // Player not found. Send not found message
+				ChatManager.chatInfoError(pc,
+						"Cannot find that player to summon.");
+			else if (target.isCombat())
+				ChatManager.chatInfoError(pc,
+						"Cannot summon player in combat.");
+			// else trying to summon self, just fail
+
+			// recycle summon
+			sendRecyclePower(msg.getPowerToken(), origin);
+
+			// TODO: client already subtracted 200 mana.. need to correct it
+			// end cast
+			PerformActionMsg pam = new PerformActionMsg(msg.getPowerToken(),
+					msg.getTrains(), msg.getSourceType(), msg.getSourceID(), 0,
+					0, 0f, 0f, 0f, 1, 0);
+			sendPowerMsg(pc, 2, pam);
+
+			return;
+		}
+
+		PerformActionMsg pam = new PerformActionMsg(msg.getPowerToken(), msg
+				.getTrains(), msg.getSourceType(), msg.getSourceID(), target
+				.getObjectType().ordinal(), target.getObjectUUID(), 0f, 0f, 0f, 1, 0);
+
+		// Client removes 200 mana on summon use.. so don't send message to self
+		target.addSummoner(pc.getObjectUUID(), System.currentTimeMillis() + MBServerStatics.FOURTYFIVE_SECONDS);
+		usePower(pam, origin, false);
+	}
+
+	public static void recvSummon(RecvSummonsRequestMsg msg, ClientConnection origin) {
+		PlayerCharacter pc = SessionManager.getPlayerCharacter(origin);
+		if (pc == null)
+			return;
+
+		PlayerCharacter source = PlayerCharacter.getFromCache(msg.getSourceID());
+		if (source == null)
+			return;
+
+		long tooLate = pc.getSummoner(source.getObjectUUID());
+		if (tooLate < System.currentTimeMillis()) {
+			ChatManager.chatInfoError(pc, "You waited too long to " + (msg.accepted() ? "accept" : "decline") + " the summons.");
+			pc.removeSummoner(source.getObjectUUID());
+			return;
+		}
+
+		if (pc.getBonuses() != null && pc.getBonuses().getBool(ModType.BlockedPowerType, SourceType.SUMMON)) {
+			ErrorPopupMsg.sendErrorMsg(pc, "You have been blocked from receiving summons!");
+			ErrorPopupMsg.sendErrorMsg(source, "Target is blocked from receiving summons!");
+			pc.removeSummoner(source.getObjectUUID());
+			return;
+		}
+		pc.removeSummoner(source.getObjectUUID());
+
+		// Handle Accepting or Denying a summons.
+		// set timer based on summon type.
+		boolean wentThrough = false;
+		if (msg.accepted())
+			// summons accepted, let's move the player if within time
+			if (source.isAlive()) {
+
+				//				//make sure summons handled in time
+				ConcurrentHashMap<String, JobContainer> timers = source.getTimers();
+				//				if (timers == null || !timers.containsKey("SummonSend")) {
+				//					ChatManager.chatInfoError(pc, "You waited too long to " + (msg.accepted() ? "accept" : "decline") + " the summons.");
+				//					return;
+				//				}
+
+				//				// clear last summons accept timer
+				//	timers.get("SummonSend").cancelJob();
+				//timers.remove("SummonSend");
+				// cancel any other summons waiting
+				timers = pc.getTimers();
+				if (timers != null && timers.containsKey("Summon"))
+					timers.get("Summon").cancelJob();
+
+				// get time to wait before summons goes through
+				BaseClass base = source.getBaseClass();
+				PromotionClass promo = source.getPromotionClass();
+				int duration;
+
+
+				//determine if in combat with another player
+
+
+				//comment out this block to disable combat timer
+				//				if (lastAttacked < 60000) {
+				//					if (pc.inSafeZone()) //player in safe zone, no need for combat timer
+				//						combat = false;
+				//					else if (source.inSafeZone()) //summoner in safe zone, apply combat timer
+				//						combat = true;
+				//					else if ((source.getLoc().distance2D(pc.getLoc())) > 6144f)
+				//						combat = true; //more than 1.5x width of zone, not tactical summons
+				//				}
+
+                if (promo != null && promo.getObjectUUID() == 2519)
+					duration = 10000; // Priest summons, 10 seconds
+				else if (base != null && base.getObjectUUID() == 2501)
+					duration = 15000; // Healer Summons, 15 seconds
+				else
+					duration = 45000; // Belgosh Summons, 45 seconds
+
+
+				// Teleport to summoners location
+				FinishSummonsJob fsj = new FinishSummonsJob(source, pc);
+				JobContainer jc = JobScheduler.getInstance().scheduleJob(fsj,
+						duration);
+				if (timers != null)
+					timers.put("Summon", jc);
+				wentThrough = true;
+			}
+
+		// Summons failed
+		if (!wentThrough)
+			// summons refused. Let's be nice and reset recycle timer
+			if (source != null) {
+
+				// Send summons refused Message
+				ErrorPopupMsg.sendErrorPopup(source, 29);
+
+				// recycle summons power
+				//finishRecycleTime(428523680, source, true);
+			}
+	}
+
+	public static void trackWindow(TrackWindowMsg msg, ClientConnection origin) {
+
+		PlayerCharacter playerCharacter = SessionManager.getPlayerCharacter(
+				origin);
+
+		if (playerCharacter == null)
+			return;
+
+		if (MBServerStatics.POWERS_DEBUG) {
+			ChatManager.chatSayInfo(
+					playerCharacter,
+					"Using Power: " + Integer.toHexString(msg.getPowerToken())
+					+ " (" + msg.getPowerToken() + ')');
+			Logger.info( "Using Power: "
+					+ Integer.toHexString(msg.getPowerToken()) + " ("
+					+ msg.getPowerToken() + ')');
+		}
+
+		// get track power used
+		PowersBase pb = PowersManager.powersBaseByToken.get(msg.getPowerToken());
+
+		if (pb == null || !pb.isTrack())
+			return;
+
+		//check track threshold timer to prevent spam
+		long currentTime = System.currentTimeMillis();
+		long timestamp = playerCharacter.getTimeStamp("trackWindow");
+		long dif = currentTime - timestamp;
+		if (dif < MBServerStatics.TRACK_WINDOW_THRESHOLD)
+			return;
+		playerCharacter.setTimeStamp("trackWindow", currentTime);
+
+		ArrayList<ActionsBase> ablist = pb.getActions();
+		if (ablist == null)
+			return;
+
+		TrackPowerAction tpa = null;
+		for (ActionsBase ab : ablist) {
+			AbstractPowerAction apa = ab.getPowerAction();
+			if (apa != null && apa instanceof TrackPowerAction)
+				tpa = (TrackPowerAction) apa;
+		}
+		if (tpa == null)
+			return;
+
+		// Check powers for normal users
+		if (playerCharacter.getPowers() == null || !playerCharacter.getPowers().containsKey(msg.getPowerToken()))
+			if (!playerCharacter.isCSR())
+				if (!MBServerStatics.POWERS_DEBUG) {
+					//  ChatManager.chatSayInfo(pc, "You may not cast that spell!");
+					//  this.logEXPLOIT("usePowerA(): Cheat attempted? '" + msg.getPowerToken() + "' was not associated with " + pc.getName());
+					return;
+				}
+
+		// Get search mask for track
+		int mask = 0;
+		if (pb.targetPlayer())
+			if (tpa.trackVampire()) // track vampires
+				mask = MBServerStatics.MASK_PLAYER | MBServerStatics.MASK_UNDEAD;
+			else
+				// track all players
+				mask = MBServerStatics.MASK_PLAYER;
+		else if (pb.targetCorpse()) // track corpses
+			mask = MBServerStatics.MASK_CORPSE;
+		else if (tpa.trackNPC()) // Track NPCs
+			mask = MBServerStatics.MASK_NPC;
+		else if (tpa.trackUndead()) // Track Undead
+			mask = MBServerStatics.MASK_MOB | MBServerStatics.MASK_UNDEAD;
+		else
+			// Track All
+			mask = MBServerStatics.MASK_MOB | MBServerStatics.MASK_NPC;
+
+		// Find characters in range
+		HashSet<AbstractWorldObject> allTargets;
+		allTargets = WorldGrid.getObjectsInRangeContains(playerCharacter.getLoc(),
+				pb.getRange(), mask);
+
+		//remove anyone who can't be tracked
+		Iterator<AbstractWorldObject> it = allTargets.iterator();
+		while (it.hasNext()) {
+			AbstractWorldObject awo = it.next();
+			if (awo == null)
+				continue;
+			else if (!awo.isAlive())
+				it.remove();
+			else if (awo.getObjectType().equals(GameObjectType.PlayerCharacter)) {
+				PlayerBonuses bonus = ((PlayerCharacter) awo).getBonuses();
+				if (bonus != null && bonus.getBool(ModType.CannotTrack, SourceType.None))
+					it.remove();
+			}
+		}
+
+		// get max charcters for window
+		int maxTargets = 20;
+		PromotionClass promo = playerCharacter.getPromotionClass();
+		if (promo != null) {
+			int tableID = promo.getObjectUUID();
+			if (tableID == 2512 || tableID == 2514 || tableID == 2515)
+				maxTargets = 40;
+		}
+
+		// create list of characters
+		HashSet<AbstractCharacter> trackChars = RangeBasedAwo.getTrackList(
+				allTargets, playerCharacter, maxTargets);
+
+		TrackWindowMsg trackWindowMsg = new TrackWindowMsg(msg);
+
+		// send track window
+		trackWindowMsg.setSource(playerCharacter);
+		trackWindowMsg.setCharacters(trackChars);
+
+		Dispatch dispatch = Dispatch.borrow(playerCharacter, trackWindowMsg);
+		DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
+
+	}
+
+	private static void sendRecyclePower(int token, ClientConnection origin) {
+		RecyclePowerMsg recyclePowerMsg = new RecyclePowerMsg(token);
+
+		Dispatch dispatch = Dispatch.borrow(origin.getPlayerCharacter(), recyclePowerMsg);
+		DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.PRIMARY);
+
+	}
+
+	public static boolean verifyInvalidRange(AbstractCharacter ac,
+			AbstractWorldObject target, float range) {
+		Vector3fImmutable sl = ac.getLoc();
+		Vector3fImmutable tl = target.getLoc();
+		 if (target.getObjectType().equals(GameObjectType.Item)) {
+
+			Item item = (Item) target;
+			AbstractGameObject owner = item.getOwner();
+
+			if (owner == null || owner.getObjectType().equals(GameObjectType.Account))
+				return true;
+
+			if (owner.getObjectType().equals(GameObjectType.PlayerCharacter) || owner.getObjectType().equals(GameObjectType.Mob)) {
+				AbstractCharacter acOwner = (AbstractCharacter) owner;
+				CharacterItemManager itemMan = acOwner.getCharItemManager();
+				if (itemMan == null)
+					return true;
+				if (itemMan.inventoryContains(item)) {
+					tl = acOwner.getLoc();
+					return !(sl.distanceSquared(tl) <= sqr(range));
+				}
+				return true;
+			}
+			return true;
+		}
+
+		range += (calcHitBox(ac) + calcHitBox(target));
+
+
+		float distanceToTarget = sl.distanceSquared(tl);//distance to center of target
+
+		return distanceToTarget > range * range;
+
+	}
+
+	public static float calcHitBox(AbstractWorldObject ac) {
+		//TODO Figure out how Str Affects HitBox
+		float hitBox = 1;
+		switch (ac.getObjectType()) {
+		case PlayerCharacter:
+			PlayerCharacter pc = (PlayerCharacter) ac;
+			if (MBServerStatics.COMBAT_TARGET_HITBOX_DEBUG)
+                Logger.info( "Hit box radius for " + pc.getFirstName() + " is " + ((int) pc.statStrBase / 200f));
+            hitBox = 2f + (int) ((PlayerCharacter) ac).statStrBase / 50f;
+			break;
+
+		case Mob:
+			Mob mob = (Mob) ac;
+			if (MBServerStatics.COMBAT_TARGET_HITBOX_DEBUG)
+				Logger.info( "Hit box radius for " + mob.getFirstName()
+				+ " is " + ((Mob) ac).getMobBase().getHitBoxRadius());
+
+			hitBox = ((Mob) ac).getMobBase().getHitBoxRadius();
+			break;
+		case Building:
+			Building building = (Building) ac;
+			if (building.getBlueprint() == null)
+				return 32;
+			hitBox = Math.max(building.getBlueprint().getBuildingGroup().getExtents().x,
+					building.getBlueprint().getBuildingGroup().getExtents().y);
+			if (MBServerStatics.COMBAT_TARGET_HITBOX_DEBUG)
+				Logger.info("Hit box radius for " + building.getName() + " is " + hitBox);
+			break;
+
+		}
+		return hitBox;
+	}
+
+	// Apply a power based on it's IDString
+	public static void applyPower(AbstractCharacter ac, AbstractWorldObject target,
+			Vector3fImmutable targetLoc, String ID, int trains, boolean fromItem) {
+		if (ac == null || target == null || !ac.isAlive())
+			return;
+		PowersBase pb = powersBaseByIDString.get(ID);
+		if (pb == null) {
+			Logger.error(
+					"applyPower(): Got NULL on powersBaseByIDString table lookup for: "
+							+ ID);
+			return;
+		}
+		applyPowerA(ac, target, targetLoc, pb, trains, fromItem);
+	}
+
+	// Apply a power based on it's Token
+	public static void applyPower(AbstractCharacter ac, AbstractWorldObject target,
+			Vector3fImmutable targetLoc, int token, int trains, boolean fromItem) {
+		if (ac == null || target == null)
+			return;
+
+		//Don't apply power if ac is dead, unless death shroud or safe mode
+		if (!ac.isAlive())
+			if (!(token == -1661758934 || token == 1672601862))
+				return;
+
+		PowersBase pb = powersBaseByToken.get(token);
+		if (pb == null) {
+			Logger.error(
+					"applyPower(): Got NULL on powersBaseByToken table lookup for: "
+							+ token);
+			return;
+		}
+		applyPowerA(ac, target, targetLoc, pb, trains, fromItem);
+	}
+
+	private static void applyPowerA(AbstractCharacter ac, AbstractWorldObject target,
+			Vector3fImmutable targetLoc, PowersBase pb, int trains,
+			boolean fromItem) {
+		int time = pb.getCastTime(trains);
+		if (!fromItem)
+			finishApplyPowerA(ac, target, targetLoc, pb, trains, false);
+		else if (time == 0)
+			finishApplyPower(ac, target, targetLoc, pb, trains, ac.getLiveCounter());
+		else {
+
+			ac.setItemCasting(true);
+			int tarType = (target == null) ? 0 : target.getObjectType().ordinal();
+			int tarID = (target == null) ? 0 : target.getObjectUUID();
+
+			// start the action animation
+			PerformActionMsg msg = new PerformActionMsg(pb.getToken(),
+					trains, ac.getObjectType().ordinal(), ac.getObjectUUID(), tarType, tarID, 0,
+					0, 0, 1, 0);
+			DispatchMessage.sendToAllInRange(target, msg);
+
+
+			ConcurrentHashMap<String, JobContainer> timers = ac.getTimers();
+
+			if (timers.containsKey(Integer.toString(pb.getToken()))) {
+				JobContainer jc = timers.get(Integer.toString(pb.getToken()));
+				if (jc != null)
+					jc.cancelJob();
+			}
+
+			//				// clear any other items being used
+			//				JobContainer jc = ac.getLastItem();
+			//				if (jc != null) {
+			//					jc.cancelJob();
+			//					ac.clearLastItem();
+			//				}
+			// run timer job to end cast
+			UseItemJob uij = new UseItemJob(ac, target, pb, trains, ac.getLiveCounter());
+			JobContainer jc = js.scheduleJob(uij, time);
+
+			// make lastItem
+			timers.put(Integer.toString(pb.getToken()), jc);
+		}
+	}
+
+	public static void finishApplyPower(AbstractCharacter ac,
+			AbstractWorldObject target, Vector3fImmutable targetLoc,
+			PowersBase pb, int trains, int liveCounter) {
+		
+		if (ac != null)
+			ac.setItemCasting(false);
+		if (ac == null || target == null || pb == null)
+			return;
+		
+		ac.clearTimer(Integer.toString(pb.getToken()));
+		if (liveCounter == ac.getLiveCounter())
+			finishApplyPowerA(ac, target, targetLoc, pb, trains, false);
+	}
+
+	public static void finishApplyPowerA(AbstractCharacter ac,
+			AbstractWorldObject target, Vector3fImmutable targetLoc,
+			PowersBase pb, int trains, boolean fromChant) {
+		// finally Apply actions
+		ArrayList<ActionsBase> actions = pb.getActions();
+		for (ActionsBase ab : actions) {
+			// get numTrains for power, skip action if invalid
+			if (trains < ab.getMinTrains() || trains > ab.getMaxTrains())
+				continue;
+			// If something blocks the action, then stop
+			if (ab.blocked(target, pb, trains))
+				// sendPowerMsg(pc, 5, msg);
+				continue;
+			// TODO handle overwrite stack order here
+			String stackType = ab.getStackType();
+			stackType = (stackType.equals("IgnoreStack")) ? Integer.toString(ab.getUUID()) : stackType;
+			if (target.getEffects().containsKey(stackType)) {
+				// remove any existing power that overrides
+				Effect ef = target.getEffects().get(stackType);
+				AbstractEffectJob effect = null;
+				if (ef != null) {
+					JobContainer jc = ef.getJobContainer();
+					if (jc != null)
+						effect = (AbstractEffectJob) jc.getJob();
+				}
+				ActionsBase overwrite = effect.getAction();
+				PowersBase pbOverwrite = effect.getPower();
+				if (pbOverwrite != null && pbOverwrite.equals(pb)
+						&& (trains >= effect.getTrains()))
+					removeEffect(target, overwrite, true, fromChant);
+				else if (ab.getStackOrder() < overwrite.getStackOrder())
+					continue; // not high enough to overwrite
+				else if (ab.getStackOrder() > overwrite.getStackOrder())
+					removeEffect(target, overwrite, true, false);
+				else if (ab.getStackOrder() == overwrite.getStackOrder())
+					if (ab.greaterThanEqual()
+							&& (trains >= effect.getTrains()))
+						removeEffect(target, overwrite, true, false);
+					else if (ab.always())
+						removeEffect(target, overwrite, true, false);
+					else if (ab.greaterThan()
+							&& (trains > effect.getTrains()))
+						removeEffect(target, overwrite, true, false);
+					else if (ab.greaterThan() && pb.getToken() == effect.getPowerToken())
+						removeEffect(target, overwrite, true, false);
+					else
+						continue; // trains not high enough to overwrite
+			}
+			if (fromChant)
+				targetLoc = Vector3fImmutable.ZERO;
+			runPowerAction(ac, target, targetLoc, ab, trains, pb);
+		}
+
+		//Handle chant
+		if (pb != null && pb.isChant())
+			for (ActionsBase ab : pb.getActions()) {
+				AbstractPowerAction pa = ab.getPowerAction();
+				if (pa != null)
+					pa.handleChant(ac, target, targetLoc, trains, ab, pb);
+			}
+
+		// for chants, only send the animation if the character is not is not moving or casting
+		boolean doAnimation = true;
+
+		if (target.getObjectType().equals(GameObjectType.PlayerCharacter)) {
+			PlayerCharacter pc = (PlayerCharacter) target;
+			if (pb != null && pb.isChant() && (pc.isMoving() || pc.isCasting()))
+				doAnimation = false;
+		}
+
+		if (pb.getToken() == 428950414)
+			doAnimation = true;
+
+		if (doAnimation) {
+			PerformActionMsg msg = new PerformActionMsg(pb.getToken(), 9999, ac
+					.getObjectType().ordinal(), ac.getObjectUUID(), target.getObjectType().ordinal(),
+					target.getObjectUUID(), 0, 0, 0, 2, 0);
+
+			DispatchMessage.sendToAllInRange(ac, msg);
+
+		}
+	}
+
+	public static void runPowerAction(AbstractCharacter source,
+			AbstractWorldObject awo, Vector3fImmutable targetLoc,
+			ActionsBase ab, int trains, PowersBase pb) {
+		AbstractPowerAction pa = ab.getPowerAction();
+		if (pa == null) {
+			Logger.error(
+					"runPowerAction(): PowerAction not found of IDString: "
+							+ ab.getEffectID());
+			return;
+		}
+		pa.startAction(source, awo, targetLoc, trains, ab, pb);
+	}
+
+	public static void runPowerAction(AbstractCharacter source,
+			AbstractWorldObject awo, Vector3fImmutable targetLoc,
+			ActionsBase ab, int trains, PowersBase pb, int duration) {
+		AbstractPowerAction pa = ab.getPowerAction();
+		if (pa == null) {
+			Logger.error(
+					"runPowerAction(): PowerAction not found of IDString: "
+							+ ab.getEffectID());
+			return;
+		}
+		pa.startAction(source, awo, targetLoc, trains, ab, pb, duration);
+	}
+
+	public static HashSet<AbstractWorldObject> getAllTargets(
+			AbstractWorldObject target, Vector3fImmutable tl,
+			PlayerCharacter pc, PowersBase pb) {
+		HashSet<AbstractWorldObject> allTargets;
+		if (pb.isAOE()) {
+			Vector3fImmutable targetLoc = null;
+			if (pb.usePointBlank()) {
+				targetLoc = pc.getLoc();
+			} else {
+				if (target != null) {
+					targetLoc = target.getLoc();
+				} else {
+					targetLoc = tl;
+					try{
+						targetLoc = targetLoc.setY(HeightMap.getWorldHeight(targetLoc)); //on ground
+					}catch (Exception e){
+						Logger.error(e);
+						targetLoc = tl;
+					}
+				
+				}
+			}
+
+			if (targetLoc.x == 0f || targetLoc.z == 0f)
+				return new HashSet<>(); // invalid loc,
+			// return
+			// nothing
+
+			//first find targets in range quickly with QTree
+			if (pb.targetPlayer() && pb.targetMob())
+				// Player and mobs
+				allTargets = WorldGrid.getObjectsInRangePartial(
+						targetLoc, pb.getRadius(), MBServerStatics.MASK_MOBILE);
+			else if (pb.targetPlayer())
+				// Player only
+				allTargets = WorldGrid.getObjectsInRangePartial(
+						targetLoc, pb.getRadius(), MBServerStatics.MASK_PLAYER);
+			else if (pb.targetMob())
+				// Mob only
+				allTargets = WorldGrid.getObjectsInRangePartial(
+						targetLoc, pb.getRadius(), MBServerStatics.MASK_MOB
+						| MBServerStatics.MASK_PET);
+			else if (pb.targetPet())
+				//Pet only
+				allTargets = WorldGrid.getObjectsInRangePartial(
+						targetLoc, pb.getRadius(), MBServerStatics.MASK_PET);
+			else if (pb.targetNecroPet())
+				allTargets = WorldGrid.getObjectsInRangePartialNecroPets(
+						targetLoc, pb.getRadius());
+			else
+				allTargets = WorldGrid.getObjectsInRangePartial(
+						targetLoc, pb.getRadius(), 0);
+
+			// cleanup self, group and nation targets if needed
+			Iterator<AbstractWorldObject> awolist = allTargets.iterator();
+			while (awolist.hasNext()) {
+				AbstractWorldObject awo = awolist.next();
+				if (awo == null) {
+					awolist.remove(); // won't hit a null
+					continue;
+				}
+
+				//see if targets are within 3D range of each other
+				Vector3fImmutable tloc = awo.getLoc();
+
+				if (tloc.distanceSquared(targetLoc) > sqr(pb.getRadius())) {
+					awolist.remove(); // too far away
+					continue;
+				}
+
+				if (pb.isCasterFriendly() && pc.equals(awo)) {
+					awolist.remove(); // won't hit self
+					continue;
+				}
+
+				if (!awo.isAlive()) {
+					awolist.remove(); // too far away
+					continue;
+				}
+
+				if (awo.getObjectType().equals(GameObjectType.PlayerCharacter)) {
+
+					PlayerCharacter pcc = (PlayerCharacter) awo;
+
+					if (pb.isGroupFriendly() && GroupManager.getGroup(pc) != null && GroupManager.getGroup(pcc) != null)
+						if (GroupManager.getGroup(pc).equals(GroupManager.getGroup(pcc))) {
+							awolist.remove(); // Won't hit group members
+							continue;
+						}
+					if (pb.isNationFriendly() && pc.getGuild() != null &&
+							pc.getGuild().getNation() != null && pcc.getGuild() != null &&
+							pc.getGuild().getNation() != null)
+						if (pc.getGuild().getNation().equals(pcc.getGuild().getNation())) {
+							awolist.remove(); // Won't hit nation members
+							continue;
+						}
+
+					// Remove players for non-friendly spells in safe zone
+					if (pb.isHarmful() && (pcc.inSafeZone() || pc.inSafeZone())) {
+						awolist.remove();
+						continue;
+					}
+				}
+			}
+			// Trim list down to max size closest targets, limited by max
+			// Player/Mob amounts
+			allTargets = RangeBasedAwo.getSortedList(allTargets, targetLoc, pb
+					.getMaxNumPlayerTargets(), pb.getMaxNumMobTargets());
+		} else if (pb.targetGroup()) {
+
+			if (GroupManager.getGroup(pc) != null) {
+				allTargets = WorldGrid.getObjectsInRangePartial(pc
+						.getLoc(), pb.getRange(), MBServerStatics.MASK_PLAYER);
+				Iterator<AbstractWorldObject> awolist = allTargets.iterator();
+				while (awolist.hasNext()) {
+
+					AbstractWorldObject awo = awolist.next();
+
+					if (!(awo.getObjectType().equals(GameObjectType.PlayerCharacter))) {
+						awolist.remove(); // remove non players if there are any
+						continue;
+					}
+					PlayerCharacter pcc = (PlayerCharacter) awo;
+
+					if (GroupManager.getGroup(pcc) == null)
+						awolist.remove(); // remove players not in a group
+					else if (!GroupManager.getGroup(pcc).equals(GroupManager.getGroup(pc)))
+						awolist.remove(); // remove if not same group
+
+				}
+			} else {
+				allTargets = new HashSet<>();
+				allTargets.add(pc); // no group, use only self
+			}
+		} else {
+			allTargets = new HashSet<>();
+			if (pb.targetSelf())
+				allTargets.add(pc);
+			else if (pb.targetFromLastTarget())
+				allTargets.add(target);
+			else if (pb.targetFromNearbyMobs())
+				allTargets.add(target); // need better way to do this later
+			else
+				// targetByName
+				allTargets.add(target); // need to get name later
+			// can't target self if caster friendly
+			if (pb.isCasterFriendly() && allTargets.contains(pc))
+				allTargets.remove(0);
+		}
+
+		Iterator<AbstractWorldObject> awolist = allTargets.iterator();
+		while (awolist.hasNext()) {
+			AbstractWorldObject awo = awolist.next();
+
+			//See if target is valid type
+			if (!validMonsterType(awo, pb)) {
+				awolist.remove();
+				continue;
+			}
+
+			if (awo != null && awo.getObjectType().equals(GameObjectType.PlayerCharacter)) {
+
+				// Remove players who are in safe mode
+				PlayerCharacter pcc = (PlayerCharacter) awo;
+				PlayerBonuses bonuses = pcc.getBonuses();
+
+				if (bonuses != null && bonuses.getBool(ModType.ImmuneToPowers, SourceType.None)) {
+					awolist.remove();
+					continue;
+				}
+
+				//remove if power is harmful and caster or target is in safe zone
+				if (pb.isHarmful() && (pcc.inSafeZone() || pc.inSafeZone())) {
+					awolist.remove();
+					continue;
+				}
+			}
+		}
+
+		// verify target has proper effects applied to receive power
+		if (pb.getTargetEffectPrereqs().size() > 0) {
+			Iterator<AbstractWorldObject> it = allTargets.iterator();
+			while (it.hasNext()) {
+				boolean passed = false;
+				AbstractWorldObject awo = it.next();
+				if (awo.getEffects() != null) {
+					for (PowerPrereq pp : pb.getTargetEffectPrereqs()) {
+						EffectsBase eb = PowersManager.getEffectByIDString(pp.getEffect());
+						if (awo.containsEffect(eb.getToken())) {
+							passed = true;
+							break;
+						}
+					}
+					if (!passed)
+						it.remove();
+				} else
+					it.remove(); //awo is missing it's effects list
+			}
+		}
+		return allTargets;
+	}
+
+	public static HashSet<AbstractWorldObject> getAllTargets(
+			AbstractWorldObject target, Vector3fImmutable tl,
+			AbstractCharacter caster, PowersBase pb) {
+		HashSet<AbstractWorldObject> allTargets;
+		if (pb.isAOE()) {
+			Vector3fImmutable targetLoc = tl;
+			if (pb.usePointBlank()) {
+				targetLoc = caster.getLoc();
+			} else {
+				if (target != null) {
+					targetLoc = target.getLoc();
+				} else {
+					targetLoc = tl;
+					try{
+						targetLoc = targetLoc.setY(HeightMap.getWorldHeight(targetLoc)); //on ground
+					}catch (Exception e){
+						Logger.error(e);
+					}
+					
+				}
+			}
+
+			if (targetLoc.x == 0f || targetLoc.z == 0f)
+				return new HashSet<>(); // invalid loc,
+	
+			//first find targets in range quickly with QTree
+			if (pb.targetPlayer() && pb.targetMob())
+				// Player and mobs
+				allTargets = WorldGrid.getObjectsInRangePartial(
+						targetLoc, pb.getRadius(), MBServerStatics.MASK_MOBILE);
+			else if (pb.targetPlayer())
+				// Player only
+				allTargets = WorldGrid.getObjectsInRangePartial(
+						targetLoc, pb.getRadius(), MBServerStatics.MASK_PLAYER);
+			else if (pb.targetMob())
+				// Mob only
+				allTargets = WorldGrid.getObjectsInRangePartial(
+						targetLoc, pb.getRadius(), MBServerStatics.MASK_MOB
+						| MBServerStatics.MASK_PET);
+			else if (pb.targetPet())
+				//Pet only
+				allTargets = WorldGrid.getObjectsInRangePartial(
+						targetLoc, pb.getRadius(), MBServerStatics.MASK_PET);
+			else if (pb.targetNecroPet())
+				allTargets = WorldGrid.getObjectsInRangePartialNecroPets(
+						targetLoc, pb.getRadius());
+			else
+				allTargets = WorldGrid.getObjectsInRangePartial(
+						targetLoc, pb.getRadius(), 0);
+
+			// cleanup self, group and nation targets if needed
+			Iterator<AbstractWorldObject> awolist = allTargets.iterator();
+			while (awolist.hasNext()) {
+				AbstractWorldObject awo = awolist.next();
+				if (awo == null) {
+					awolist.remove(); // won't hit a null
+					continue;
+				}
+
+				//see if targets are within 3D range of each other
+				Vector3fImmutable tloc = awo.getLoc();
+
+				if (tloc.distanceSquared(targetLoc) > sqr(pb.getRadius())) {
+					awolist.remove(); // too far away
+					continue;
+				}
+
+				if (pb.isCasterFriendly() && caster.equals(awo)) {
+					awolist.remove(); // won't hit self
+					continue;
+				}
+
+				if (awo.getObjectType() == GameObjectType.Mob) {
+					awolist.remove(); // Won't hit other mobs.
+					continue;
+				}
+			}
+			// Trim list down to max size closest targets, limited by max
+			// Player/Mob amounts
+			allTargets = RangeBasedAwo.getSortedList(allTargets, targetLoc, pb
+					.getMaxNumPlayerTargets(), pb.getMaxNumMobTargets());
+		} else if (pb.targetGroup()) {
+			allTargets = new HashSet<>();
+			allTargets.add(caster); // no group, use only self
+		} else {
+			allTargets = new HashSet<>();
+			if (pb.targetSelf())
+				allTargets.add(caster);
+			else if (pb.targetFromLastTarget())
+				allTargets.add(target);
+			else if (pb.targetFromNearbyMobs())
+				allTargets.add(target); // need better way to do this later
+			else
+				// targetByName
+				allTargets.add(target); // need to get name later
+			// can't target self if caster friendly
+			if (pb.isCasterFriendly() && allTargets.contains(caster))
+				allTargets.remove(caster);
+		}
+
+		Iterator<AbstractWorldObject> awolist = allTargets.iterator();
+		while (awolist.hasNext()) {
+			AbstractWorldObject awo = awolist.next();
+
+			//See if target is valid type
+			if (!validMonsterType(awo, pb)) {
+				awolist.remove();
+				continue;
+			}
+
+			if (awo != null && awo.getObjectType().equals(GameObjectType.PlayerCharacter)) {
+				// Remove players who are in safe mode
+				PlayerCharacter pcc = (PlayerCharacter) awo;
+				PlayerBonuses bonuses = pcc.getBonuses();
+				if (bonuses != null && bonuses.getBool(ModType.ImmuneToPowers, SourceType.None)) {
+					awolist.remove();
+					continue;
+				}
+			}
+		}
+
+		// verify target has proper effects applied to receive power
+		if (pb.getTargetEffectPrereqs().size() > 0) {
+			Iterator<AbstractWorldObject> it = allTargets.iterator();
+			while (it.hasNext()) {
+				boolean passed = false;
+				AbstractWorldObject awo = it.next();
+				if (awo.getEffects() != null) {
+					for (PowerPrereq pp : pb.getTargetEffectPrereqs()) {
+						EffectsBase eb = PowersManager.getEffectByIDString(pp.getEffect());
+						if (awo.containsEffect(eb.getToken())) {
+							passed = true;
+							break;
+						}
+					}
+					if (!passed)
+						it.remove();
+				} else
+					it.remove(); //awo is missing it's effects list
+			}
+		}
+		return allTargets;
+	}
+
+	// removes an effect before time is finished
+	public static void removeEffect(AbstractWorldObject awo, ActionsBase toRemove,
+			boolean overwrite, boolean fromChant) {
+		if (toRemove == null)
+			return;
+
+		String stackType = toRemove.getStackType();
+		stackType = (stackType.equals("IgnoreStack")) ? Integer
+				.toString(toRemove.getUUID()) : stackType;
+				if (fromChant) {
+					Effect eff = awo.getEffects().get(stackType);
+					if (eff != null)
+						eff.cancelJob(true);
+				} else
+					awo.cancelEffect(stackType, overwrite);
+	}
+
+	// removes an effect when timer finishes
+	public static void finishEffectTime(AbstractWorldObject source,
+			AbstractWorldObject awo, ActionsBase toRemove, int trains) {
+		if (awo == null || toRemove == null)
+			return;
+
+		// remove effect from player
+		String stackType = toRemove.getStackType();
+		if (stackType.equals("IgnoreStack"))
+			stackType = Integer.toString(toRemove.getUUID());
+		awo.endEffect(stackType);
+	}
+
+	// removes an effect when timer is canceled
+	public static void cancelEffectTime(AbstractWorldObject source,
+			AbstractWorldObject awo, PowersBase pb, EffectsBase eb,
+			ActionsBase toRemove, int trains, AbstractEffectJob efj) {
+		if (awo == null || pb == null || eb == null || toRemove == null)
+			return;
+		eb.endEffect(source, awo, trains, pb, efj);
+	}
+
+	// called when cooldown ends letting player cast next spell
+	public static void finishCooldownTime(PerformActionMsg msg, PlayerCharacter pc) {
+		// clear spell so player can cast again
+		// if (pc != null)
+		// pc.clearLastPower();
+	}
+
+	// called when recycle time ends letting player cast spell again
+	public static void finishRecycleTime(PerformActionMsg msg, PlayerCharacter pc,
+			boolean canceled) {
+		finishRecycleTime(msg.getPowerUsedID(), pc, canceled);
+	}
+
+	public static void finishRecycleTime(int token, PlayerCharacter pc,
+			boolean canceled) {
+		if (pc == null)
+			return;
+
+		ConcurrentHashMap<Integer, JobContainer> recycleTimers = pc
+				.getRecycleTimers();
+		// clear recycle time
+		if (recycleTimers != null)
+			if (recycleTimers.containsKey(token)) {
+				if (canceled) {
+					JobContainer jc = recycleTimers.get(token);
+					if (jc != null)
+						jc.cancelJob();
+				}
+				recycleTimers.remove(token);
+			}
+
+		// send recycle message to unlock power
+
+		RecyclePowerMsg recyclePowerMsg = new RecyclePowerMsg(token);
+		Dispatch dispatch = Dispatch.borrow(pc, recyclePowerMsg);
+		DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.PRIMARY);
+
+	}
+
+	// Called when a fail condition is met by player
+	// such as moving, taking damage, ect.
+
+	public static void cancelUseLastPower(PlayerCharacter pc) {
+
+		if (pc == null)
+			return;
+
+		// set player is not casting for regens
+		if (pc.isCasting()){
+			pc.update();
+		}
+		pc.setIsCasting(false);
+
+		UsePowerJob lastPower = null;
+		JobContainer jc = pc.getLastPower();
+
+		if (jc != null)
+			lastPower = ((UsePowerJob) jc.getJob());
+
+		if (lastPower == null)
+			return;
+
+		// clear recycle timer
+		int token = lastPower.getToken();
+
+		if (pc.getRecycleTimers().contains(token))
+			finishRecycleTime(token, pc, true);
+
+		//			pc.getRecycleTimers().remove(token);
+		// Cancel power
+		js.cancelScheduledJob(lastPower);
+
+		// clear last power
+		pc.clearLastPower();
+
+	}
+
+	private static AbstractWorldObject getTarget(PerformActionMsg msg) {
+
+		int type = msg.getTargetType();
+		int UUID = msg.getTargetID();
+
+		if (type == -1 || type == 0 || UUID == -1 || UUID == 0)
+			return null;
+
+		return (AbstractWorldObject) DbManager.getObject(GameObjectType.values()[type], UUID);
+	}
+
+	public static boolean testAttack(PlayerCharacter pc, AbstractWorldObject awo,
+			PowersBase pb, PerformActionMsg msg) {
+		// Get defense for target
+		float atr = CharacterSkill.getATR(pc, pb.getSkillName());
+		float defense;
+
+		if (AbstractWorldObject.IsAbstractCharacter(awo)) {
+			AbstractCharacter tar = (AbstractCharacter) awo;
+			defense = tar.getDefenseRating();
+		} else
+			defense = 0f;
+		// Get hit chance
+
+		if (pc.getDebug(16)) {
+			String smsg = "ATR: " + atr + ", Defense: " + defense;
+			ChatManager.chatSystemInfo(pc, smsg);
+		}
+
+		int chance;
+
+		if (atr > defense || defense == 0)
+			chance = 94;
+		else {
+			float dif = atr / defense;
+			if (dif <= 0.8f)
+				chance = 4;
+			else
+				chance = ((int) (450 * (dif - 0.8f)) + 4);
+		}
+
+		// calculate hit/miss
+		int roll = ThreadLocalRandom.current().nextInt(100);
+
+		boolean disable = true;
+		if (roll < chance) {
+			// Hit, check if dodge kicked in
+			if (awo instanceof AbstractCharacter) {
+				AbstractCharacter tarAc = (AbstractCharacter) awo;
+				// Handle Dodge passive
+				if (testPassive(pc, tarAc, "Dodge")) {
+					// Dodge fired, send dodge message
+					PerformActionMsg dodgeMsg = new PerformActionMsg(msg);
+					dodgeMsg.setTargetType(awo.getObjectType().ordinal());
+					dodgeMsg.setTargetID(awo.getObjectUUID());
+					sendPowerMsg(pc, 4, dodgeMsg);
+					return true;
+				}
+			}
+			return false;
+		} else {
+			// Miss. Send miss message
+			PerformActionMsg missMsg = new PerformActionMsg(msg);
+
+			missMsg.setTargetType(awo.getObjectType().ordinal());
+			missMsg.setTargetID(awo.getObjectUUID());
+			sendPowerMsg(pc, 3, missMsg);
+			return true;
+		}
+	}
+
+	public static boolean testAttack(Mob caster, AbstractWorldObject awo,
+			PowersBase pb, PerformActionMsg msg) {
+		// Get defense for target
+		float atr = 2000;
+		float defense;
+
+		if (AbstractWorldObject.IsAbstractCharacter(awo)) {
+			AbstractCharacter tar = (AbstractCharacter) awo;
+			defense = tar.getDefenseRating();
+		} else
+			defense = 0f;
+		// Get hit chance
+
+		int chance;
+
+		if (atr > defense || defense == 0)
+			chance = 94;
+		else {
+			float dif = atr / defense;
+			if (dif <= 0.8f)
+				chance = 4;
+			else
+				chance = ((int) (450 * (dif - 0.8f)) + 4);
+		}
+
+		// calculate hit/miss
+		int roll = ThreadLocalRandom.current().nextInt(100);
+
+		if (roll < chance) {
+			// Hit, check if dodge kicked in
+			if (AbstractWorldObject.IsAbstractCharacter(awo)) {
+				AbstractCharacter tarAc = (AbstractCharacter) awo;
+				// Handle Dodge passive
+				return testPassive(caster, tarAc, "Dodge");
+			}
+			return false;
+		} else
+			return true;
+	}
+
+	public static void sendPowerMsg(PlayerCharacter playerCharacter, int type, PerformActionMsg msg) {
+
+		if (playerCharacter == null)
+			return;
+
+		msg.setUnknown05(type);
+
+		switch (type) {
+		case 3:
+		case 4:
+			msg.setUnknown04(2);
+			DispatchMessage.dispatchMsgToInterestArea(playerCharacter, msg, DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, true, false);
+			break;
+		default:
+			msg.setUnknown04(1);
+			Dispatch dispatch = Dispatch.borrow(playerCharacter, msg);
+			DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.PRIMARY);
+		}
+	}
+
+	public static void sendEffectMsg(PlayerCharacter pc, int type, ActionsBase ab, PowersBase pb) {
+
+		if (pc == null)
+			return;
+
+		try {
+
+			EffectsBase eb = PowersManager.effectsBaseByIDString.get(ab.getEffectID());
+
+			if (eb == null)
+				return;
+
+			ApplyEffectMsg aem = new ApplyEffectMsg(pc, pc, 0, eb.getToken(), 9, pb.getToken(), pb.getName());
+			aem.setUnknown03(type);
+			DispatchMessage.dispatchMsgToInterestArea(pc, aem, DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, true, false);
+
+
+		} catch (Exception e) {
+			Logger.error( e.getMessage());
+		}
+
+	}
+
+	public static void sendEffectMsg(PlayerCharacter pc, int type, EffectsBase eb) {
+
+		if (pc == null)
+			return;
+		try {
+
+			if (eb == null)
+				return;
+			ApplyEffectMsg aem = new ApplyEffectMsg(pc, pc, 0, eb.getToken(), 0, eb.getToken(), "");
+			aem.setUnknown03(type);
+			aem.setUnknown05(1);
+
+			DispatchMessage.dispatchMsgToInterestArea(pc, aem, DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, true, false);
+
+
+		} catch (Exception e) {
+			Logger.error( e.getMessage());
+		}
+
+	}
+
+	public static void sendMobPowerMsg(Mob mob, int type, PerformActionMsg msg) {
+
+		msg.setUnknown05(type);
+		switch (type) {
+		case 3:
+		case 4:
+			DispatchMessage.sendToAllInRange(mob, msg);
+
+		}
+	}
+
+	private static boolean testPassive(AbstractCharacter source,
+			AbstractCharacter target, String type) {
+
+		float chance = target.getPassiveChance(type, source.getLevel(), false);
+
+		if (chance == 0f)
+			return false;
+
+		// max 75% chance of passive to fire
+		if (chance > 75f)
+			chance = 75f;
+
+		int roll = ThreadLocalRandom.current().nextInt(100);
+		// Passive fired
+		// TODO send message
+		// Passive did not fire
+		return roll < chance;
+	}
+
+	private static boolean validateTarget(AbstractWorldObject target,
+			PlayerCharacter pc, PowersBase pb) {
+
+		//group target. uses pbaoe rules
+		if (pb.targetGroup())
+			return true;
+
+		// target is player
+		else if ((target.getObjectTypeMask() & MBServerStatics.MASK_PLAYER) != 0) {
+			if (pb.targetPlayer())
+				if (pb.isGroupOnly()) { //single target group only power
+					PlayerCharacter trg = (PlayerCharacter) target;
+
+					if (GroupManager.getGroup(trg) != null && GroupManager.getGroup(pc) != null)
+						if (GroupManager.getGroup(trg).getObjectUUID() == GroupManager.getGroup(pc).getObjectUUID())
+							return true; // both in same group, good to go
+					return trg != null && pc.getObjectUUID() == trg.getObjectUUID();
+				} else
+					return true; // can target player, good to go.
+			else if (target.getObjectUUID() == pc.getObjectUUID() && pb.targetSelf())
+				return true; // can target self, good to go
+			else if (pb.targetCorpse()) {
+				//target is dead player
+				PlayerCharacter trg = (PlayerCharacter) target;
+				return !trg.isAlive();
+			} else {
+				PlayerCharacter trg = (PlayerCharacter) target;
+
+				if (pb.targetGroup())
+					if (GroupManager.getGroup(trg) != null && GroupManager.getGroup(pc) != null)
+						if (GroupManager.getGroup(trg).getObjectUUID() == GroupManager.getGroup(pc)
+						.getObjectUUID())
+							return true; // both in same group, good to go
+				if (pb.targetGuildLeader())
+					if (pc.getGuild() != null)
+						if (pc.getGuild().getGuildLeaderUUID() == trg.getObjectUUID())
+							return true; // can hit guild leader, good to go
+			}
+			String outmsg = "Invalid Target";
+			ChatManager.chatSystemInfo(pc, outmsg);
+			return false; // can't target player, stop here
+		} // target is mob
+		else if ((target.getObjectTypeMask() & MBServerStatics.MASK_MOB) != 0)
+			return pb.targetMob();
+
+		// target is pet
+		else if ((target.getObjectTypeMask() & MBServerStatics.MASK_PET) != 0)
+			return pb.targetPet();
+
+		// target is Building
+		else if ((target.getObjectTypeMask() & MBServerStatics.MASK_BUILDING) != 0)
+			return pb.targetBuilding();
+
+		else if (target.getObjectType().equals(GameObjectType.Item)) {
+			Item item = (Item) target;
+			if (pb.targetItem())
+				return true;
+			// TODO add these checks later
+			else if (pb.targetArmor() && item.getItemBase().getType().equals(ItemType.ARMOR))
+				return true;
+			else if (pb.targetJewelry() && item.getItemBase().getType().equals(ItemType.JEWELRY))
+				return true;
+			else return pb.targetWeapon() && item.getItemBase().getType().equals(ItemType.WEAPON);
+		} // How did we get here? all valid targets have been covered
+		else
+			return false;
+	}
+
+	/*
+	 * Cancel spell upon actions
+	 */
+	public static void cancelOnAttack(AbstractCharacter ac) {
+		ac.cancelTimer("Stuck");
+	}
+
+	public static void cancelOnAttackSwing(AbstractCharacter ac) {
+	}
+
+	public static void cancelOnCast(AbstractCharacter ac) {
+
+	}
+
+	public static void cancelOnSpell(AbstractCharacter ac) {
+
+		PowersBase power = getLastPower(ac);
+
+		if (power != null && power.cancelOnCastSpell())
+			cancelPower(ac, false);
+		ac.cancelLastChant();
+	}
+
+	public static void cancelOnEquipChange(AbstractCharacter ac) {
+
+	}
+
+	public static void cancelOnLogout(AbstractCharacter ac) {
+
+	}
+
+	public static void cancelOnMove(AbstractCharacter ac) {
+
+		PowersBase power = getLastPower(ac);
+
+		if (power != null && !power.canCastWhileMoving())
+			cancelPower(ac, false);
+
+		//cancel items
+		cancelItems(ac, true, false);
+		ac.cancelTimer("Stuck");
+	}
+	
+	
+
+	public static void cancelOnNewCharm(AbstractCharacter ac) {
+
+	}
+
+	public static void cancelOnSit(AbstractCharacter ac) {
+		cancelPower(ac, false); // Always cancel casts on sit
+	}
+
+	public static void cancelOnTakeDamage(AbstractCharacter ac) {
+
+		PowersBase power = getLastPower(ac);
+
+		if (power != null && power.cancelOnTakeDamage())
+			cancelPower(ac, true);
+		cancelItems(ac, false, true);
+		ac.cancelTimer("Stuck");
+	}
+
+	public static void cancelOnTerritoryClaim(AbstractCharacter ac) {
+
+	}
+
+	public static void cancelOnUnEquip(AbstractCharacter ac) {
+
+	}
+
+	public static void cancelOnStun(AbstractCharacter ac) {
+
+	}
+
+	private static PowersBase getLastPower(AbstractCharacter ac) {
+		if (ac == null)
+			return null;
+
+		JobContainer jc = ac.getLastPower();
+
+		if (jc == null)
+			return null;
+
+		AbstractJob aj = jc.getJob();
+
+		if (aj == null)
+			return null;
+
+		if (aj instanceof UsePowerJob) {
+			UsePowerJob upj = (UsePowerJob) aj;
+			return upj.getPowersBase();
+		}
+		return null;
+	}
+
+	private static PowersBase getLastItem(AbstractCharacter ac) {
+
+		if (ac == null)
+			return null;
+
+		JobContainer jc = ac.getLastItem();
+
+		if (jc == null)
+			return null;
+
+		AbstractJob aj = jc.getJob();
+
+		if (aj == null)
+			return null;
+
+		if (aj instanceof UseItemJob) {
+			UseItemJob uij = (UseItemJob) aj;
+			return uij.getPowersBase();
+		}
+		return null;
+	}
+
+	//cancels last casted power
+	private static void cancelPower(AbstractCharacter ac, boolean cancelCastAnimation) {
+
+		if (ac == null)
+			return;
+
+		JobContainer jc = ac.getLastPower();
+
+		if (jc == null)
+			return;
+
+		AbstractJob aj = jc.getJob();
+
+		if (aj == null)
+			return;
+
+		if (aj instanceof AbstractScheduleJob)
+			((AbstractScheduleJob) aj).cancelJob();
+
+		ac.clearLastPower();
+
+		//clear cast animation for everyone in view range
+		if (aj instanceof UsePowerJob && cancelCastAnimation) {
+
+			PerformActionMsg pam = ((UsePowerJob) aj).getMsg();
+
+			if (pam != null) {
+				pam.setNumTrains(9999);
+				pam.setUnknown04(2);
+				DispatchMessage.sendToAllInRange(ac, pam);
+			}
+		}
+	}
+
+	public static PerformActionMsg createPowerMsg(PowersBase pb, int trains, AbstractCharacter source, AbstractCharacter target) {
+		return new PerformActionMsg(pb.getToken(), trains, source.getObjectType().ordinal(), source.getObjectUUID(), target.getObjectType().ordinal(), target.getObjectUUID(), target.getLoc().x, target.getLoc().y, target.getLoc().z, 0, 0);
+
+	}
+
+	//cancels any casts from using an item
+
+	private static void cancelItems(AbstractCharacter ac, boolean cancelOnMove, boolean cancelOnTakeDamage) {
+		JobContainer jc;
+		AbstractJob aj;
+		ConcurrentHashMap<String, JobContainer> timers;
+		UseItemJob uij;
+		PowersBase pb;
+		AbstractWorldObject target;
+
+		if (ac == null)
+			return;
+
+		timers = ac.getTimers();
+
+		if (timers == null)
+			return;
+
+		for (String name : timers.keySet()) {
+
+			jc = timers.get(name);
+
+			if (jc == null)
+				continue;
+
+			aj = jc.getJob();
+
+			if (aj != null && aj instanceof UseItemJob) {
+				uij = (UseItemJob) aj;
+				pb = uij.getPowersBase();
+
+				if (pb == null)
+					continue;
+
+				if (!pb.canCastWhileMoving() && cancelOnMove) {
+					uij.cancelJob();
+					timers.remove(name);
+					continue;
+				}
+
+				if ((pb.cancelOnTakeDamage() == false) &&
+						(cancelOnTakeDamage == false))
+					continue;
+
+				uij.cancelJob();
+				timers.remove(name);
+
+				//clear cast animation for everyone in view range
+				target = uij.getTarget();
+
+				if (target != null) {
+					PerformActionMsg pam = new PerformActionMsg(pb.getToken(), 9999, ac
+							.getObjectType().ordinal(), ac.getObjectUUID(), target.getObjectType().ordinal(),
+							target.getObjectUUID(), 0, 0, 0, 2, 0);
+					DispatchMessage.sendToAllInRange(ac, pam);
+
+				}
+			}
+		}
+	}
+}
+
+
+
diff --git a/src/engine/gameManager/SessionManager.java b/src/engine/gameManager/SessionManager.java
new file mode 100644
index 00000000..10682986
--- /dev/null
+++ b/src/engine/gameManager/SessionManager.java
@@ -0,0 +1,289 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+package engine.gameManager;
+
+import engine.net.client.ClientConnection;
+import engine.objects.Account;
+import engine.objects.Guild;
+import engine.objects.PlayerCharacter;
+import engine.server.MBServerStatics;
+import engine.session.CSSession;
+import engine.session.Session;
+import engine.session.SessionID;
+import engine.util.ByteUtils;
+import org.pmw.tinylog.Logger;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.concurrent.ConcurrentHashMap;
+
+public enum SessionManager {
+
+	SESSIONMANAGER;
+
+	// TODO add session activity timestamping & timeout monitors
+
+	private static ConcurrentHashMap<SessionID, Session> sessionIDtoSession = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_HIGH);
+	private static ConcurrentHashMap<PlayerCharacter, Session> pcToSession = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_HIGH);
+	private static ConcurrentHashMap<Account, Session> accountToSession = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_HIGH);
+	private static ConcurrentHashMap<ClientConnection, Session> connToSession = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_HIGH);
+	public static int _maxPopulation = 0;
+
+	// 0 = login server
+	// 1 = gateway server
+	// 2 = all other servers
+	private static int crossServerBehavior = 2;
+
+	public static Session getNewSession(SessionID sesID, Account a, ClientConnection c) {
+		Session ses = new Session(sesID, a, c);
+
+		SessionManager.sessionIDtoSession.put(sesID, ses);
+		SessionManager.accountToSession.put(a, ses);
+		SessionManager.connToSession.put(c, ses);
+
+		if (crossServerBehavior == 0)
+			if (!CSSession.addCrossServerSession(ByteUtils.byteArrayToSafeStringHex(c.getSecretKeyBytes()), a, c.getSocketChannel()
+					.socket().getInetAddress(), c.machineID))
+				Logger.warn("Failed to create cross server session: " + a.getUname());
+
+		return ses;
+	}
+
+	public static Session getNewSession(Account a, ClientConnection c) {
+		SessionID sesID = c.getSessionID();
+		return SessionManager.getNewSession(sesID, a, c);
+	}
+
+
+	public static void cSessionCleanup(String key) {
+		if (!CSSession.deleteCrossServerSession(key))
+			Logger.warn(
+				"Failed to remove cross server session for key: " + key);
+	}
+
+	public static void remSession(Session s) {
+
+		if (s == null) {
+			return;
+		}
+
+		SessionManager.remSessionID(s);
+		SessionManager.remAccount(s);
+		SessionManager.remClientConnection(s);
+		SessionManager.remPlayerCharacter(s);
+
+		//TODO LATER fix
+		s.setAccount(null);
+		s.setConn(null);
+		s.setPlayerCharacter(null);
+		s.setSessionID(null);
+	}
+
+	/*
+	 * Get Sessions
+	 */
+	public static Session getSession(SessionID id) {
+		return SessionManager.sessionIDtoSession.get(id);
+	}
+
+	public static Session getSession(PlayerCharacter pc) {
+		return SessionManager.pcToSession.get(pc);
+	}
+
+	public  static Session getSession(Account a) {
+		return SessionManager.accountToSession.get(a);
+	}
+
+	public  static Session getSession(ClientConnection cc) {
+		return SessionManager.connToSession.get(cc);
+	}
+
+	/*
+	 * Get Connections
+	 */
+	public static  ClientConnection getClientConnection(SessionID id) {
+		Session s = SessionManager.getSession(id);
+		return (s == null) ? null : s.getConn();
+	}
+
+	public static  ClientConnection getClientConnection(PlayerCharacter pc) {
+		Session s = SessionManager.getSession(pc);
+		return (s == null) ? null : s.getConn();
+	}
+
+	public static  ClientConnection getClientConnection(Account a) {
+		Session s = SessionManager.getSession(a);
+		return (s == null) ? null : s.getConn();
+	}
+
+	/*
+	 * Get PlayerCharacter
+	 */
+	public static PlayerCharacter getPlayerCharacter(SessionID id) {
+		Session s = SessionManager.getSession(id);
+		return (s == null) ? null : s.getPlayerCharacter();
+	}
+
+	public static PlayerCharacter getPlayerCharacter(ClientConnection conn) {
+		Session s = SessionManager.getSession(conn);
+		return (s == null) ? null : s.getPlayerCharacter();
+	}
+
+	public static PlayerCharacter getPlayerCharacter(Account a) {
+		Session s = SessionManager.getSession(a);
+		return (s == null) ? null : s.getPlayerCharacter();
+	}
+
+	/*
+	 * Get Account
+	 */
+	public static Account getAccount(SessionID id) {
+		Session s = SessionManager.getSession(id);
+		return (s == null) ? null : s.getAccount();
+	}
+
+	public static Account getAccount(ClientConnection conn) {
+		Session s = SessionManager.getSession(conn);
+		return (s == null) ? null : s.getAccount();
+	}
+
+	public static Account getAccount(PlayerCharacter pc) {
+		Session s = SessionManager.getSession(pc);
+		return (s == null) ? null : s.getAccount();
+	}
+
+	public static void setPlayerCharacter(Session s, PlayerCharacter pc) {
+		SessionManager.pcToSession.put(pc, s);
+		s.setPlayerCharacter(pc);
+                
+		// Update max player
+		SessionManager._maxPopulation = Math.max(_maxPopulation, SessionManager.pcToSession.size());
+                
+	}
+
+	public static void remPlayerCharacter(Session s) {
+		if (s.getPlayerCharacter() != null) {
+			SessionManager.pcToSession.remove(s.getPlayerCharacter());
+			s.setPlayerCharacter(null);
+		}
+	}
+
+	protected static void remAccount(Session s) {
+		if (s.getAccount() != null) {
+			SessionManager.accountToSession.remove(s.getAccount());
+			s.setAccount(null);
+		}
+	}
+
+	protected static void remSessionID(Session s) {
+
+		if (s.getSessionID() != null) {
+			SessionManager.sessionIDtoSession.remove(s.getSessionID());
+			s.setSessionID(null);
+		}
+	}
+
+	protected static void remClientConnection(Session s) {
+		if (s.getConn() != null) {
+			SessionManager.connToSession.remove(s.getConn());
+			s.setConn(null);
+		}
+	}
+
+
+
+	/*
+	 * Utils
+	 */
+
+	public static void setCrossServerBehavior(int type) {
+		crossServerBehavior = type;
+	}
+
+	/**
+	 *
+	 * @return a new HashSet<ClientConnection> object so the caller cannot
+	 *         modify the internal Set
+	 */
+	public static Collection<ClientConnection> getAllActiveClientConnections() {
+			return SessionManager.connToSession.keySet();
+	}
+
+	/**
+	 *
+	 * @return a new HashSet<PlayerCharacter> object so the caller cannot modify
+	 *         the internal Set
+	 */
+	public static Collection<PlayerCharacter> getAllActivePlayerCharacters() {
+	
+			return SessionManager.pcToSession.keySet();
+	}
+
+	public static Collection<PlayerCharacter> getAllActivePlayers() {
+		
+			return SessionManager.pcToSession.keySet();
+	}
+
+	public static int getActivePlayerCharacterCount() {
+
+			return SessionManager.pcToSession.keySet().size();
+	}
+
+	public static ArrayList<PlayerCharacter> getActivePCsInGuildID(int id) {
+		ArrayList<PlayerCharacter> pcs = new ArrayList<>();
+
+		for (PlayerCharacter pc : SessionManager.getAllActivePlayerCharacters()) {
+			Guild g = pc.getGuild();
+			if (g != null && g.getObjectUUID() == id) {
+				pcs.add(pc);
+			}
+		}
+
+		return pcs;
+	}
+
+	public static PlayerCharacter getPlayerCharacterByLowerCaseName(String name) {
+
+		String queryName = name.toLowerCase();
+
+		for (PlayerCharacter playerCharacter : SessionManager.getAllActivePlayerCharacters()) {
+
+			if ((playerCharacter.getFirstName().toLowerCase()).equals(queryName)) {
+				return playerCharacter;
+			}
+		}
+		return null;
+	}
+
+	public static PlayerCharacter getPlayerCharacterByID(int UUID) {
+
+		for (PlayerCharacter playerCharacter : SessionManager.getAllActivePlayerCharacters()) {
+
+			if (playerCharacter.getObjectUUID() == UUID) {
+				return playerCharacter;
+			}
+		}
+		return null;
+	}
+
+	public static Collection<Account> getAllActiveAccounts() {
+			return SessionManager.accountToSession.keySet();
+	}
+
+	public static Account getAccountByID(int UUID) {
+
+		for (Account acc :  SessionManager.getAllActiveAccounts()) {
+
+			if (acc.getObjectUUID() == UUID)
+				return acc;
+
+		}
+		return null;
+	}
+}
diff --git a/src/engine/gameManager/SimulationManager.java b/src/engine/gameManager/SimulationManager.java
new file mode 100644
index 00000000..6905c099
--- /dev/null
+++ b/src/engine/gameManager/SimulationManager.java
@@ -0,0 +1,216 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+package engine.gameManager;
+
+import engine.Enum;
+import engine.Enum.GameObjectType;
+import engine.objects.AbstractGameObject;
+import engine.objects.City;
+import engine.objects.PlayerCharacter;
+import engine.objects.Runegate;
+import org.pmw.tinylog.Logger;
+
+import java.util.Collection;
+
+/*
+ * This class contains all methods necessary to drive periodic
+ * updates of the game simulation from the main _exec loop.
+ */
+public enum SimulationManager {
+
+	SERVERHEARTBEAT;
+
+	private static SimulationManager instance = null;
+
+	private static final long CITY_PULSE = 2000;
+	private static final long RUNEGATE_PULSE = 3000;
+	private static final long UPDATE_PULSE = 1000;
+	private static final long FlIGHT_PULSE = 100;
+
+	private long _cityPulseTime = System.currentTimeMillis() + CITY_PULSE;
+	private long _runegatePulseTime = System.currentTimeMillis()
+			+ RUNEGATE_PULSE;
+	private long _updatePulseTime = System.currentTimeMillis() + UPDATE_PULSE;
+	private long _flightPulseTime = System.currentTimeMillis() + FlIGHT_PULSE;
+	
+	public static long HeartbeatDelta = 0;
+	public static long currentHeartBeatDelta = 0;
+
+	private SimulationManager() {
+
+		// don't allow instantiation.
+	}
+
+    public static String getPopulationString() {
+        String outString;
+        String newLine = System.getProperty("line.separator");
+        outString = "[LUA_POPULATION()]" + newLine;
+        outString += DbManager.CSSessionQueries.GET_POPULATION_STRING();
+        return outString;
+    }
+
+    /*
+	 * Update the simulation. *** Important: Whatever you do in here, do it damn
+	 * quick!
+	 */
+	public void tick() {
+
+		/*
+		 * As we're on the main thread we must be sure to catch any possible
+		 * errors.
+		 *
+		 * IF something does occur, disable that particular heartbeat. Better
+		 * runegates stop working than the game itself!
+		 */
+		
+		long start = System.currentTimeMillis();
+
+		try {
+			if ((_flightPulseTime != 0)
+					&& (System.currentTimeMillis() > _flightPulseTime))
+				pulseFlight();
+		} catch (Exception e) {
+			Logger.error(
+					"Fatal error in City Pulse: DISABLED. Error Message : "
+							+ e.getMessage());
+		}
+		try {
+
+			if ((_updatePulseTime != 0)
+					&& (System.currentTimeMillis() > _updatePulseTime))
+				pulseUpdate();
+		} catch (Exception e) {
+			Logger.error(
+					"Fatal error in Update Pulse: DISABLED");
+			//  _runegatePulseTime = 0;
+		}
+
+		try {
+			if ((_runegatePulseTime != 0)
+					&& (System.currentTimeMillis() > _runegatePulseTime))
+				pulseRunegates();
+		} catch (Exception e) {
+			Logger.error(
+					"Fatal error in Runegate Pulse: DISABLED");
+			_runegatePulseTime = 0;
+		}
+
+		try {
+			if ((_cityPulseTime != 0)
+					&& (System.currentTimeMillis() > _cityPulseTime))
+				pulseCities();
+		} catch (Exception e) {
+			Logger.error(
+					"Fatal error in City Pulse: DISABLED. Error Message : "
+							+ e.getMessage());
+			e.printStackTrace();
+	
+		}
+		
+		long end = System.currentTimeMillis();
+		
+		long delta = end - start;
+		
+		if (delta > SimulationManager.HeartbeatDelta)
+			SimulationManager.HeartbeatDelta = delta;
+		
+		SimulationManager.currentHeartBeatDelta = delta;
+		
+
+
+	}
+
+	/*
+	 * Mainline simulation update method: handles movement and regen for all
+	 * player characters
+	 */
+
+	private void pulseUpdate() {
+
+		Collection<AbstractGameObject> playerList;
+
+		playerList = DbManager.getList(GameObjectType.PlayerCharacter);
+
+		// Call update() on each player in game
+
+		if (playerList == null)
+			return;
+
+		for (AbstractGameObject ago : playerList) {
+			PlayerCharacter player = (PlayerCharacter)ago;
+
+			if (player == null)
+				continue;
+			player.update();
+		}
+
+        _updatePulseTime = System.currentTimeMillis() + 500;
+	}
+	
+	private void pulseFlight() {
+
+		Collection<AbstractGameObject> playerList;
+
+		playerList = DbManager.getList(GameObjectType.PlayerCharacter);
+
+		// Call update() on each player in game
+
+		if (playerList == null)
+			return;
+
+		for (AbstractGameObject ago : playerList) {
+			PlayerCharacter player = (PlayerCharacter)ago;
+
+			if (player == null)
+				continue;
+			
+			
+			player.updateFlight();
+		}
+
+        _flightPulseTime = System.currentTimeMillis() + FlIGHT_PULSE;
+	}
+
+	private void pulseCities() {
+
+		City city;
+
+		// *** Refactor: Need a list cached somewhere as it doesn't change very
+		// often at all.  Have a cityListIsDirty boolean that gets set if it
+		// needs an update.  Will speed up this method a great deal.
+
+		Collection<AbstractGameObject> cityList = DbManager.getList(Enum.GameObjectType.City);
+
+		if (cityList == null) {
+			Logger.info( "City List null");
+			return;
+		}
+
+		for (AbstractGameObject cityObject : cityList) {
+			city = (City) cityObject;
+				city.onEnter();
+		}
+
+		_cityPulseTime = System.currentTimeMillis() + CITY_PULSE;
+	}
+
+	/*
+	 * Method runs proximity collision detection for all active portals on the
+	 * game's Runegates
+	 */
+	private void pulseRunegates() {
+
+		for (Runegate runegate : Runegate.getRunegates()) {
+			runegate.collidePortals();
+		}
+
+		_runegatePulseTime = System.currentTimeMillis() + RUNEGATE_PULSE;
+
+	}
+}
diff --git a/src/engine/gameManager/TradeManager.java b/src/engine/gameManager/TradeManager.java
new file mode 100644
index 00000000..b202007d
--- /dev/null
+++ b/src/engine/gameManager/TradeManager.java
@@ -0,0 +1,163 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+package engine.gameManager;
+
+import engine.Enum;
+import engine.net.Dispatch;
+import engine.net.DispatchMessage;
+import engine.net.client.ClientConnection;
+import engine.net.client.msg.*;
+import engine.objects.CharacterItemManager;
+import engine.objects.PlayerCharacter;
+import org.pmw.tinylog.Logger;
+
+public enum TradeManager {
+
+    TRADEMANAGER;
+
+    public static void tradeRequest(TradeRequestMsg msg, ClientConnection origin) {
+
+        PlayerCharacter source = origin.getPlayerCharacter();
+      
+        if (source == null)
+        	return;
+        
+        source.getCharItemManager().tradeRequest(msg);
+
+    }
+
+
+    public static void acceptTradeRequest(AcceptTradeRequestMsg msg, ClientConnection origin) {
+
+        PlayerCharacter source = origin.getPlayerCharacter();
+     
+        if (source == null)
+        	return;
+        
+        try {
+			source.getCharItemManager().acceptTradeRequest(msg);
+		} catch (Exception e) {
+			Logger.error(e);
+			// TODO Auto-generated catch block
+			}
+        
+    }
+
+    public static void rejectTradeRequest(RejectTradeRequestMsg msg, ClientConnection origin) {
+        // TODO Do nothing? If so, delete this method & case above
+    }
+
+    public static void addItemToTradeWindow(AddItemToTradeWindowMsg msg, ClientConnection origin) {
+    	
+    	
+        PlayerCharacter source = origin.getPlayerCharacter();
+        if (source == null || !source.isAlive())
+            return;
+        try{
+            source.getCharItemManager().addItemToTradeWindow(msg);
+
+        }catch(Exception e){
+        	Logger.error(e);
+        }
+
+    }
+
+    public static void addGoldToTradeWindow(AddGoldToTradeWindowMsg msg, ClientConnection origin) {
+
+        PlayerCharacter source = origin.getPlayerCharacter();
+
+        if (source == null || !source.isAlive())
+            return;
+        
+        
+
+        CharacterItemManager sourceItemMan = source.getCharItemManager();
+
+        if (sourceItemMan == null)
+            return;
+        
+        try{
+            sourceItemMan.addGoldToTradeWindow(msg);
+        }catch(Exception e){
+        	Logger.error(e);
+        }
+    }
+
+    public static void commitToTrade(CommitToTradeMsg msg, ClientConnection origin) {
+
+        PlayerCharacter source = origin.getPlayerCharacter();
+
+        if (source == null || !source.isAlive())
+            return;
+
+        CharacterItemManager sourceItemMan = source.getCharItemManager();
+
+        if (sourceItemMan == null)
+            return;
+
+        try {
+			sourceItemMan.commitToTrade(msg);
+		} catch (Exception e) {
+			// TODO Auto-generated catch block
+		Logger.error(e);
+		}
+    }
+
+    public static void uncommitToTrade(UncommitToTradeMsg msg, ClientConnection origin) {
+
+        PlayerCharacter source = origin.getPlayerCharacter();
+
+        if (source == null || !source.isAlive())
+            return;
+
+        CharacterItemManager sourceItemMan = source.getCharItemManager();
+
+        if (sourceItemMan == null)
+            return;
+        
+        try {
+			sourceItemMan.uncommitToTrade(msg);
+		} catch (Exception e) {
+			// TODO Auto-generated catch block
+			Logger.error(e);
+		}
+
+    }
+
+    
+
+    public static void closeTradeWindow(CloseTradeWindowMsg msg, ClientConnection origin) {
+
+        PlayerCharacter source = origin.getPlayerCharacter();
+
+        if (source == null)
+            return;
+
+        CharacterItemManager sourceItemMan = source.getCharItemManager();
+
+        if (sourceItemMan == null)
+            return;
+
+        try {
+        	sourceItemMan.closeTradeWindow(msg, true);
+        } catch (Exception e) {
+        	// TODO Auto-generated catch block
+        	Logger.error(e);
+        }
+    }
+
+    public static void invalidTradeRequest(InvalidTradeRequestMsg msg) {
+        PlayerCharacter requester = PlayerCharacter.getFromCache(msg.getRequesterID());
+        Dispatch dispatch;
+
+        dispatch = Dispatch.borrow(requester, msg);
+        DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+
+    }
+}
\ No newline at end of file
diff --git a/src/engine/gameManager/ZoneManager.java b/src/engine/gameManager/ZoneManager.java
new file mode 100644
index 00000000..ef8c1c61
--- /dev/null
+++ b/src/engine/gameManager/ZoneManager.java
@@ -0,0 +1,437 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+package engine.gameManager;
+
+import engine.Enum;
+import engine.math.Bounds;
+import engine.math.Vector2f;
+import engine.math.Vector3f;
+import engine.math.Vector3fImmutable;
+import engine.objects.Building;
+import engine.objects.City;
+import engine.objects.Zone;
+import engine.server.MBServerStatics;
+import engine.server.world.WorldServer;
+import org.pmw.tinylog.Logger;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ThreadLocalRandom;
+
+/*
+ * Class contains methods and structures which
+ * track in-game Zones
+ */
+public enum ZoneManager {
+
+	ZONEMANAGER;
+
+	/* Instance variables */
+	private static Zone seaFloor = null;
+	private static Zone hotzone = null;
+	private static  ConcurrentHashMap<Integer, Zone> zonesByID = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD);
+	private static  ConcurrentHashMap<Integer, Zone> zonesByUUID = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD);
+	private static  ConcurrentHashMap<String, Zone> zonesByName = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD);
+	private static  Set<Zone> macroZones = Collections.newSetFromMap(new ConcurrentHashMap<>());
+	private static  Set<Zone> npcCityZones = Collections.newSetFromMap(new ConcurrentHashMap<>());
+	private static Set<Zone> playerCityZones = Collections.newSetFromMap(new ConcurrentHashMap<>());
+
+	// Find all zones coordinates fit into, starting with Sea Floor
+
+	public static ArrayList<Zone> getAllZonesIn(final Vector3fImmutable loc) {
+
+		ArrayList<Zone> allIn = new ArrayList<>();
+		Zone zone;
+
+		zone = ZoneManager.findSmallestZone(loc);
+
+		if (zone != null) {
+			allIn.add(zone);
+			while (zone.getParent() != null) {
+				zone = zone.getParent();
+				allIn.add(zone);
+			}
+		}
+		return allIn;
+	}
+
+	// Find smallest zone coordinates fit into.
+
+	public static final Zone findSmallestZone(final Vector3fImmutable loc) {
+
+		Zone zone = ZoneManager.seaFloor;
+
+		if (zone == null)
+			return null;
+
+		boolean childFound = true;
+
+		while (childFound) {
+
+			childFound = false;
+
+			ArrayList<Zone> nodes = zone.getNodes();
+
+			// Logger.info("soze", "" + nodes.size());
+			if (nodes != null)
+				for (Zone child : nodes) {
+
+					if (Bounds.collide(loc, child.getBounds()) == true) {
+						zone = child;
+						childFound = true;
+						break;
+					}
+				}
+		}
+		return zone;
+	}
+
+	public static void addZone(final int zoneID, final Zone zone) {
+
+		ZoneManager.zonesByID.put(zoneID, zone);
+
+		if (zone != null)
+			ZoneManager.zonesByUUID.put(zone.getObjectUUID(), zone);
+
+		ZoneManager.zonesByName.put(zone.getName().toLowerCase(), zone);
+
+	}
+
+	public static  Zone getZoneByUUID(final int zoneUUID) {
+		return ZoneManager.zonesByUUID.get(zoneUUID);
+	}
+
+	public static  Zone getZoneByZoneID(final int zoneID) {
+
+		return ZoneManager.zonesByID.get(zoneID);
+	}
+
+	public static final Collection<Zone> getAllZones() {
+		return ZoneManager.zonesByUUID.values();
+	}
+
+	public static final Zone getHotZone() {
+		return ZoneManager.hotzone;
+	}
+
+	public static final void setHotZone(final Zone zone) {
+		if (!zone.isMacroZone())
+			return;
+		ZoneManager.hotzone = zone;
+	}
+
+	public static boolean inHotZone(final Vector3fImmutable loc) {
+
+		if (ZoneManager.hotzone == null)
+			return false;
+
+		return (Bounds.collide(loc, ZoneManager.hotzone.getBounds()) == true);
+	}
+
+	public static void setSeaFloor(final Zone value) {
+		ZoneManager.seaFloor = value;
+	}
+
+	public static Zone getSeaFloor() {
+		return ZoneManager.seaFloor;
+	}
+
+	public static final void populateWorldZones(final Zone zone) {
+
+		int loadNum = zone.getLoadNum();
+
+		// Zones are added to separate
+		// collections for quick access
+		// based upon their type.
+
+		if (zone.isMacroZone()) {
+			addMacroZone(zone);
+			return;
+		}
+
+
+		if (zone.isPlayerCity()) {
+			addPlayerCityZone(zone);
+			return;
+		}
+
+		if (zone.isNPCCity())
+			addNPCCityZone(zone);
+
+	}
+
+	private static void addMacroZone(final Zone zone) {
+		ZoneManager.macroZones.add(zone);
+	}
+
+	private static void addNPCCityZone(final Zone zone) {
+		zone.setNPCCity(true);
+		ZoneManager.npcCityZones.add(zone);
+	}
+
+	public static final void addPlayerCityZone(final Zone zone) {
+		zone.setPlayerCity(true);
+		ZoneManager.playerCityZones.add(zone);
+	}
+
+	public static final void generateAndSetRandomHotzone() {
+
+		Zone hotzone;
+		ArrayList<Integer> zoneArray = new ArrayList<>();
+
+		if (ZoneManager.macroZones.isEmpty())
+			return;
+
+		for (Zone zone : ZoneManager.macroZones) {
+
+			if (validHotZone(zone))
+				zoneArray.add(zone.getObjectUUID());
+
+		}
+
+		int entryIndex = ThreadLocalRandom.current().nextInt(zoneArray.size());
+
+		hotzone = ZoneManager.getZoneByUUID(zoneArray.get(entryIndex));
+
+
+		if (hotzone == null){
+			Logger.error( "Hotzone is null");
+			return;
+		}
+
+
+		ZoneManager.setHotZone(hotzone);
+		WorldServer.setLastHZChange(System.currentTimeMillis());
+
+	}
+
+	public static final boolean validHotZone(Zone zone) {
+
+		if (zone.getSafeZone() == (byte) 1)
+			return false; // no safe zone hotzones// if (this.hotzone == null)
+
+		if (zone.getNodes().isEmpty())
+			return false;
+
+		if (zone.equals(ZoneManager.seaFloor))
+			return false;
+
+		// return false; //first time setting, accept it
+		// if (this.hotzone.getUUID() == zone.getUUID())
+		// return true; //no same hotzone
+
+		if (ZoneManager.hotzone != null)
+			return ZoneManager.hotzone.getObjectUUID() != zone.getObjectUUID();
+
+		return true;
+	}
+
+	/**
+	 * Gets a MacroZone by name.
+	 *
+	 * @param inputName
+	 *            MacroZone name to search for
+	 * @return Zone of the MacroZone, or Null
+	 */
+
+	public static Zone findMacroZoneByName(String inputName) {
+		synchronized (ZoneManager.macroZones) {
+			for (Zone zone : ZoneManager.macroZones) {
+				String zoneName = zone.getName();
+				if (zoneName.equalsIgnoreCase(inputName))
+					return zone;
+			}
+		}
+		return null;
+	}
+
+	// Converts world coordinates to coordinates local to a given zone.
+
+	public static Vector3fImmutable worldToLocal(Vector3fImmutable worldVector,
+			Zone serverZone) {
+
+		Vector3fImmutable localCoords;
+
+		localCoords = new Vector3fImmutable(worldVector.x - serverZone.absX,
+				worldVector.y - serverZone.absY, worldVector.z
+				- serverZone.absZ);
+
+		return localCoords;
+	}
+
+	public static Vector2f worldToZoneSpace(Vector3fImmutable worldVector,
+			Zone serverZone) {
+
+		Vector2f localCoords;
+		Vector2f zoneOrigin;
+
+		// Top left corner of zone is calculated in world space by the center and it's extents.
+
+		zoneOrigin = new Vector2f(serverZone.getLoc().x, serverZone.getLoc().z);
+		zoneOrigin = zoneOrigin.subtract(new Vector2f(serverZone.getBounds().getHalfExtents().x, serverZone.getBounds().getHalfExtents().y));
+
+		// Local coordinate in world space translated to an offset from the calculated zone origin.
+
+		localCoords = new Vector2f(worldVector.x, worldVector.z);
+		localCoords = localCoords.subtract(zoneOrigin);
+
+		localCoords.setY((serverZone.getBounds().getHalfExtents().y * 2) - localCoords.y);
+
+
+
+
+		// TODO : Make sure this value does not go outside the zone's bounds.
+
+		return localCoords;
+	}
+
+	// Converts local zone coordinates to world coordinates
+
+	public static Vector3fImmutable localToWorld(Vector3fImmutable worldVector,
+			Zone serverZone) {
+
+		Vector3fImmutable worldCoords;
+
+		worldCoords = new Vector3fImmutable(worldVector.x + serverZone.absX,
+				worldVector.y + serverZone.absY, worldVector.z
+				+ serverZone.absZ);
+
+		return worldCoords;
+	}
+
+
+	/**
+	 * Converts from local (relative to this building) to world.
+	 *
+	 * @param localPos position in local reference (relative to this building)
+	 * @return position relative to world
+	 */
+
+	public static Vector3fImmutable convertLocalToWorld(Building building, Vector3fImmutable localPos) {
+
+		// convert from SB rotation value to radians
+		
+		
+		if (building.getBounds().getQuaternion() == null)
+			return building.getLoc();
+		Vector3fImmutable rotatedLocal = Vector3fImmutable.rotateAroundPoint(Vector3fImmutable.ZERO, localPos, building.getBounds().getQuaternion());
+		// handle building rotation
+		// handle building translation
+
+		return building.getLoc().add(rotatedLocal.x, rotatedLocal.y,rotatedLocal.z);
+	}
+	
+	
+	//used for regions, Building bounds not set yet.
+	public static Vector3f convertLocalToWorld(Building building, Vector3f localPos, Bounds bounds) {
+
+		// convert from SB rotation value to radians
+		
+		
+		Vector3f rotatedLocal = Vector3f.rotateAroundPoint(Vector3f.ZERO, localPos, bounds.getQuaternion());
+		// handle building rotation
+		// handle building translation
+
+		return new Vector3f(building.getLoc().add(rotatedLocal.x, rotatedLocal.y,rotatedLocal.z));
+	}
+
+	public static Vector3fImmutable convertWorldToLocal(Building building, Vector3fImmutable WorldPos) {
+		Vector3fImmutable convertLoc = Vector3fImmutable.rotateAroundPoint(building.getLoc(),WorldPos,-building.getBounds().getQuaternion().angleY);
+	
+		
+		convertLoc = convertLoc.subtract(building.getLoc());
+
+		// convert from SB rotation value to radians
+		
+		return convertLoc;
+
+	}
+
+	public static Vector3fImmutable convertNPCLoc(Building building, Vector3fImmutable npcLoc) {
+
+		return Vector3fImmutable.rotateAroundPoint(Vector3fImmutable.ZERO, npcLoc, -building.getBounds().getQuaternion().angleY);
+
+	}
+
+	 // Method returns a city if the given location is within
+	// a city siege radius.
+
+	public static City getCityAtLocation(Vector3fImmutable worldLoc) {
+
+		Zone currentZone;
+		ArrayList<Zone> zoneList;
+		City city;
+
+		currentZone = ZoneManager.findSmallestZone(worldLoc);
+
+		if (currentZone.isPlayerCity())
+			return City.getCity(currentZone.getPlayerCityUUID());
+
+		// Not currently on a city grid.  Test nearby cities
+		// to see if we are on one of their seige bounds.
+
+		zoneList = currentZone.getNodes();
+
+		for (Zone zone : zoneList) {
+
+			if (zone == currentZone)
+				continue;
+
+			if (zone.isPlayerCity() == false)
+				continue;
+
+			city = City.getCity(zone.getPlayerCityUUID());
+
+			if (worldLoc.isInsideCircle(city.getLoc(), Enum.CityBoundsType.SIEGE.extents))
+				return city;
+		}
+
+		return null;
+	}
+
+	/* Method is called when creating a new player city to
+	 * validate that the new zone does not overlap any other
+	 * zone that might currently exist
+	 */
+
+	public static boolean validTreePlacementLoc(Zone currentZone, float positionX, float positionZ) {
+
+		// Member Variable declaration
+
+		ArrayList<Zone> zoneList;
+		boolean validLocation = true;
+		Bounds treeBounds;
+		
+		if (currentZone.isContininent() == false)
+			return false;
+		
+		
+		treeBounds = Bounds.borrow();
+		treeBounds.setBounds(new Vector2f(positionX, positionZ), new Vector2f(Enum.CityBoundsType.SIEGE.extents, Enum.CityBoundsType.SIEGE.extents), 0.0f);
+
+		zoneList = currentZone.getNodes();
+
+	
+		
+		for (Zone zone : zoneList) {
+
+			if (zone.isContininent())
+				continue;
+
+			if (Bounds.collide(treeBounds, zone.getBounds(), 0.0f))
+				validLocation = false;
+		}
+
+		treeBounds.release();
+		return validLocation;
+    }
+}
diff --git a/src/engine/job/AbstractJob.java b/src/engine/job/AbstractJob.java
new file mode 100644
index 00000000..72ff2c58
--- /dev/null
+++ b/src/engine/job/AbstractJob.java
@@ -0,0 +1,151 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.job;
+
+import org.pmw.tinylog.Logger;
+
+import java.util.UUID;
+import java.util.concurrent.atomic.AtomicReference;
+
+
+/**
+ * Provides mandatory base implementation for all 'Job's'.
+ * 
+ * @author 
+ */
+public abstract class AbstractJob implements Runnable {
+
+	public enum JobRunStatus {
+		CREATED, RUNNING, FINISHED;
+	};
+
+	public enum JobCompletionStatus {
+		NOTCOMPLETEDYET, SUCCESS, SUCCESSWITHERRORS, FAIL;
+	};
+
+	/**
+	 * Keep fields private. All access through getters n setters for Thread
+	 * Safety.
+	 */
+	private JobRunStatus runStatus;
+	private JobCompletionStatus completeStatus;
+	private UUID uuid = null;
+
+	private final AtomicReference<String> workerID;
+	private static final String DEFAULT_WORKERID = "UNPROCESSED_JOB";
+
+	private long submitTime = 0L;
+	private long startTime = 0L;
+	private long stopTime = 0L;
+
+	public AbstractJob() {
+			//Tests against DEFAULT_WORKERID to ensure single execution
+			this.workerID = new AtomicReference<>(DEFAULT_WORKERID);
+			this.runStatus = JobRunStatus.RUNNING;
+			this.completeStatus = JobCompletionStatus.NOTCOMPLETEDYET;
+			this.uuid = UUID.randomUUID();
+	}
+
+	@Override
+	public void run() {
+
+		if(workerID.get().equals(DEFAULT_WORKERID)) {
+			Logger.warn("FIX ME! Job ran through 'run()' directly. Use executeJob(String) instead.");
+		}
+		
+		this.markStartRunTime();
+
+		this.setRunStatus(JobRunStatus.RUNNING);
+		try {
+			this.doJob();
+		} catch (Exception e) {
+			Logger.error(e);
+		}
+		
+		this.setRunStatus(JobRunStatus.FINISHED);
+
+		this.markStopRunTime();
+	}
+
+	protected abstract void doJob();
+
+	public void executeJob(String threadName) {
+		this.workerID.set(threadName);
+		this.run();
+	}
+		
+	protected void setRunStatus(JobRunStatus status) {
+		synchronized (this.runStatus) {
+			this.runStatus = status;
+		}
+	}
+
+	public JobRunStatus getRunStatus() {
+		synchronized (this.runStatus) {
+			return runStatus;
+		}
+	}
+
+	protected void setCompletionStatus(JobCompletionStatus status) {
+		synchronized (this.completeStatus) {
+			this.completeStatus = status;
+		}
+	}
+
+	public JobCompletionStatus getCompletionStatus() {
+		synchronized (this.completeStatus) {
+			return completeStatus;
+		}
+	}
+
+	protected void setJobId(UUID id) {
+		synchronized (this.uuid) {
+			this.uuid = id;
+		}
+	}
+
+	public UUID getJobId() {
+		synchronized (this.uuid) {
+			return uuid;
+		}
+	}
+	
+	public String getWorkerID() {
+		return workerID.get();
+	}
+
+	/*
+	 * Time markers
+	 */
+
+	protected void markSubmitTime() {
+		this.submitTime = System.currentTimeMillis()-2;
+	}
+
+	protected void markStartRunTime() {
+		this.startTime = System.currentTimeMillis()-1;
+	}
+
+	protected void markStopRunTime() {
+		this.stopTime = System.currentTimeMillis();
+	}
+
+	public final long getSubmitTime() {
+		return submitTime;
+	}
+
+	public final long getStartTime() {
+		return startTime;
+	}
+
+	public final long getStopTime() {
+		return stopTime;
+	}
+}
diff --git a/src/engine/job/AbstractJobStatistics.java b/src/engine/job/AbstractJobStatistics.java
new file mode 100644
index 00000000..be5a6e4a
--- /dev/null
+++ b/src/engine/job/AbstractJobStatistics.java
@@ -0,0 +1,118 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.job;
+
+public abstract class AbstractJobStatistics {
+
+	private String objectName;
+	private long totalServiceTime = 0L;
+	private long totalQueueTime = 0L;
+	private long executions = 0L;
+	private long maxServiceTime = 0L;
+	private long minServiceTime = 0L;
+	private long minQueueTime = 0L;
+	private long maxQueueTime = 0L;
+	
+	
+	public AbstractJobStatistics() {
+		this.objectName = "Unknown";
+	}
+	
+	public AbstractJobStatistics(String objectName) {
+		this.objectName = objectName;
+	}
+	
+	public void setObjectName (String objectName) {
+		this.objectName = objectName;
+	}
+	
+		public String getObjectName () {
+		return this.objectName;
+	}
+	
+	public long getExecutions() {
+		return this.executions;
+	}
+	
+	public long getTotalServiceTime() { 
+		return this.totalServiceTime;
+	}
+	
+	public long getTotalQueueTime() { 
+		return this.totalQueueTime;
+	}
+	
+	public long getAvgQueueTime() {
+		if (this.executions > 0L && this.totalQueueTime > 0L)
+			return this.totalQueueTime / this.executions;
+		else
+			return 0L;
+	}
+	
+	public long getAvgServiceTime() {
+		if (this.executions > 0L && this.totalServiceTime > 0L)
+			return this.totalServiceTime / this.executions;
+		else
+			return 0L;
+	}
+	
+	public long getMinServiceTime() {
+		return this.minServiceTime;
+	}
+	
+	public long getMinQueueTime() {
+		return this.minQueueTime;
+	}
+	
+	public long getMaxServiceTime() {
+		return this.maxServiceTime;
+	}
+	
+	public long getMaxQueueTime() {
+		return this.maxQueueTime;
+	}
+	
+	public void incrExecutions() {
+		this.executions++;
+	}
+	
+	public void addServiceTime(long svcTime) {
+		this.totalServiceTime += svcTime;
+		this.incrExecutions();
+		if (svcTime > this.maxServiceTime)
+			this.maxServiceTime = svcTime;
+		if (svcTime < this.minServiceTime || this.minServiceTime == 0L)
+			this.minServiceTime = svcTime;
+		
+	}
+	
+	public void addQueueTime(long queueTime) {
+		this.totalQueueTime += queueTime;
+		this.incrExecutions();
+		if (queueTime > this.maxQueueTime)
+			this.maxQueueTime = queueTime;
+		if (queueTime < this.minQueueTime || this.minQueueTime == 0L)
+			this.minQueueTime = queueTime;
+		
+	}
+	
+	public String asString() {
+        return this.objectName + " execs=" + this.executions + " avg_svc_ms=" + this.getAvgServiceTime() +
+			" min_svc_ms=" + this.minServiceTime + " max_svc_ms=" + this.maxServiceTime +
+			" avg_q_ms=" + this.getAvgQueueTime() + " min_q_ms=" + this.minQueueTime +
+			" max_q_ms=" + this.maxQueueTime;
+	}
+	
+	public String asChatMsg() {
+        return this.objectName + "=>" + this.executions + ',' + this.getAvgServiceTime() + '/' + this.minServiceTime +
+                '/' + this.maxServiceTime + "  "+ this.getAvgQueueTime() + '/' +
+                this.minQueueTime + '/' + this.maxQueueTime + '\n';
+	}
+}
diff --git a/src/engine/job/AbstractScheduleJob.java b/src/engine/job/AbstractScheduleJob.java
new file mode 100644
index 00000000..c33ae9ae
--- /dev/null
+++ b/src/engine/job/AbstractScheduleJob.java
@@ -0,0 +1,28 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+ package engine.job;
+
+
+public abstract class AbstractScheduleJob extends AbstractJob {
+
+	public AbstractScheduleJob() {
+		super();
+	}
+
+	@Override
+	protected abstract void doJob();
+
+	public void cancelJob() {
+		JobScheduler.getInstance().cancelScheduledJob(this);
+		_cancelJob();
+	}
+
+	protected abstract void _cancelJob();
+}
diff --git a/src/engine/job/ClassJobStatistics.java b/src/engine/job/ClassJobStatistics.java
new file mode 100644
index 00000000..be283ab5
--- /dev/null
+++ b/src/engine/job/ClassJobStatistics.java
@@ -0,0 +1,19 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.job;
+
+public class ClassJobStatistics extends AbstractJobStatistics {
+
+	public ClassJobStatistics (String className) {
+		super(className);
+	}
+
+	
+}
diff --git a/src/engine/job/JobContainer.java b/src/engine/job/JobContainer.java
new file mode 100644
index 00000000..56048b5c
--- /dev/null
+++ b/src/engine/job/JobContainer.java
@@ -0,0 +1,84 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.job;
+
+
+public class JobContainer implements Comparable<JobContainer> {
+
+	final AbstractJob job;
+	final long timeOfExecution;
+	final boolean noTimer;
+
+	JobContainer(AbstractJob job, long timeOfExecution) {
+		if (job == null) {
+			throw new IllegalArgumentException("No 'null' jobs allowed.");
+		}
+		this.job = job;
+		this.timeOfExecution = timeOfExecution;
+		this.noTimer = false;
+	}
+
+	public JobContainer(AbstractJob job) {
+		if (job == null) {
+			throw new IllegalArgumentException("No 'null' jobs allowed.");
+		}
+		this.job = job;
+		this.timeOfExecution = Long.MAX_VALUE;
+		this.noTimer = true;
+	}
+
+	public AbstractJob getJob() {
+		return job;
+	}
+
+	public boolean noTimer() {
+		return noTimer;
+	}
+
+	public long timeOfExection() {
+		return this.timeOfExecution;
+	}
+
+	public int timeToExecutionLeft() {
+		if (JobScheduler.getInstance().isAlive()) {
+			int timeLeft = (int) (timeOfExecution - System.currentTimeMillis());
+			if (timeLeft < 0)
+				timeLeft = 0;
+			return timeLeft;
+		} else
+			return (int) (timeOfExecution - JobScheduler.getInstance().getTimeOfKill());
+	}
+
+	@Override
+	public int compareTo(JobContainer compared) {
+		if (timeOfExecution < compared.timeOfExecution) {
+			return -1;
+		}
+		if (timeOfExecution > compared.timeOfExecution) {
+			return 1;
+		}
+		return 0;
+	}
+
+	@Override
+	public boolean equals(Object obj) {
+		return job.equals(((JobContainer) obj).job);
+	}
+
+	@Override
+	public int hashCode() {
+		return job.hashCode();
+	}
+
+	public void cancelJob() {
+		if (job != null && job instanceof AbstractScheduleJob)
+			((AbstractScheduleJob)job).cancelJob();
+	}
+}
diff --git a/src/engine/job/JobManager.java b/src/engine/job/JobManager.java
new file mode 100644
index 00000000..36612f7f
--- /dev/null
+++ b/src/engine/job/JobManager.java
@@ -0,0 +1,232 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.job;
+
+import engine.core.ControlledRunnable;
+import engine.server.MBServerStatics;
+import engine.util.ThreadUtils;
+import org.pmw.tinylog.Logger;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.concurrent.ConcurrentHashMap;
+
+
+/**
+ * Note to DEVs. When attempting to log something in this class, use logDIRECT
+ * only.
+ */
+public class JobManager extends ControlledRunnable {
+
+	/*
+	 * Singleton implementation.
+	 */
+	private static volatile JobManager INSTANCE;
+	public static JobManager getInstance() {
+		if (JobManager.INSTANCE == null) {
+			synchronized (JobManager.class) {
+				if (JobManager.INSTANCE == null) {
+					JobManager.INSTANCE = new JobManager();
+					JobManager.INSTANCE.startup();
+				}
+			}
+		}
+		return JobManager.INSTANCE;
+	}
+
+	/*
+	 * Class implementation
+	 */
+
+	private final ArrayList<JobPool> jobPoolList = new ArrayList<>();
+	private final ConcurrentHashMap<String, JobPool> jobQueueMapping= new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_HIGH);
+	
+	private boolean shutdownNowFlag = false;
+
+	private JobManager() {
+		super("JobManager");
+		
+		// create the initial job pools with the correct sizes 
+		// based on the number of array elements in the initial_jo_workers
+		// definition in Statisc
+
+		if (MBServerStatics.INITIAL_JOBPOOL_WORKERS != null && MBServerStatics.INITIAL_JOBPOOL_WORKERS.length >0 ) {
+			for (int i=0; i<MBServerStatics.INITIAL_JOBPOOL_WORKERS.length; i++) {
+				JobPool jp = new JobPool(i,MBServerStatics.INITIAL_JOBPOOL_WORKERS[i]);
+				this.jobPoolList.add(jp);
+				Logger.info( "Adding JobPool_" + jp.getJobPoolID() + " with " + MBServerStatics.INITIAL_JOBPOOL_WORKERS[i] + " workers");
+			}
+		} else {
+		// not defined or empty in statics so just create 1 JobPool with 25 workers
+		System.out.println("Creating 1 pool called 0");
+		JobPool jp = new JobPool(0,25);
+		this.jobPoolList.add(jp);
+			Logger.info( "Adding JobPool_" + jp.getJobPoolID() + " with 25 workers");
+		}
+
+		JobScheduler.getInstance();
+		
+		// if you want any jobs to default onto a given queue put it here
+		// otherwise everything goes on the P1 queue by default
+
+	}
+	
+		/**
+	 * Submit a job to be processed by the JobManager. There is no guarantee
+	 * that the job will be executed immediately.
+	 *
+	 * @param aj
+	 *            AbstractJob to be submitted.
+	 * @return boolean value indicating whether the job was submitted or not.
+	 */
+	public boolean submitJob(AbstractJob aj) {
+
+		if (jobQueueMapping.containsKey(aj.getClass().getSimpleName())) {
+			return this.jobQueueMapping.get(aj.getClass().getSimpleName()).submitJob(aj);
+		} else {
+			return this.jobPoolList.get(0).submitJob(aj);
+		}
+	}
+
+		
+	private void auditAllWorkers() {
+		
+		for (JobPool jp : this.jobPoolList) {
+			jp.auditWorkers();
+		}
+	}
+
+	@Override
+	protected boolean _Run() {
+		// This thread is to be used to JM internal monitoring.
+
+		// Startup workers:
+		this.auditAllWorkers();
+		ThreadUtils.sleep(MBServerStatics.JOBMANAGER_INTERNAL_MONITORING_INTERVAL_MS * 2);
+
+		this.runStatus = true;
+
+		// Monitoring loop
+		while (this.runCmd) {
+			this.auditAllWorkers();
+			ThreadUtils.sleep(MBServerStatics.JOBMANAGER_INTERNAL_MONITORING_INTERVAL_MS);
+		}
+
+		// No new submissions
+		for (JobPool jp : this.jobPoolList){
+			jp.setBlockNewSubmissions(true);
+		}
+
+		if (this.shutdownNowFlag == false) {
+			// shutdown each pool
+			for (JobPool jp : this.jobPoolList) {
+				jp.shutdown();
+			}
+		} else {
+			// Emergency Stop each pool
+			for (JobPool jp : this.jobPoolList) {
+				jp.emergencyStop();
+			}
+		}
+		this.runStatus = false;
+		Logger.info("JM Shutdown");
+		return true;
+	}
+
+	@Override
+	protected boolean _postRun() {
+		return true;
+	}
+
+	@Override
+	protected boolean _preRun() {
+		return true;
+	}
+
+	@Override
+	protected void _shutdown() {
+		Logger.info("JM Shutdown Requested");
+		JobScheduler.getInstance().shutdown();
+	}
+
+	@Override
+	protected void _startup() {
+		Logger.info("JM Starting up... ");
+	}
+
+	public void shutdownNow() {
+		this.shutdownNowFlag = true;
+		this.shutdown();
+	}
+	
+	public String moveJob(String simpleClassName, String jobPoolID) {
+		// moves a job to a different queue
+		
+		try {
+			// parse string into an int
+			Integer i = Integer.parseInt(jobPoolID);
+			for (JobPool jp : this.jobPoolList) {
+				if (jp.getJobPoolID() == i) {
+					// move the job to the new queue
+					this.jobQueueMapping.put(simpleClassName, jp);
+					return simpleClassName + " moved to JobPool ID " + jp.getJobPoolID(); 
+				}
+			}
+
+		} catch (NumberFormatException e) {
+			return "jobPoolID is not a valid number";
+		}
+		
+		//Verify jobpool ID
+		return "Invalid parameters <simpleClassName - case sensitive> <jobPoolID> required";
+	}
+
+	public String resetJobs() {
+		// moves all jobs to the P1 queue
+		this.jobQueueMapping.clear();
+		return "All Jobs reset onto P1 queue";
+	}
+
+	public String showJobs() {
+		String out = "";
+		Iterator<String> jmi = this.jobQueueMapping.keySet().iterator();
+		
+		while (jmi.hasNext()) {
+			String jmiKey = jmi.next();
+			out += jmiKey + ' ' + this.jobQueueMapping.get(jmiKey).getJobPoolID() + '\n';
+		}
+		return out;
+	}
+
+	public ArrayList<JobPool> getJobPoolList() {
+		
+		return this.jobPoolList;
+	}
+
+	public String modifyJobPoolWorkers(String jobPoolID, String maxWorkers) {
+		
+		try {
+			// parse string into an int
+			Integer jid = Integer.parseInt(jobPoolID);
+			Integer mw = Integer.parseInt(maxWorkers);
+			for (JobPool jp : this.jobPoolList) {
+				if (jp.getJobPoolID() == jid) {
+					// change the number of workers
+					return jp.setMaxWorkers(mw);
+				}
+			}
+
+		} catch (NumberFormatException e) {
+			return "Invalid parameters <jobPoolID> <maxWorkers> required";
+		}
+		
+		return "Invalid parameters <jobPoolID> <maxWorkers> required";
+	}
+}
diff --git a/src/engine/job/JobPool.java b/src/engine/job/JobPool.java
new file mode 100644
index 00000000..fc874407
--- /dev/null
+++ b/src/engine/job/JobPool.java
@@ -0,0 +1,295 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.job;
+
+
+import engine.jobs.AttackJob;
+import engine.jobs.UsePowerJob;
+import engine.net.CheckNetMsgFactoryJob;
+import engine.net.ConnectionMonitorJob;
+import engine.server.MBServerStatics;
+import org.pmw.tinylog.Logger;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Queue;
+import java.util.concurrent.LinkedBlockingQueue;
+
+public class JobPool {
+
+	int jobPoolID;
+	int maxWorkers;
+	int nextWorkerID;
+	private final LinkedBlockingQueue<AbstractJob> jobWaitQueue = new LinkedBlockingQueue<>();
+	private final LinkedBlockingQueue<JobWorker> jobWorkerQueue = new LinkedBlockingQueue<>();
+	private final ArrayList<JobWorker> jobWorkerList = new ArrayList<>();
+	private final LinkedBlockingQueue<AbstractJob> jobRunList = new LinkedBlockingQueue<>();
+	private boolean blockNewSubmissions = false;
+	
+	public JobPool(int id, int workers) {
+		this.jobPoolID = id;
+			
+		// default to 1 unless workers parameter is higher
+		int actualWorkers = 1;
+		if (workers > 1)
+			actualWorkers = workers;
+		
+		this.maxWorkers = actualWorkers;	
+		for (int i=0;i<actualWorkers; i++) {
+			this.startWorker(i);
+		}
+		
+		this.nextWorkerID = this.maxWorkers;
+		
+			
+			
+	}
+	
+	private int getNextWorkerID () {
+		return this.nextWorkerID++;
+		
+		
+	}
+	public int getJobPoolID () {
+		return this.jobPoolID;
+	}
+	
+	public void setBlockNewSubmissions(boolean blocked) {
+		this.blockNewSubmissions = blocked;
+	}
+	
+	public boolean submitJob(AbstractJob aj) {
+		
+		if (blockNewSubmissions) {
+			Logger.warn("A '" + aj.getClass().getSimpleName() + "' job was submitted, but submissions are currently blocked.");
+			return false;
+		}
+	
+		aj.markSubmitTime();
+		jobWaitQueue.add(aj);
+		
+		// keep notifying workers if the wait queue has items
+		// commented out as the price of polling the wait queue
+		// size while it is being updated out-weighs the gain 
+		// for not just blindly waking all workers
+		// unless we have a stupidly large pool vs CPU threads
+		
+		JobWorker jw = jobWorkerQueue.poll();
+		if(jw != null) {
+			synchronized (jw) {
+				jw.notify();
+			}
+		}
+		
+		return true;
+	}
+	
+	private void startWorker(int workerID) {
+		
+		// check we dont already have a jobWorker with that ID
+		synchronized(this.jobWorkerList) {
+			for (JobWorker jwi : this.jobWorkerList) {
+				if (jwi.getWorkerId() == workerID) {
+					Logger.error("Attempt to create worker with ID " + workerID + " failed in JobPool " + jobPoolID + " as worker ID already exists");
+					return;
+				}
+			}
+		}
+		
+		// ID is unique, create worker		
+		JobWorker jw;
+		jw = new JobWorker(workerID, this.jobPoolID, this.jobWaitQueue, this.jobWorkerQueue);
+		
+		synchronized(this.jobWorkerList) {
+			//Adds to the overall list..
+			jobWorkerList.add(jw);
+		}
+		
+		//Returns to the free worker queue..
+		jw.startup();
+	}
+
+	private String getQueueByClassAsString(Queue<AbstractJob> q) {
+		HashMap<String, Integer> ch = new HashMap<>();
+		int cnt = 0;
+		
+		
+		// iterate through the linked queue and get every item
+		// putting classname and incrementing the value each time in the hashmap
+		Iterator<AbstractJob> wi = q.iterator();
+		
+		while (cnt < q.size() && wi.hasNext()) {
+			AbstractJob aj = wi.next();
+			if (ch.containsKey(aj.getClass().getSimpleName())) {
+				int newValue = ch.get(aj.getClass().getSimpleName()) + 1;
+				ch.put(aj.getClass().getSimpleName(), newValue);
+			} else {
+				ch.put(aj.getClass().getSimpleName(), 1);
+			}
+			cnt++;
+		}
+		
+		// iterate through the hashmap outputting the classname and number of jobs
+		Iterator<String> i = ch.keySet().iterator();
+		String out = "";
+		while(i.hasNext()) {
+			Object key = i.next();
+			out += "JobPoolID_" + this.jobPoolID + ' ' + key.toString() + "=>" + ch.get(key) + '\n';
+		}
+		if (out.isEmpty())
+			return "No Jobs on queue\n";
+		else
+			return out;
+	}
+	
+	public void auditWorkers() {
+		
+		if(!MBServerStatics.ENABLE_AUDIT_JOB_WORKERS) {
+			return;
+		}
+		ArrayList<AbstractJob> problemJobs = new ArrayList<>();
+
+		// Checked for stalled Workers
+		Iterator<JobWorker> it = jobWorkerList.iterator();
+
+		while (it.hasNext()) {
+			JobWorker jw = it.next();
+			AbstractJob curJob = jw.getCurrentJob();
+
+			if (curJob != null) { // Has a job
+
+				if (JobPool.isExemptJobFromAudit(curJob)) {
+					continue;
+				}
+
+				// Determine whether the job is being executed or waiting to
+				// start;
+
+				if (curJob.getStartTime() <= 0) {
+					// Waiting to start
+					long diff = System.currentTimeMillis() - curJob.getSubmitTime();
+
+					if (diff >= MBServerStatics.JOB_STALL_THRESHOLD_MS) {
+						Logger.warn("Job did not start within threshold.  Stopping worker#" + jw.getWorkerId() + " JobData:"
+								+ curJob.toString());
+						jw.EmergencyStop();
+						problemJobs.add(jw.getCurrentJob());
+						it.remove();
+					} // end if (diff >=
+
+				} else if (curJob.getStopTime() <= 0L) {
+					// is executing it
+					long diff = System.currentTimeMillis() - curJob.getStartTime();
+
+					if (diff >= MBServerStatics.JOB_STALL_THRESHOLD_MS) {
+						Logger.warn("Job execution time exceeded threshold(" + diff + "). Stopping worker#" + jw.getWorkerId() + " JobData:"
+								+ curJob.toString());
+						jw.EmergencyStop();
+						problemJobs.add(jw.getCurrentJob());
+						it.remove();
+					} // end if (diff >=
+				} // end if(curJob.getStopTime()
+			} // end if(curJob != null)
+		} // end While
+
+		// Check Worker Count and add workers as necessary;
+		int workerCount = jobWorkerList.size();
+
+		int maxThreads = this.maxWorkers;
+		
+		
+		// no pool can go below a single thread
+		if (maxThreads < 1)
+			maxThreads = 1;
+				
+		while (workerCount != maxThreads) {
+			Logger.info("Resizing JobPool " + this.jobPoolID + " from " + workerCount + " to " + maxThreads);
+			
+			if (workerCount < maxThreads) {
+				this.startWorker(this.getNextWorkerID());
+
+				
+				if (jobWorkerList.size() <= workerCount) {
+					// Something didnt work correctly
+					Logger.warn("auditWorkers() failed to add a new JobWorker to JobPool " + this.jobPoolID + ". Worker count " + workerCount + " Worker pool size " + jobWorkerList.size() + " Aborting Audit.");
+					return;
+				}
+
+			} else if (workerCount > maxThreads) {
+				synchronized(this.jobWorkerList) {
+					Logger.warn("Reducing workers in JobPool " + this.jobPoolID + " Worker Count: " + workerCount + " to Max threads: " + maxThreads);
+					// pick a worker off the list and shut it down
+					
+					JobWorker toRemove = null;
+					int loopTries = 5;
+					do {
+						//Infinite loop could be bad..
+						toRemove = jobWorkerQueue.poll();
+					} while (toRemove == null && --loopTries >= 0);
+					
+					//remove it from the list
+					toRemove.shutdown();
+					jobWorkerList.remove(toRemove);
+				}
+			}
+
+			// update value for next loop pass
+			workerCount = jobWorkerList.size();
+		}
+	
+	}
+
+	private static boolean isExemptJobFromAudit(AbstractJob aj) {
+		// If the job is any of the following classes, exempt it from auditWorkers
+                if (aj instanceof ConnectionMonitorJob) {
+			return true;
+		} else
+                    return aj instanceof CheckNetMsgFactoryJob  || aj instanceof AttackJob || aj instanceof UsePowerJob;
+
+    }
+	
+	public void shutdown() {
+		synchronized(this.jobWorkerList) {
+			for (JobWorker jw : this.jobWorkerList)
+				jw.shutdown();
+		}
+	}
+	
+	public void emergencyStop() {
+		synchronized(this.jobWorkerList) {
+			for (JobWorker jw : this.jobWorkerList)
+				jw.EmergencyStop();
+		}
+	}
+
+	public String getRunningQueueByClassAsString() {
+		return this.getQueueByClassAsString(this.jobRunList);
+	}
+	
+	public String getWaitQueueByClassAsString () {
+		return this.getQueueByClassAsString(this.jobWaitQueue);
+	}
+	
+	
+	// used by devcmds
+	public String setMaxWorkers (int maxWorkers) {
+		
+		if (maxWorkers > 0 && maxWorkers < 101) {
+			this.maxWorkers = maxWorkers;
+			// audit workers reduces the cap
+			this.auditWorkers();
+			return "Max workers set to " + maxWorkers + " for JobPool_" + this.jobPoolID;
+		} else {
+			return "Max workers not set, value must be from 1-100";
+		}
+		
+	}
+}
diff --git a/src/engine/job/JobScheduler.java b/src/engine/job/JobScheduler.java
new file mode 100644
index 00000000..4ba503d6
--- /dev/null
+++ b/src/engine/job/JobScheduler.java
@@ -0,0 +1,169 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.job;
+
+import engine.server.MBServerStatics;
+
+import java.util.PriorityQueue;
+
+
+public class JobScheduler {
+
+	private static final JobScheduler INSTANCE = new JobScheduler();
+
+	private final PriorityQueue<JobContainer> jobs;
+	private volatile boolean alive;
+	private long timeOfKill = -1;
+
+	public static JobScheduler getInstance() {
+		return INSTANCE;
+	}
+
+	private JobScheduler() {
+		jobs = new PriorityQueue<>(MBServerStatics.SCHEDULER_INITIAL_CAPACITY);
+		Runnable worker = new Runnable() {
+			@Override
+			public void run() {
+				execution();
+			}
+		};
+
+		alive = true;
+
+		Thread t = new Thread(worker, "JobScheduler");
+		t.start();
+	}
+
+	/**
+	 * This function schedules a job to execute in <i>timeToExecution</i>
+	 * milliseconds from now.
+	 *
+	 * @param job
+	 * @param timeToExecution
+	 * @return
+	 */
+	public JobContainer scheduleJob(AbstractJob job, int timeToExecution) {
+		long timeOfExecution = System.currentTimeMillis() + timeToExecution;
+		JobContainer container = new JobContainer(job, timeOfExecution);
+
+		synchronized (jobs) {
+			jobs.offer(container);
+			jobs.notify();
+		}
+
+		return container;
+	}
+
+	/**
+	 * This function schedules a job to execute at the absolute time of
+	 * <i>timeOfExecution</i> (milliseconds).
+	 *
+	 * @param job
+	 * @param timeOfExecution
+	 * @return
+	 */
+	public JobContainer scheduleJob(AbstractJob job, long timeOfExecution) {
+		JobContainer container = new JobContainer(job, timeOfExecution);
+
+		synchronized (jobs) {
+			jobs.offer(container);
+			jobs.notify();
+		}
+
+		return container;
+	}
+
+	public boolean cancelScheduledJob(JobContainer container) {
+		return cancelScheduledJob(container.getJob());
+	}
+
+	public boolean cancelScheduledJob(AbstractJob job) {
+		JobContainer container = new JobContainer(job, -1);
+
+		boolean success = false;
+		synchronized (jobs) {
+			success = jobs.remove(container);
+			jobs.notify();
+		}
+
+		return success;
+	}
+
+	/**
+	 * Stops the jobScheduler
+	 */
+	public void shutdown() {
+		if (alive) {
+			alive = false;
+			timeOfKill = System.currentTimeMillis();
+			synchronized (jobs) {
+				jobs.notify();
+			}
+		}
+	}
+
+	public JobContainer pollNextJobContainer() {
+		if (alive) {
+			throw new IllegalStateException("Can't poll jobs from a live scheduler.");
+		}
+
+		synchronized (jobs) {
+			return jobs.poll();
+		}
+	}
+
+	public long getTimeOfKill() {
+		return this.timeOfKill;
+	}
+
+	public boolean isAlive() {
+		return this.alive;
+	}
+
+	private void execution() {
+		long duration;
+		JobContainer container;
+		int compensation = MBServerStatics.SCHEDULER_EXECUTION_TIME_COMPENSATION;
+
+		while (alive) {
+			synchronized (jobs) {
+				container = jobs.peek();
+				if (container == null) {
+					// queue is empty, wait until notified (which happens after
+					// a new job is offered)
+					try {
+						jobs.wait(0);
+					} catch (InterruptedException ie) {
+						// do nothing
+					}
+				} else {
+					duration = container.timeOfExecution - System.currentTimeMillis();
+					if (duration < compensation) {
+						jobs.poll();
+					} else {
+						// enforce new loop
+						container = null;
+
+						// sleep until the head job execution time
+						try {
+							jobs.wait(duration);
+						} catch (InterruptedException ie) {
+							// do nothing
+						}
+					}
+				}
+			}
+
+			if (container != null) {
+				JobManager.getInstance().submitJob(container.job);
+			}
+		}
+	}
+}
diff --git a/src/engine/job/JobWorker.java b/src/engine/job/JobWorker.java
new file mode 100644
index 00000000..2fc24c4d
--- /dev/null
+++ b/src/engine/job/JobWorker.java
@@ -0,0 +1,109 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.job;
+
+import engine.core.ControlledRunnable;
+import org.pmw.tinylog.Logger;
+
+import java.util.Queue;
+
+
+public class JobWorker extends ControlledRunnable {
+	private final int workerId;
+
+	private final Queue<AbstractJob> jobWaitQueue;
+	private final Queue<JobWorker> jobWorkerList;
+		
+	private AbstractJob currentJob;
+
+	public JobWorker(final int workerID, int priorityQueue,
+			Queue<AbstractJob> jobWaitQueue,
+			Queue<JobWorker> jobWorkerList) {
+		super("JobWorker_" +  priorityQueue + '_' +  workerID);
+		
+		workerId = workerID;
+		this.jobWaitQueue = jobWaitQueue;
+		this.jobWorkerList = jobWorkerList;
+	}
+
+	@Override
+	protected boolean _Run() {
+
+		while (this.runCmd) {
+			// Access to Queue is synchronized internal to JobManager
+			this.currentJob = this.jobWaitQueue.poll();
+
+			if (this.currentJob == null) {
+				try {
+					// use self as MUTEX
+					synchronized (this) {
+						this.jobWorkerList.add(this);
+						this.wait();
+					}
+
+				} catch (InterruptedException e) {
+					Logger.error(this.getThreadName(), e.getClass()
+							.getSimpleName()
+							+ ": " + e.getMessage());
+					break;
+
+				} 
+			} else {
+
+				// execute the new job..
+				this.currentJob.executeJob(this.getThreadName());
+				this.currentJob = null;
+			}
+
+		}
+		return true;
+	}
+
+	@Override
+	protected boolean _postRun() {
+		return true;
+	}
+
+	@Override
+	protected boolean _preRun() {
+		return true;
+	}
+
+	@Override
+	protected void _shutdown() {
+	}
+
+	@Override
+	protected void _startup() {
+		//this.logDirectINFO(this.getThreadName(), "Starting up...");
+	}
+
+	public final int getWorkerId() {
+		return workerId;
+	}
+
+	public final AbstractJob getCurrentJob() {
+		return currentJob;
+	}
+
+	public final boolean hasCurrentJob() {
+        return (currentJob != null);
+	}
+
+	protected void EmergencyStop() {
+		this.runCmd = false;
+		String out = "Stack Trace";
+		for(StackTraceElement e : this.getThisThread().getStackTrace()) {
+			out += " -> " + e.toString();
+		}
+		Logger.info(out);
+		this.getThisThread().interrupt();
+	}
+}
diff --git a/src/engine/jobs/AbstractEffectJob.java b/src/engine/jobs/AbstractEffectJob.java
new file mode 100644
index 00000000..eaafb760
--- /dev/null
+++ b/src/engine/jobs/AbstractEffectJob.java
@@ -0,0 +1,152 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+ package engine.jobs;
+
+import engine.job.AbstractScheduleJob;
+import engine.objects.AbstractWorldObject;
+import engine.powers.ActionsBase;
+import engine.powers.EffectsBase;
+import engine.powers.PowersBase;
+
+
+public abstract class AbstractEffectJob extends AbstractScheduleJob {
+
+	protected String stackType;
+	protected AbstractWorldObject target;
+	protected AbstractWorldObject source;
+	protected int trains;
+	protected ActionsBase action;
+	protected PowersBase power;
+	protected EffectsBase eb;
+	protected boolean skipSendEffect=false;
+	protected boolean skipApplyEffect=false;
+	protected boolean isChant=false;
+	protected boolean skipCancelEffect=false;
+	private boolean noOverwrite;
+	private int effectSourceType = 0;
+	private int effectSourceID = 0;
+
+	public AbstractEffectJob(AbstractWorldObject source, AbstractWorldObject target, String stackType, int trains, ActionsBase action, PowersBase power, EffectsBase eb) {
+		super();
+		this.source = source;
+		this.target = target;
+		this.stackType = stackType;
+		this.trains = trains;
+		this.action = action;
+		this.power = power;
+		this.eb = eb;
+	}
+
+	@Override
+	protected abstract void doJob();
+	@Override
+	protected abstract void _cancelJob();
+
+	public String getStackType() {
+		return this.stackType;
+	}
+
+	public AbstractWorldObject getTarget() {
+		return this.target;
+	}
+
+	public AbstractWorldObject getSource() {
+		return this.target;
+	}
+
+	public int getTrains() {
+		return this.trains;
+	}
+
+	public ActionsBase getAction() {
+		return this.action;
+	}
+
+	public PowersBase getPower() {
+		return this.power;
+	}
+
+	public int getPowerToken() {
+		if (this.power == null)
+			return 0;
+		return this.power.getToken();
+	}
+
+	public EffectsBase getEffect() {
+		return this.eb;
+	}
+
+	public boolean skipSendEffect() {
+		return this.skipSendEffect;
+	}
+
+	public void setSkipSendEffect(boolean value) {
+		this.skipSendEffect = value;
+	}
+
+	public boolean skipApplyEffect() {
+		return this.skipApplyEffect;
+	}
+
+	public void setSkipApplyEffect(boolean value) {
+		this.skipApplyEffect = value;
+	}
+
+	public boolean skipCancelEffect() {
+		return this.skipCancelEffect;
+	}
+
+	public void setSkipCancelEffect(boolean value) {
+		this.skipCancelEffect = value;
+	}
+
+	public boolean isChant() {
+		return this.isChant;
+	}
+
+	public void setChant(boolean value) {
+		this.isChant = value;
+	}
+
+	public void endEffect() {
+		if (this.eb == null)
+			return;
+		this.eb.endEffect(this.source, this.target, this.trains, this.power, this);
+	}
+	
+	public void endEffectNoPower() {
+		if (this.eb == null)
+			return;
+		this.eb.endEffectNoPower(this.trains,this);
+	}
+	public boolean isNoOverwrite() {
+		return noOverwrite;
+	}
+
+	public void setNoOverwrite(boolean noOverwrite) {
+		this.noOverwrite = noOverwrite;
+	}
+
+	public int getEffectSourceType() {
+		return effectSourceType;
+	}
+
+	public void setEffectSourceType(int effectSourceType) {
+		this.effectSourceType = effectSourceType;
+	}
+
+	public int getEffectSourceID() {
+		return effectSourceID;
+	}
+
+	public void setEffectSourceID(int effectSourceID) {
+		this.effectSourceID = effectSourceID;
+	}
+}
diff --git a/src/engine/jobs/ActivateBaneJob.java b/src/engine/jobs/ActivateBaneJob.java
new file mode 100644
index 00000000..ee5df670
--- /dev/null
+++ b/src/engine/jobs/ActivateBaneJob.java
@@ -0,0 +1,76 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.jobs;
+
+import engine.Enum;
+import engine.Enum.ChatChannelType;
+import engine.gameManager.DbManager;
+import engine.job.AbstractScheduleJob;
+import engine.net.DispatchMessage;
+import engine.net.client.msg.chat.ChatSystemMsg;
+import engine.objects.City;
+import org.pmw.tinylog.Logger;
+
+public class ActivateBaneJob extends AbstractScheduleJob {
+
+    private final int cityUUID;
+
+    public ActivateBaneJob(int cityUUID) {
+        super();
+        this.cityUUID = cityUUID;
+
+    }
+
+    @Override
+    protected void doJob() {
+
+        City city;
+
+        city = (City) DbManager.getObject(Enum.GameObjectType.City, cityUUID);
+
+        if (city == null)
+            return;
+
+
+        if (city.getBane() == null) {
+            Logger.info( "No bane found for " + city.getCityName());
+            return;
+        }
+
+        if (city.getBane().isErrant()) {
+            Logger.info("Removed errant bane on " + city.getCityName());
+            city.getBane().remove();
+            return;
+        }
+
+        if (city.getBane() == null)
+            return;
+
+        if (city.protectionEnforced == true)
+            city.protectionEnforced = false;
+        else {
+            Logger.info("Bane on " + city.getCityName() + " activated for unprotected city?");
+            return;
+        }
+
+        Logger.info("ActivateBaneJob", "Bane on " + city.getCityName() + " is now active");
+
+        ChatSystemMsg msg = new ChatSystemMsg(null, "[Bane Channel]  The Banecircle placed by " + city.getBane().getOwner().getGuild().getName() + " is now active! Buildings are now vulnerable to damage!");
+        msg.setMessageType(4); // Error message
+        msg.setChannel(ChatChannelType.SYSTEM.getChannelID());
+        
+        DispatchMessage.dispatchMsgToAll(msg);
+    }
+
+    @Override
+    protected void _cancelJob() {
+    }
+
+}
diff --git a/src/engine/jobs/AttackJob.java b/src/engine/jobs/AttackJob.java
new file mode 100644
index 00000000..3e3ba607
--- /dev/null
+++ b/src/engine/jobs/AttackJob.java
@@ -0,0 +1,39 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+ package engine.jobs;
+
+import engine.gameManager.CombatManager;
+import engine.job.AbstractJob;
+import engine.objects.AbstractCharacter;
+
+public class AttackJob extends AbstractJob {
+
+	private final AbstractCharacter source;
+	private final int slot;
+	private final boolean success;
+
+	public AttackJob(AbstractCharacter source, int slot, boolean success) {
+		super();
+		this.source = source;
+		this.slot = slot;
+		this.success = success;
+	}
+
+	@Override
+	protected void doJob() {
+		CombatManager.doCombat(this.source, slot);
+	}
+
+	public boolean success() {
+		return this.success;
+	}
+	protected void _cancelJob() {
+	}
+}
\ No newline at end of file
diff --git a/src/engine/jobs/BaneDefaultTimeJob.java b/src/engine/jobs/BaneDefaultTimeJob.java
new file mode 100644
index 00000000..54f8e162
--- /dev/null
+++ b/src/engine/jobs/BaneDefaultTimeJob.java
@@ -0,0 +1,47 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.jobs;
+
+import engine.job.AbstractScheduleJob;
+import engine.objects.Bane;
+import org.joda.time.DateTime;
+
+public class BaneDefaultTimeJob extends AbstractScheduleJob {
+
+    private final Bane bane;
+
+    public BaneDefaultTimeJob(Bane bane) {
+        super();
+        this.bane = bane;
+
+    }
+
+    @Override
+    protected void doJob() {
+
+        //bane already set.
+        if (this.bane.getLiveDate() != null) {
+            return;
+        }
+
+        DateTime defaultTime = new DateTime(this.bane.getPlacementDate());
+        defaultTime = defaultTime.plusDays(2);
+        defaultTime = defaultTime.hourOfDay().setCopy(22);
+        defaultTime = defaultTime.minuteOfHour().setCopy(0);
+        defaultTime = defaultTime.secondOfMinute().setCopy(0);
+        this.bane.setLiveDate(defaultTime);
+
+    }
+
+    @Override
+    protected void _cancelJob() {
+    }
+
+}
diff --git a/src/engine/jobs/BasicScheduledJob.java b/src/engine/jobs/BasicScheduledJob.java
new file mode 100644
index 00000000..e4bb8fb2
--- /dev/null
+++ b/src/engine/jobs/BasicScheduledJob.java
@@ -0,0 +1,126 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.jobs;
+
+import engine.job.AbstractJob;
+import engine.job.JobScheduler;
+import org.pmw.tinylog.Logger;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+
+/**
+ * A generic execution job which is an extension of {@link AbstractJob}.
+ * <p>
+ * Intended to be used with the {@link JobScheduler}, a BasicScheduledJob will
+ * hold a reference to an execution method.
+ * 
+ * @author Burfo
+ **/
+
+public class BasicScheduledJob extends AbstractJob {
+
+	private Method execution;
+	private Object referenceObject;
+
+	/**
+	 * Generates a new BasicScheduledJob that executes a static method that has
+	 * no parameters.
+	 * 
+	 * @param methodName
+	 *            Name of the static method to execute, such as "myMethod"
+	 * 
+	 * @param methodClass
+	 *            The class in which {@code methodName} exists
+	 */
+	public BasicScheduledJob(String methodName, Class methodClass) {
+		this(methodName, methodClass, null);
+	}
+
+	/**
+	 * Generates a new BasicScheduledJob that executes an instance method that
+	 * has no parameters.
+	 * 
+	 * @param methodName
+	 *            Name of the instance method to execute, such as "myMethod"
+	 * 
+	 * @param methodClass
+	 *            The class in which {@code methodName} exists
+	 * 
+	 * @param referenceObject
+	 *            Instance of {@code methodClass} against which {@code
+	 *            methodName} should be executed
+	 */
+	@SuppressWarnings("unchecked")
+	public BasicScheduledJob(String methodName, Class methodClass, Object referenceObject) {
+		super();
+		Method method = null;
+		try {
+			method = methodClass.getMethod(methodName);
+		} catch (SecurityException e) {
+			Logger.error( e);
+		} catch (NoSuchMethodException e) {
+			Logger.error(  e);
+		}
+		setData(method, null);
+	}
+
+	/**
+	 * Generates a new BasicScheduledJob that executes a static method that has
+	 * no parameters.
+	 * 
+	 * @param executionMethod
+	 *            Reference to the static method to execute
+	 */
+	public BasicScheduledJob(Method executionMethod) {
+		this(executionMethod, null);
+	}
+
+	/**
+	 * Generates a new BasicScheduledJob that executes an instance method that
+	 * has no parameters.
+	 * 
+	 * @param executionMethod
+	 *            Reference to the static method to execute
+	 * 
+	 * @param referenceObject
+	 *            Instanciated object against which {@code executionMethod}
+	 *            should be executed
+	 */
+	public BasicScheduledJob(Method executionMethod, Object referenceObject) {
+		super();
+		setData(executionMethod, referenceObject);
+	}
+
+	private void setData(Method executionMethod, Object referenceObject) {
+		this.execution = executionMethod;
+		this.referenceObject = referenceObject;
+		if (execution == null) {
+			Logger.error("BasicScheduledJob instanciated with no execution method.");
+		}
+	}
+
+	@Override
+	protected void doJob() {
+		if (execution == null) {
+			Logger.error( "BasicScheduledJob executed with nothing to execute.");
+			return;
+		}
+
+		try {
+			execution.invoke(referenceObject);
+		} catch (IllegalArgumentException | IllegalAccessException e) {
+			Logger.error( "BasicScheduledJob execution failed. Method: " + execution.toString(), e);
+		} catch (InvocationTargetException e) {
+			Logger.error( "BasicScheduledJob execution failed. " + "Method: " + execution.toString(), e);
+		}
+	}
+}
diff --git a/src/engine/jobs/BonusCalcJob.java b/src/engine/jobs/BonusCalcJob.java
new file mode 100644
index 00000000..2fdaf060
--- /dev/null
+++ b/src/engine/jobs/BonusCalcJob.java
@@ -0,0 +1,31 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.jobs;
+
+import engine.job.AbstractJob;
+import engine.objects.AbstractCharacter;
+
+public class BonusCalcJob extends AbstractJob {
+
+    private final AbstractCharacter ac;
+
+    public BonusCalcJob(AbstractCharacter ac) {
+        super();
+        this.ac = ac;
+    }
+
+    @Override
+    protected void doJob() {
+        if (this.ac != null) {
+            this.ac.applyBonuses();
+           
+        }
+    }
+}
diff --git a/src/engine/jobs/CSessionCleanupJob.java b/src/engine/jobs/CSessionCleanupJob.java
new file mode 100644
index 00000000..df591aab
--- /dev/null
+++ b/src/engine/jobs/CSessionCleanupJob.java
@@ -0,0 +1,28 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+ package engine.jobs;
+
+import engine.gameManager.SessionManager;
+import engine.job.AbstractJob;
+
+public class CSessionCleanupJob extends AbstractJob {
+
+	private final String secKey;
+
+	public CSessionCleanupJob(String key) {
+		super();
+		this.secKey = key;
+	}
+
+	@Override
+	protected void doJob() {
+		SessionManager.cSessionCleanup(secKey);
+	}
+}
diff --git a/src/engine/jobs/ChangeAltitudeJob.java b/src/engine/jobs/ChangeAltitudeJob.java
new file mode 100644
index 00000000..72a1cd8c
--- /dev/null
+++ b/src/engine/jobs/ChangeAltitudeJob.java
@@ -0,0 +1,42 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.jobs;
+
+import engine.gameManager.MovementManager;
+import engine.job.AbstractScheduleJob;
+import engine.objects.AbstractCharacter;
+
+public class ChangeAltitudeJob extends AbstractScheduleJob {
+
+    private final AbstractCharacter ac;
+    private final float targetAlt;
+    private final float startAlt;
+
+    public ChangeAltitudeJob(AbstractCharacter ac, float startAlt, float targetAlt) {
+        super();
+        this.ac = ac;
+        this.startAlt = startAlt;
+        this.targetAlt = targetAlt;
+    }
+
+    @Override
+    protected void doJob() {
+        if (this.ac != null)
+            MovementManager.finishChangeAltitude(ac, targetAlt);
+    }
+
+    @Override
+    protected void _cancelJob() {
+    }
+
+    public float getStartAlt() {
+        return startAlt;
+    }
+}
diff --git a/src/engine/jobs/ChantJob.java b/src/engine/jobs/ChantJob.java
new file mode 100644
index 00000000..2c3de86e
--- /dev/null
+++ b/src/engine/jobs/ChantJob.java
@@ -0,0 +1,103 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.jobs;
+
+import engine.Enum.GameObjectType;
+import engine.Enum.ModType;
+import engine.Enum.SourceType;
+import engine.gameManager.PowersManager;
+import engine.gameManager.SessionManager;
+import engine.objects.AbstractCharacter;
+import engine.objects.AbstractWorldObject;
+import engine.objects.PlayerBonuses;
+import engine.objects.PlayerCharacter;
+import engine.powers.ActionsBase;
+import engine.powers.EffectsBase;
+import engine.powers.PowersBase;
+
+import java.util.HashSet;
+
+
+public class ChantJob extends AbstractEffectJob {
+
+	private final AbstractEffectJob aej;
+	private int iteration = 0;
+
+	public ChantJob(AbstractWorldObject source, AbstractWorldObject target, String stackType, int trains, ActionsBase action, PowersBase power, EffectsBase eb, AbstractEffectJob aej) {
+		super(source, target, stackType, trains, action, power, eb);
+		this.aej = aej;
+	}
+
+	@Override
+	protected void doJob() {
+		if (this.aej == null || this.source == null || this.target == null || this.action == null || this.power == null || this.source == null || this.eb == null)
+			return;
+		PlayerBonuses bonuses = null;
+		
+		//if player isnt in game, do not run chant.
+		if (this.source.getObjectType().equals(GameObjectType.PlayerCharacter)){
+			if (SessionManager.getPlayerCharacterByID(this.source.getObjectUUID()) == null)
+				return;
+		}
+		if (AbstractWorldObject.IsAbstractCharacter(source))
+			bonuses = ((AbstractCharacter)source).getBonuses();
+		if (!this.source.isAlive()) {
+			PowersManager.finishEffectTime(this.source, this.target, this.action, this.trains);
+			if (AbstractWorldObject.IsAbstractCharacter(source))
+				((AbstractCharacter)source).cancelLastChant();
+		} else if (bonuses != null && bonuses.getBool(ModType.Silenced, SourceType.None)) {
+			PowersManager.finishEffectTime(this.source, this.target, this.action, this.trains);
+			if (AbstractWorldObject.IsAbstractCharacter(source))
+				((AbstractCharacter)source).cancelLastChant();
+		}
+		else if (AbstractWorldObject.IsAbstractCharacter(source) &&  ((AbstractCharacter)source).isSit()){
+			return;
+		}else if (this.iteration < this.power.getChantIterations() && AbstractWorldObject.IsAbstractCharacter(source)) {
+			this.skipSendEffect = true;
+			this.iteration++;
+
+			// *** Refactor holy wtf batman
+
+			String stackType = action.getStackType();
+			stackType = (stackType.equals("IgnoreStack")) ? Integer.toString(action.getUUID()) : stackType;
+
+			HashSet<AbstractWorldObject> awolist = null;
+			if (this.source instanceof PlayerCharacter)
+				awolist = PowersManager.getAllTargets(this.source, this.source.getLoc(), (PlayerCharacter)this.source, this.power);
+			else
+				awolist = new HashSet<>();
+			for (AbstractWorldObject awo : awolist) {
+
+				if (awo == null)
+					continue;
+
+				PowersManager.finishApplyPowerA((AbstractCharacter) this.source, awo, awo.getLoc(), this.power, this.trains, true);
+
+			}
+
+			if (AbstractWorldObject.IsAbstractCharacter(source))
+				//handle invul
+				if(power.getUUID() != 334)
+					((AbstractCharacter)this.source).setLastChant((int)(this.power.getChantDuration()) * 1000, this);
+				else
+					((AbstractCharacter)this.source).setLastChant((int)(this.power.getChantDuration()) * 1000, this);
+		} else {
+			PowersManager.finishEffectTime(this.source, this.target, this.action, this.trains);
+			if (AbstractWorldObject.IsAbstractCharacter(source)) {
+				((AbstractCharacter)source).cancelLastChant();
+			}
+		}
+	}
+
+	@Override
+	protected void _cancelJob() {
+	}
+
+}
diff --git a/src/engine/jobs/CloseGateJob.java b/src/engine/jobs/CloseGateJob.java
new file mode 100644
index 00000000..591a4d0a
--- /dev/null
+++ b/src/engine/jobs/CloseGateJob.java
@@ -0,0 +1,44 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+ package engine.jobs;
+
+import engine.Enum.RunegateType;
+import engine.job.AbstractScheduleJob;
+import engine.objects.Building;
+import engine.objects.Runegate;
+import org.pmw.tinylog.Logger;
+ 
+public class CloseGateJob extends AbstractScheduleJob {
+
+	private final Building building;
+        private final RunegateType portalType;
+
+	public CloseGateJob(Building building, RunegateType portalType) {
+		super();
+		this.building = building;
+		this.portalType = portalType;
+	}
+
+	@Override
+	protected void doJob() {
+            
+		if (building == null) {
+                    Logger.error("Rungate building was null");
+                    return;
+                }
+
+                Runegate.getRunegates()[RunegateType.getGateTypeFromUUID(building.getObjectUUID()).ordinal()].deactivatePortal(portalType);
+	}
+
+	@Override
+	protected void _cancelJob() {
+	}
+}
+
diff --git a/src/engine/jobs/DamageOverTimeJob.java b/src/engine/jobs/DamageOverTimeJob.java
new file mode 100644
index 00000000..3470842a
--- /dev/null
+++ b/src/engine/jobs/DamageOverTimeJob.java
@@ -0,0 +1,92 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.jobs;
+
+import engine.Enum.GameObjectType;
+import engine.gameManager.PowersManager;
+import engine.objects.AbstractCharacter;
+import engine.objects.AbstractWorldObject;
+import engine.objects.Building;
+import engine.powers.ActionsBase;
+import engine.powers.EffectsBase;
+import engine.powers.PowersBase;
+import engine.powers.poweractions.DamageOverTimePowerAction;
+
+
+public class DamageOverTimeJob extends AbstractEffectJob {
+
+	private final DamageOverTimePowerAction dot;
+	private int iteration = 0;
+	private int liveCounter = 0;
+
+	public DamageOverTimeJob(AbstractWorldObject source, AbstractWorldObject target, String stackType, int trains, ActionsBase action, PowersBase power, EffectsBase eb, DamageOverTimePowerAction dot) {
+		super(source, target, stackType, trains, action, power, eb);
+
+		this.dot = dot;
+		if (this.target != null && AbstractWorldObject.IsAbstractCharacter(target))
+			this.liveCounter = ((AbstractCharacter)target).getLiveCounter();
+		
+		this.iteration = action.getDurationInSeconds(trains) / this.dot.getNumIterations();
+	}
+
+	@Override
+	protected void doJob() {
+		if (this.target.getObjectType().equals(GameObjectType.Building)
+				&& ((Building)this.target).isVulnerable() == false) {
+			_cancelJob();
+			return;
+		}
+		
+		
+		if (this.dot == null || this.target == null || this.action == null || this.source == null || this.eb == null)
+			return;
+		if (AbstractWorldObject.IsAbstractCharacter(target) && ((AbstractCharacter)this.target).getLiveCounter() != liveCounter){
+			PowersManager.finishEffectTime(this.source, this.target, this.action, this.trains);
+			return;
+		}
+			if (!this.target.isAlive()){
+				PowersManager.finishEffectTime(this.source, this.target, this.action, this.trains);
+				return;
+			}
+			
+		this.iteration--;
+		
+		if (this.iteration < 0){
+			PowersManager.finishEffectTime(this.source, this.target, this.action, this.trains);
+			return;
+		}
+			this.skipSendEffect = true;
+			String stackType = action.getStackType();
+			if (stackType.equals("IgnoreStack")) 
+				this.target.addEffect(Integer.toString(action.getUUID()), getTickLength(), this, this.eb, this.trains);
+			else
+				this.target.addEffect(stackType, getTickLength(), this, this.eb, this.trains);
+			if (AbstractWorldObject.IsAbstractCharacter(source))
+				eb.startEffect((AbstractCharacter)this.source, this.target, this.trains, this);
+	}
+
+	@Override
+	protected void _cancelJob() {
+		PowersManager.cancelEffectTime(this.source, this.target, this.power, this.eb, this.action, this.trains, this);
+	}
+
+	public int getIteration() {
+		return this.iteration;
+	}
+
+	public int getTickLength() {
+		return this.dot.getNumIterations() * 1000;
+	}
+
+	public int inc() {
+		this.iteration++;
+		return this.iteration;
+	}
+}
diff --git a/src/engine/jobs/DatabaseUpdateJob.java b/src/engine/jobs/DatabaseUpdateJob.java
new file mode 100644
index 00000000..18e3dd61
--- /dev/null
+++ b/src/engine/jobs/DatabaseUpdateJob.java
@@ -0,0 +1,65 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+ package engine.jobs;
+
+import engine.Enum.GameObjectType;
+import engine.gameManager.DbManager;
+import engine.job.AbstractScheduleJob;
+import engine.objects.AbstractGameObject;
+import engine.objects.Building;
+import engine.objects.PlayerCharacter;
+
+public class DatabaseUpdateJob extends AbstractScheduleJob {
+
+	private final AbstractGameObject ago;
+	private final String type;
+
+	public DatabaseUpdateJob(AbstractGameObject ago, String type) {
+		super();
+		this.ago = ago;
+		this.type = type;
+	}
+
+	@Override
+	protected void doJob() {
+		if (this.ago == null)
+			return;
+		ago.removeDatabaseJob(this.type, false);
+                
+		if (ago.getObjectType().equals(GameObjectType.PlayerCharacter)) {
+                    
+                    PlayerCharacter pc = (PlayerCharacter) ago;
+                        
+                    switch (this.type) {
+                        case "Skills":
+                            pc.updateSkillsAndPowersToDatabase();
+                            break;
+                        case "Stats":
+                            DbManager.PlayerCharacterQueries.UPDATE_CHARACTER_STATS(pc);
+                            break;
+                        case "EXP":
+                            DbManager.PlayerCharacterQueries.UPDATE_CHARACTER_EXPERIENCE(pc);
+                            break;
+                    }
+
+		}
+		else if (ago instanceof Building) {
+			Building b = (Building) ago;
+			if (this.type.equals("health"))
+				DbManager.BuildingQueries.UPDATE_BUILDING_HEALTH(b.getObjectUUID(), (int)(b.getHealth()));
+		}
+
+	}
+
+	@Override
+	protected void _cancelJob() {
+	}
+}
+
diff --git a/src/engine/jobs/DebugTimerJob.java b/src/engine/jobs/DebugTimerJob.java
new file mode 100644
index 00000000..17f7ec04
--- /dev/null
+++ b/src/engine/jobs/DebugTimerJob.java
@@ -0,0 +1,61 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.jobs;
+
+import engine.gameManager.ChatManager;
+import engine.job.AbstractScheduleJob;
+import engine.objects.PlayerCharacter;
+
+public class DebugTimerJob extends AbstractScheduleJob {
+
+    private final PlayerCharacter pc;
+    private final String command;
+    private final int commandNum;
+    private final int duration;
+
+    public DebugTimerJob(PlayerCharacter pc, String command, int commandNum, int duration) {
+        super();
+        this.pc = pc;
+        this.command = command;
+        this.commandNum = commandNum;
+        this.duration = duration;
+    }
+
+    @Override
+    protected void doJob() {
+        if (this.pc == null) {
+            return;
+        }
+
+        String text;
+        switch (this.commandNum) {
+            case 1: //health
+                text = "Health: " + pc.getHealth();
+                ChatManager.chatSystemInfo(pc, text);
+                break;
+            case 2: //mana
+                text = "Mana: " + pc.getMana();
+                ChatManager.chatSystemInfo(pc, text);
+                break;
+            case 3: //stamina
+                text = "Stamina: " + pc.getStamina();
+                ChatManager.chatSystemInfo(pc, text);
+                break;
+            default:
+        }
+
+        //re-up the timer for this
+        this.pc.renewTimer(command, this, duration);
+    }
+
+    @Override
+    protected void _cancelJob() {
+    }
+}
diff --git a/src/engine/jobs/DeferredPowerJob.java b/src/engine/jobs/DeferredPowerJob.java
new file mode 100644
index 00000000..17ffefa6
--- /dev/null
+++ b/src/engine/jobs/DeferredPowerJob.java
@@ -0,0 +1,109 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.jobs;
+
+import engine.gameManager.CombatManager;
+import engine.gameManager.PowersManager;
+import engine.math.Vector3fImmutable;
+import engine.objects.AbstractCharacter;
+import engine.objects.AbstractWorldObject;
+import engine.objects.Mob;
+import engine.objects.PlayerCharacter;
+import engine.powers.ActionsBase;
+import engine.powers.EffectsBase;
+import engine.powers.PowersBase;
+import engine.powers.poweractions.ApplyEffectPowerAction;
+import engine.powers.poweractions.DeferredPowerPowerAction;
+
+public class DeferredPowerJob extends AbstractEffectJob {
+
+	private final DeferredPowerPowerAction def;
+
+	public DeferredPowerJob(AbstractWorldObject source, AbstractWorldObject target, String stackType, int trains, ActionsBase action, PowersBase power, EffectsBase eb, DeferredPowerPowerAction def) {
+		super(source, target, stackType, trains, action, power, eb);
+		this.def = def;
+	}
+
+	public DeferredPowerJob(AbstractWorldObject source, AbstractWorldObject target, String stackType, int trains, ActionsBase action, PowersBase power, EffectsBase eb, ApplyEffectPowerAction def) {
+		super(source, target, stackType, trains, action, power, eb);
+		this.def = null;
+	}
+
+	@Override
+	protected void doJob() {
+		//Power ended with no attack, cancel weapon power boost
+		if (this.source != null && this.source instanceof PlayerCharacter) {
+			((PlayerCharacter) this.source).setWeaponPower(null);
+		}
+		PowersManager.finishEffectTime(this.source, this.target, this.action, this.trains);
+	}
+
+	@Override
+	protected void _cancelJob() {
+		//Attack happened.
+		PowersManager.cancelEffectTime(this.source, this.target, this.power, this.eb, this.action, this.trains, this);
+	}
+
+	public void attack(AbstractWorldObject tar, float attackRange) {
+
+		if (this.source == null)
+			return;
+
+		if (!AbstractWorldObject.IsAbstractCharacter(tar))
+			return;
+
+		if (this.power == null)
+			return;
+
+
+		switch(this.source.getObjectType()){
+
+		case PlayerCharacter:
+
+			if (def == null) {
+				//No powers applied, just reset weapon power.
+				((PlayerCharacter) this.source).setWeaponPower(null);
+				return;
+			}
+			float powerRange = this.power.getWeaponRange();
+
+			// Wtf?  Method returns TRUE if rage test fails?  Seriously?
+
+			//DO valid range check ONLY for weapon powers with range less than attack range.
+			if (attackRange > powerRange)
+				if (CombatManager.NotInRange((AbstractCharacter)this.source, tar, powerRange))
+					return;
+
+			//Range check passed, apply power and clear weapon power.
+			((PlayerCharacter) this.source).setWeaponPower(null);
+
+
+			//weapon powers with no deferedpoweraction have null Def, but still have bonuses applied already and will finish here.
+
+
+
+
+			PowersManager.applyPower((AbstractCharacter) this.source, tar, Vector3fImmutable.ZERO, def.getDeferredPowerID(), this.trains, false);
+			PowersManager.finishEffectTime(this.source, this.target, this.action, this.trains);
+			break;
+		case Mob:
+			((Mob) this.source).setWeaponPower(null);
+			if (def == null) {
+				return;
+			}
+
+			PowersManager.applyPower((AbstractCharacter) this.source, tar, Vector3fImmutable.ZERO, def.getDeferredPowerID(), this.trains, false);
+			PowersManager.finishEffectTime(this.source, this.target, this.action, this.trains);
+			break;
+
+		}
+
+	}
+}
diff --git a/src/engine/jobs/DisconnectJob.java b/src/engine/jobs/DisconnectJob.java
new file mode 100644
index 00000000..f78448cc
--- /dev/null
+++ b/src/engine/jobs/DisconnectJob.java
@@ -0,0 +1,34 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.jobs;
+
+import engine.job.AbstractScheduleJob;
+import engine.net.client.ClientConnection;
+
+public class DisconnectJob extends AbstractScheduleJob {
+
+    private final ClientConnection origin;
+
+    public DisconnectJob(ClientConnection origin) {
+        super();
+        this.origin = origin;
+    }
+
+    @Override
+    protected void doJob() {
+        if (this.origin != null) {
+            this.origin.disconnect();
+        }
+    }
+
+    @Override
+    protected void _cancelJob() {
+    }
+}
diff --git a/src/engine/jobs/DoorCloseJob.java b/src/engine/jobs/DoorCloseJob.java
new file mode 100644
index 00000000..1614fa96
--- /dev/null
+++ b/src/engine/jobs/DoorCloseJob.java
@@ -0,0 +1,63 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+
+
+
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+
+package engine.jobs;
+
+import engine.Enum.DoorState;
+import engine.job.AbstractScheduleJob;
+import engine.net.DispatchMessage;
+import engine.net.client.msg.DoorTryOpenMsg;
+import engine.objects.Blueprint;
+import engine.objects.Building;
+
+public class DoorCloseJob extends AbstractScheduleJob {
+
+    Building building;
+    int door;
+
+    public DoorCloseJob(Building building, int door) {
+        super();
+        this.building = building;
+        this.door = door;
+    }
+
+    @Override
+    protected void doJob() {
+
+        int doorNumber;
+
+        if (this.building == null)
+            return;
+
+		doorNumber = Blueprint.getDoorNumberbyMesh(this.door);
+
+		this.building.setDoorState(doorNumber, DoorState.CLOSED);
+        
+        DoorTryOpenMsg msg = new DoorTryOpenMsg(door, this.building.getObjectUUID(), 0, (byte) 0);
+        DispatchMessage.sendToAllInRange(building, msg);
+
+    }
+
+    @Override
+    protected void _cancelJob() {
+    }
+}
diff --git a/src/engine/jobs/EndFearJob.java b/src/engine/jobs/EndFearJob.java
new file mode 100644
index 00000000..981ebd31
--- /dev/null
+++ b/src/engine/jobs/EndFearJob.java
@@ -0,0 +1,45 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.jobs;
+
+import engine.objects.AbstractWorldObject;
+import engine.objects.Mob;
+import engine.powers.ActionsBase;
+import engine.powers.EffectsBase;
+import engine.powers.PowersBase;
+
+public class EndFearJob extends AbstractEffectJob {
+
+    public EndFearJob(AbstractWorldObject source, AbstractWorldObject target, String stackType, int trains, ActionsBase action, PowersBase power, EffectsBase eb) {
+        super(source, target, stackType, trains, action, power, eb);
+    }
+
+    @Override
+    protected void doJob() {
+        
+        //cancel fear for mob.
+        
+        if (this.target == null || (!(this.target instanceof Mob)))
+            return;
+        
+        ((Mob) this.target).setFearedObject(null);
+    }
+
+    @Override
+    protected void _cancelJob() {
+        
+        //cancel fear for mob.
+        
+        if (this.target == null || (!(this.target instanceof Mob))) 
+            return;
+        
+        ((Mob) this.target).setFearedObject(null);
+    }
+}
diff --git a/src/engine/jobs/FinishCooldownTimeJob.java b/src/engine/jobs/FinishCooldownTimeJob.java
new file mode 100644
index 00000000..6759cf87
--- /dev/null
+++ b/src/engine/jobs/FinishCooldownTimeJob.java
@@ -0,0 +1,32 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.jobs;
+
+import engine.gameManager.PowersManager;
+import engine.job.AbstractJob;
+import engine.net.client.msg.PerformActionMsg;
+import engine.objects.PlayerCharacter;
+
+public class FinishCooldownTimeJob extends AbstractJob {
+
+    PlayerCharacter pc;
+    PerformActionMsg msg;
+
+    public FinishCooldownTimeJob(PlayerCharacter pc, PerformActionMsg msg) {
+        super();
+        this.pc = pc;
+        this.msg = msg;
+    }
+
+    @Override
+    protected void doJob() {
+        PowersManager.finishCooldownTime(this.msg, this.pc);
+    }
+}
diff --git a/src/engine/jobs/FinishEffectTimeJob.java b/src/engine/jobs/FinishEffectTimeJob.java
new file mode 100644
index 00000000..60786f68
--- /dev/null
+++ b/src/engine/jobs/FinishEffectTimeJob.java
@@ -0,0 +1,33 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.jobs;
+
+import engine.gameManager.PowersManager;
+import engine.objects.AbstractWorldObject;
+import engine.powers.ActionsBase;
+import engine.powers.EffectsBase;
+import engine.powers.PowersBase;
+
+public class FinishEffectTimeJob extends AbstractEffectJob {
+
+    public FinishEffectTimeJob(AbstractWorldObject source, AbstractWorldObject target, String stackType, int trains, ActionsBase action, PowersBase power, EffectsBase eb) {
+        super(source, target, stackType, trains, action, power, eb);
+    }
+
+    @Override
+    protected void doJob() {
+        PowersManager.finishEffectTime(this.source, this.target, this.action, this.trains);
+    }
+
+    @Override
+    protected void _cancelJob() {
+        PowersManager.cancelEffectTime(this.source, this.target, this.power, this.eb, this.action, this.trains, this);
+    }
+}
diff --git a/src/engine/jobs/FinishRecycleTimeJob.java b/src/engine/jobs/FinishRecycleTimeJob.java
new file mode 100644
index 00000000..47c4126a
--- /dev/null
+++ b/src/engine/jobs/FinishRecycleTimeJob.java
@@ -0,0 +1,36 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.jobs;
+
+import engine.gameManager.PowersManager;
+import engine.job.AbstractScheduleJob;
+import engine.net.client.msg.PerformActionMsg;
+import engine.objects.PlayerCharacter;
+
+public class FinishRecycleTimeJob extends AbstractScheduleJob {
+
+    PlayerCharacter pc;
+    PerformActionMsg msg;
+
+    public FinishRecycleTimeJob(PlayerCharacter pc, PerformActionMsg msg) {
+        super();
+        this.pc = pc;
+        this.msg = msg;
+    }
+
+    @Override
+    protected void doJob() {
+        PowersManager.finishRecycleTime(this.msg, this.pc, false);
+    }
+
+    @Override
+    protected void _cancelJob() {
+    }
+}
diff --git a/src/engine/jobs/FinishSpireEffectJob.java b/src/engine/jobs/FinishSpireEffectJob.java
new file mode 100644
index 00000000..09e6b147
--- /dev/null
+++ b/src/engine/jobs/FinishSpireEffectJob.java
@@ -0,0 +1,37 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.jobs;
+
+import engine.objects.AbstractWorldObject;
+import engine.objects.PlayerCharacter;
+import engine.powers.EffectsBase;
+
+public class FinishSpireEffectJob extends AbstractEffectJob {
+
+    public FinishSpireEffectJob(AbstractWorldObject target, String stackType, EffectsBase eb, int trains) {
+        super(null, target, stackType, trains, null, null, eb);
+    }
+
+    @Override
+    protected void doJob() {
+        
+        PlayerCharacter pc = (PlayerCharacter) target;
+        
+        if (pc == null)
+            return;
+        
+        pc.endEffectNoPower(Integer.toString(eb.getUUID()));
+
+    }
+
+    @Override
+    protected void _cancelJob() {
+    }
+}
diff --git a/src/engine/jobs/FinishSummonsJob.java b/src/engine/jobs/FinishSummonsJob.java
new file mode 100644
index 00000000..afe4f6d2
--- /dev/null
+++ b/src/engine/jobs/FinishSummonsJob.java
@@ -0,0 +1,81 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.jobs;
+
+import engine.Enum.ModType;
+import engine.Enum.SourceType;
+import engine.gameManager.PowersManager;
+import engine.job.AbstractScheduleJob;
+import engine.job.JobContainer;
+import engine.net.client.msg.ErrorPopupMsg;
+import engine.objects.PlayerCharacter;
+
+import java.util.concurrent.ConcurrentHashMap;
+
+public class FinishSummonsJob extends AbstractScheduleJob {
+
+	PlayerCharacter source;
+	PlayerCharacter target;
+
+	public FinishSummonsJob(PlayerCharacter source, PlayerCharacter target) {
+		super();
+		this.source = source;
+		this.target = target;
+	}
+
+	@Override
+	protected void doJob() {
+
+		if (this.target == null)
+			return;
+
+		//clear summon timer
+
+		ConcurrentHashMap<String, JobContainer> timers = this.target.getTimers();
+
+		if (timers != null && timers.containsKey("Summon"))
+			timers.remove("Summon");
+
+		if (this.source == null || !this.source.isAlive() || !this.target.isAlive())
+			return;
+
+		// cannot summon a player in combat
+		if (this.target.isCombat()) {
+
+			ErrorPopupMsg.sendErrorMsg(this.source, "Cannot summon player in combat.");
+
+			PowersManager.finishRecycleTime(428523680, this.source, false);
+			return;
+		}
+
+		if (this.target.getBonuses() != null && this.target.getBonuses().getBool(ModType.BlockedPowerType, SourceType.SUMMON)){
+			ErrorPopupMsg.sendErrorMsg(this.target, "You have been blocked from receiving summons!");
+			ErrorPopupMsg.sendErrorMsg(this.source, "Target is blocked from receiving summons!");
+			return;
+		}
+
+		if (this.source.getRegion() != null)
+			this.target.setRegion(this.source.getRegion());
+		//teleport target to source
+		target.teleport(source.getLoc());
+	}
+
+	@Override
+	protected void _cancelJob() {
+	}
+
+	public PlayerCharacter getSource() {
+		return this.source;
+	}
+
+	public PlayerCharacter getTarget() {
+		return this.target;
+	}
+}
diff --git a/src/engine/jobs/FlightJob.java b/src/engine/jobs/FlightJob.java
new file mode 100644
index 00000000..1bd9366c
--- /dev/null
+++ b/src/engine/jobs/FlightJob.java
@@ -0,0 +1,40 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.jobs;
+
+import engine.gameManager.MovementManager;
+import engine.job.AbstractScheduleJob;
+import engine.net.client.msg.ChangeAltitudeMsg;
+import engine.objects.PlayerCharacter;
+
+public class FlightJob extends AbstractScheduleJob {
+
+    private final PlayerCharacter pc;
+    private final ChangeAltitudeMsg msg;
+    private final int duration;
+
+    public FlightJob(PlayerCharacter pc, ChangeAltitudeMsg msg, int duration) {
+        super();
+        this.msg = msg;
+        this.duration = duration;
+        this.pc = pc;
+    }
+
+    @Override
+    protected void doJob() {
+        if (this.pc != null && this.msg != null)
+            MovementManager.updateFlight(pc, msg, duration);
+    }
+
+    @Override
+    protected void _cancelJob() {
+    }
+
+}
diff --git a/src/engine/jobs/LoadEffectsJob.java b/src/engine/jobs/LoadEffectsJob.java
new file mode 100644
index 00000000..f21ad973
--- /dev/null
+++ b/src/engine/jobs/LoadEffectsJob.java
@@ -0,0 +1,48 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.jobs;
+
+import engine.job.AbstractJob;
+import engine.net.client.ClientConnection;
+import engine.objects.AbstractCharacter;
+import engine.objects.AbstractWorldObject;
+
+import java.util.ArrayList;
+
+public class LoadEffectsJob extends AbstractJob {
+
+	ArrayList<AbstractWorldObject> acsToLoad;
+	ClientConnection originToSend;
+
+	public LoadEffectsJob(ArrayList<AbstractWorldObject> acsToLoad, ClientConnection origin) {
+		this.acsToLoad = acsToLoad;
+		this.originToSend = origin;
+
+	}
+
+	@Override
+	protected void doJob() {
+		if (this.originToSend == null) {
+			return;
+		}
+
+		for (AbstractWorldObject awo : this.acsToLoad) {
+
+			if (AbstractWorldObject.IsAbstractCharacter(awo)) {
+				AbstractCharacter acToLoad = (AbstractCharacter) awo;
+				acToLoad.sendAllEffects(this.originToSend);
+
+			}
+
+		}
+
+	}
+
+}
diff --git a/src/engine/jobs/LogoutCharacterJob.java b/src/engine/jobs/LogoutCharacterJob.java
new file mode 100644
index 00000000..cfdf60d2
--- /dev/null
+++ b/src/engine/jobs/LogoutCharacterJob.java
@@ -0,0 +1,37 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.jobs;
+
+import engine.job.AbstractScheduleJob;
+import engine.objects.PlayerCharacter;
+import engine.server.world.WorldServer;
+
+public class LogoutCharacterJob extends AbstractScheduleJob {
+
+    private final PlayerCharacter pc;
+    private final WorldServer server;
+
+    public LogoutCharacterJob(PlayerCharacter pc, WorldServer server) {
+        super();
+        this.pc = pc;
+        this.server = server;
+    }
+
+    @Override
+    protected void doJob() {
+        server.logoutCharacter(this.pc);
+    }
+
+	@Override
+	protected void _cancelJob() {
+		// TODO Auto-generated method stub
+		
+	}
+}
diff --git a/src/engine/jobs/MineActiveJob.java b/src/engine/jobs/MineActiveJob.java
new file mode 100644
index 00000000..b6ce1453
--- /dev/null
+++ b/src/engine/jobs/MineActiveJob.java
@@ -0,0 +1,71 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+
+
+
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.jobs;
+
+import engine.job.AbstractJob;
+import engine.objects.Mine;
+import org.pmw.tinylog.Logger;
+
+import java.time.LocalDateTime;
+import java.util.ArrayList;
+
+public class MineActiveJob extends AbstractJob {
+
+	public MineActiveJob() {
+		super();
+	}
+
+	@Override
+	protected void doJob() {
+		ArrayList<Mine> mines = Mine.getMines();
+		LocalDateTime now = LocalDateTime.now();
+
+		for (Mine mine : mines) {
+			try {
+				
+				if (mine.getOwningGuild() == null){
+					mine.handleStartMineWindow();
+					Mine.setLastChange(System.currentTimeMillis());
+					continue;
+				}
+					
+				//handle claimed mines
+                LocalDateTime mineWindow =  mine.openDate.withMinute(0).withSecond(0).withNano(0);
+				if (mineWindow != null && now.plusMinutes(1).isAfter(mineWindow))
+					if (!mine.getIsActive()) {
+						Logger.info("activating mine. " + mineWindow.getHour() + " , " + now.getHour());
+						mine.handleStartMineWindow();
+						Mine.setLastChange(System.currentTimeMillis());
+					
+					}else{
+						if (mine.handleEndMineWindow()){
+							Logger.info("Deactivating mine. " + mineWindow.getHour() + " , " + now.getHour());
+							Mine.setLastChange(System.currentTimeMillis());
+						}
+							
+					}
+			}catch (Exception e) {
+				Logger.error( "mineID: " + mine.getObjectUUID() + e);
+			}
+		}
+	}
+}
diff --git a/src/engine/jobs/NoTimeJob.java b/src/engine/jobs/NoTimeJob.java
new file mode 100644
index 00000000..309bec03
--- /dev/null
+++ b/src/engine/jobs/NoTimeJob.java
@@ -0,0 +1,28 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.jobs;
+
+import engine.objects.AbstractWorldObject;
+import engine.powers.EffectsBase;
+
+public class NoTimeJob extends AbstractEffectJob {
+
+    public NoTimeJob(AbstractWorldObject target, String stackType, EffectsBase eb, int trains) {
+        super(null, target, stackType, trains, null, null, eb);
+    }
+
+    @Override
+    protected void doJob() {
+    }
+
+    @Override
+    protected void _cancelJob() {
+    }
+}
diff --git a/src/engine/jobs/PersistentAoeJob.java b/src/engine/jobs/PersistentAoeJob.java
new file mode 100644
index 00000000..33e7827c
--- /dev/null
+++ b/src/engine/jobs/PersistentAoeJob.java
@@ -0,0 +1,110 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.jobs;
+
+import engine.Enum.GameObjectType;
+import engine.gameManager.PowersManager;
+import engine.math.Vector3fImmutable;
+import engine.net.client.msg.PerformActionMsg;
+import engine.objects.AbstractCharacter;
+import engine.objects.AbstractWorldObject;
+import engine.objects.PlayerCharacter;
+import engine.powers.ActionsBase;
+import engine.powers.EffectsBase;
+import engine.powers.PowersBase;
+
+import java.util.HashSet;
+
+public class PersistentAoeJob extends AbstractEffectJob {
+
+	private final AbstractEffectJob aej;
+	private int iteration = 0;
+	private  Vector3fImmutable targetLoc;
+	private Vector3fImmutable lastLoc;
+
+	public PersistentAoeJob(AbstractWorldObject source, AbstractWorldObject target, String stackType, int trains, ActionsBase action, PowersBase power, EffectsBase eb, AbstractEffectJob aej, Vector3fImmutable targetLoc) {
+		super(source, target, stackType, trains, action, power, eb);
+		this.aej = aej;
+		if (target != null && this.target.getObjectType() == GameObjectType.PlayerCharacter)
+			this.targetLoc = this.target.getLoc();
+		else
+			this.targetLoc = targetLoc;
+		this.lastLoc = targetLoc;
+	}
+
+	@Override
+	protected void doJob() {
+
+		if (this.aej == null || this.source == null || this.action == null || this.power == null || this.source == null || this.eb == null)
+			return;
+
+		if (!this.source.isAlive())
+			PowersManager.finishEffectTime(this.source, this.target, this.action, this.trains);
+		else if (this.iteration < this.power.getChantIterations()) {
+
+
+			this.skipSendEffect = true;
+			this.iteration++;
+
+
+			if (this.target != null){
+				this.lastLoc = this.target.getLoc();
+				this.targetLoc = this.target.getLoc();
+			}
+
+			String stackType = action.getStackType();
+			stackType = (stackType.equals("IgnoreStack")) ? Integer.toString(action.getUUID()) : stackType;
+			HashSet<AbstractWorldObject> awolist = null;
+
+
+			if (this.source instanceof PlayerCharacter)
+				awolist = PowersManager.getAllTargets(null, this.targetLoc, (PlayerCharacter) this.source, this.power);
+			else
+				awolist = new HashSet<>();
+			PerformActionMsg msg = new PerformActionMsg(power.getToken(), 9999, source
+					.getObjectType().ordinal(), source.getObjectUUID(), source.getObjectType().ordinal(),
+					source.getObjectUUID(), 0, 0, 0, 2, 0);
+
+
+			for (AbstractWorldObject awo : awolist) {
+
+				//judge the defense of the target
+
+
+
+				if (awo == null
+						|| PowersManager.testAttack((PlayerCharacter) this.source, awo, this.power, msg))
+					continue;
+
+				PowersManager.finishApplyPowerA((AbstractCharacter) this.source, awo, this.targetLoc, this.power, this.trains, true);
+				if (this.target != null  && !this.target.isAlive()){
+					this.target = null;
+				}
+
+			}
+			if (AbstractWorldObject.IsAbstractCharacter(source))
+				((AbstractCharacter) this.source).addPersistantAoe(stackType, (int) (this.power.getChantDuration() * 1000), this, this.eb, this.trains);
+		} else
+			PowersManager.finishEffectTime(this.source, this.target, this.action, this.trains);
+	}
+
+	@Override
+	protected void _cancelJob() {
+	}
+
+	public int getIteration() {
+		return this.iteration;
+	}
+
+	public int inc() {
+		this.iteration++;
+		return this.iteration;
+	}
+}
diff --git a/src/engine/jobs/RefreshGroupJob.java b/src/engine/jobs/RefreshGroupJob.java
new file mode 100644
index 00000000..9d27abff
--- /dev/null
+++ b/src/engine/jobs/RefreshGroupJob.java
@@ -0,0 +1,82 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.jobs;
+
+import engine.gameManager.GroupManager;
+import engine.job.AbstractJob;
+import engine.net.client.ClientConnection;
+import engine.objects.Group;
+import engine.objects.PlayerCharacter;
+
+public class RefreshGroupJob extends AbstractJob {
+
+    private final PlayerCharacter pc;
+    private ClientConnection origin;
+    private Group grp;
+    private PlayerCharacter pcToRefresh;
+    private final boolean wholeGroup;
+
+    public RefreshGroupJob(PlayerCharacter pc, PlayerCharacter pcToRefresh) {
+        super();
+        this.pc = pc;
+        if (pc != null) {
+            this.origin = pc.getClientConnection();
+            this.grp = GroupManager.getGroup(pc);
+        }
+        this.pcToRefresh = pcToRefresh;
+        this.wholeGroup = false;
+    }
+
+    public RefreshGroupJob(PlayerCharacter pc) {
+        super();
+        this.pc = pc;
+        if (pc != null) {
+            this.origin = pc.getClientConnection();
+            this.grp = GroupManager.getGroup(pc);
+        }
+        this.wholeGroup = true;
+    }
+
+    @Override
+    protected void doJob() {
+
+        if (this.pc == null || this.origin == null || grp == null) {
+            return;
+        }
+
+        if (wholeGroup) {
+            
+            // refresh everyone in the group including me
+            // check that we are in the same group as when we submitted the job
+            
+            if (GroupManager.getGroup(pc) != null && GroupManager.getGroup(pc) == grp) {
+                
+            // refresh pc's group list for just the one player that needed refreshing
+                
+                GroupManager.RefreshMyGroupList(pc, origin);
+                GroupManager.RefreshOthersGroupList(pc);
+            }
+
+            return;
+        } 
+        
+        // only refresh the single player
+        if (this.pcToRefresh == null)
+            return;
+
+        // check that we are in the same group as when we submitted the job
+        if (GroupManager.getGroup(pc) != null && GroupManager.getGroup(pc) == grp) {
+            // refresh pc's group list for just the one player that needed refreshing
+            GroupManager.RefreshMyGroupListSinglePlayer(pc, origin, pcToRefresh);
+        }
+
+    }
+
+}
diff --git a/src/engine/jobs/RemoveCorpseJob.java b/src/engine/jobs/RemoveCorpseJob.java
new file mode 100644
index 00000000..ff4d1fbf
--- /dev/null
+++ b/src/engine/jobs/RemoveCorpseJob.java
@@ -0,0 +1,35 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.jobs;
+
+import engine.job.AbstractScheduleJob;
+import engine.objects.Corpse;
+
+public class RemoveCorpseJob extends AbstractScheduleJob {
+
+    private final Corpse corpse;
+
+    public RemoveCorpseJob(Corpse corpse) {
+        super();
+        this.corpse = corpse;
+    }
+
+    @Override
+    protected void doJob() {
+        
+        if (this.corpse != null)
+            Corpse.removeCorpse(corpse, true);
+        
+    }
+
+    @Override
+    protected void _cancelJob() {
+    }
+}
diff --git a/src/engine/jobs/SiegeSpireWithdrawlJob.java b/src/engine/jobs/SiegeSpireWithdrawlJob.java
new file mode 100644
index 00000000..cd2bbf79
--- /dev/null
+++ b/src/engine/jobs/SiegeSpireWithdrawlJob.java
@@ -0,0 +1,78 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+package engine.jobs;
+
+import engine.job.AbstractScheduleJob;
+import engine.job.JobContainer;
+import engine.job.JobScheduler;
+import engine.objects.Building;
+import engine.objects.City;
+import org.pmw.tinylog.Logger;
+
+public class SiegeSpireWithdrawlJob extends AbstractScheduleJob {
+
+	private Building spire = null;
+
+	public SiegeSpireWithdrawlJob(Building spire) {
+		super();
+		this.spire = spire;
+	}
+
+	@Override
+	protected void doJob() {
+
+		if (spire == null)
+			return;
+
+		// Early exit if someone disabled the spire
+
+		if (!spire.isSpireIsActive())
+			return;
+		
+	City buildingCity = spire.getCity();
+		
+	if (buildingCity == null)
+		return;
+	
+	
+		
+			buildingCity.transactionLock.writeLock().lock();
+			try{
+			
+		// If the spire runs out of money, disable it.
+		//*** Refactor: 5000 every 30 seconds?  Wtf?
+
+		if (!spire.hasFunds(5000)){
+			spire.disableSpire(true);
+			return;
+		}
+		if (spire.getStrongboxValue() < 5000) {
+			spire.disableSpire(true);
+			return;
+		}
+
+		// Deduct the activation cost from the strongbox and resubmit the job
+
+		if (!spire.transferGold(-5000,false))
+			return;
+		JobContainer jc = JobScheduler.getInstance().scheduleJob(new SiegeSpireWithdrawlJob(spire), 300000);
+		spire.getTimers().put("SpireWithdrawl", jc);
+		
+			}catch(Exception e){
+				Logger.error(e);
+			}finally{
+				buildingCity.transactionLock.writeLock().unlock();
+			}
+
+	}
+
+	@Override
+	protected void _cancelJob() {
+	}
+}
diff --git a/src/engine/jobs/StuckJob.java b/src/engine/jobs/StuckJob.java
new file mode 100644
index 00000000..c2a2d3ad
--- /dev/null
+++ b/src/engine/jobs/StuckJob.java
@@ -0,0 +1,119 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+ package engine.jobs;
+
+import engine.InterestManagement.WorldGrid;
+import engine.job.AbstractScheduleJob;
+import engine.math.Bounds;
+import engine.math.Vector3fImmutable;
+import engine.net.client.msg.ErrorPopupMsg;
+import engine.objects.AbstractWorldObject;
+import engine.objects.Building;
+import engine.objects.PlayerCharacter;
+import engine.server.MBServerStatics;
+
+import java.util.HashSet;
+
+public class StuckJob extends AbstractScheduleJob {
+
+	private final PlayerCharacter player;
+
+	public StuckJob(PlayerCharacter player) {
+		super();
+		this.player = player;
+	}
+
+	@Override
+	protected void doJob() {
+        
+        Vector3fImmutable stuckLoc;
+        Building stuckBuilding = null;
+ 
+        if (player == null)
+            return;
+        
+        if (player.getClientConnection() == null)
+            return;
+        
+        HashSet<AbstractWorldObject>awoList = WorldGrid.getObjectsInRangePartial(player, 150, MBServerStatics.MASK_BUILDING);
+       
+        for (AbstractWorldObject awo:awoList){
+
+        	Building toStuckOutOf = (Building)awo;
+
+        	if (toStuckOutOf.getStuckLocation() == null)
+        		continue;
+
+        	if (Bounds.collide(player.getLoc(), toStuckOutOf)){
+        		stuckBuilding = toStuckOutOf;
+        		break;
+        		
+        	}
+        }
+
+        //Could not find closest building get stuck location of nearest building.
+        
+        if (stuckBuilding == null){
+            ErrorPopupMsg.sendErrorMsg(player, "Unable to find desired location");
+    	return;
+        } else
+        	stuckLoc = stuckBuilding.getStuckLocation();
+        
+        if (stuckLoc == null){
+            ErrorPopupMsg.sendErrorMsg(player, "Unable to find desired location");
+        	return;
+        }
+        	
+        
+        player.teleport(stuckLoc);
+        
+        
+        	
+        
+        // Needs to be re-written with stuck locations
+        // Disabled for now.
+ 
+        
+        /*
+        
+        // Cannot have a null zone or player
+        
+        if (this.player == null)
+            return;
+        
+        if (ZoneManager.findSmallestZone(player.getLoc()) == null)
+            return;
+        
+        // If player is on a citygrid make sure the stuck direction
+        // is facing away from the tree
+        
+        if ((ZoneManager.findSmallestZone(player.getLoc()).isNPCCity()) ||
+            (ZoneManager.findSmallestZone(player.getLoc()).isPlayerCity())) {
+           
+            zoneVector = player.getLoc().subtract(ZoneManager.findSmallestZone(player.getLoc()).getLoc());
+            zoneVector = zoneVector.normalize();
+            
+            if (zoneVector.dot(player.getFaceDir()) > 0)
+               return;
+           
+        }
+ 
+        player.teleport(player.getLoc().add(player.getFaceDir().mult(34)));
+*/
+	}
+
+	@Override
+	protected void _cancelJob() {
+	}
+
+	private void sendTrackArrow(float rotation) {
+	}
+
+}
+
diff --git a/src/engine/jobs/SummonSendJob.java b/src/engine/jobs/SummonSendJob.java
new file mode 100644
index 00000000..ff237410
--- /dev/null
+++ b/src/engine/jobs/SummonSendJob.java
@@ -0,0 +1,54 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.jobs;
+
+import engine.job.AbstractScheduleJob;
+import engine.job.JobContainer;
+import engine.objects.PlayerCharacter;
+
+import java.util.concurrent.ConcurrentHashMap;
+
+public class SummonSendJob extends AbstractScheduleJob {
+
+    PlayerCharacter source;
+    PlayerCharacter target;
+
+    public SummonSendJob(PlayerCharacter source, PlayerCharacter target) {
+        super();
+        this.source = source;
+        this.target = target;
+    }
+
+    @Override
+    protected void doJob() {
+        
+        if (this.source == null) 
+            return;
+
+        //clear summon send timer
+        ConcurrentHashMap<String, JobContainer> timers = this.source.getTimers();
+        
+        if (timers != null && timers.containsKey("SummonSend")) 
+            timers.remove("SummonSend");
+
+    }
+
+    @Override
+    protected void _cancelJob() {
+    }
+
+    public PlayerCharacter getSource() {
+        return this.source;
+    }
+
+    public PlayerCharacter getTarget() {
+        return this.target;
+    }
+}
diff --git a/src/engine/jobs/TeleportJob.java b/src/engine/jobs/TeleportJob.java
new file mode 100644
index 00000000..7f1d43aa
--- /dev/null
+++ b/src/engine/jobs/TeleportJob.java
@@ -0,0 +1,65 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.jobs;
+
+import engine.job.AbstractScheduleJob;
+import engine.math.Vector3fImmutable;
+import engine.net.client.ClientConnection;
+import engine.net.client.msg.ErrorPopupMsg;
+import engine.objects.NPC;
+import engine.objects.PlayerCharacter;
+import engine.server.MBServerStatics;
+
+public class TeleportJob extends AbstractScheduleJob {
+
+    private final ClientConnection origin;
+    private final NPC npc;
+    private final PlayerCharacter pc;
+    private final Vector3fImmutable loc;
+    private int oldLiveCounter;
+    private final boolean setSafeMode;
+
+    public TeleportJob(PlayerCharacter pc, NPC npc, Vector3fImmutable loc, ClientConnection origin, boolean setSafeMode) {
+        super();
+        this.pc = pc;
+        this.npc = npc;
+        this.loc = loc;
+        this.origin = origin;
+        this.setSafeMode = setSafeMode;
+        if (pc != null) {
+            this.oldLiveCounter = pc.getLiveCounter();
+        }
+    }
+
+    @Override
+    protected void doJob() {
+        
+        if (this.pc == null || this.npc == null || this.origin == null)
+            return;
+
+        if (!pc.isAlive() || this.oldLiveCounter != pc.getLiveCounter())
+            return;
+        
+        if (pc.getLoc().distanceSquared2D(npc.getLoc()) > MBServerStatics.NPC_TALK_RANGE * MBServerStatics.NPC_TALK_RANGE) {
+            ErrorPopupMsg.sendErrorPopup(pc, 114);
+            return;
+        }
+
+        pc.teleport(loc);
+        
+        if (this.setSafeMode)
+            pc.setSafeMode();
+        
+    }
+
+    @Override
+    protected void _cancelJob() {
+    }
+}
diff --git a/src/engine/jobs/TrackJob.java b/src/engine/jobs/TrackJob.java
new file mode 100644
index 00000000..f46921b2
--- /dev/null
+++ b/src/engine/jobs/TrackJob.java
@@ -0,0 +1,92 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.jobs;
+
+import engine.Enum;
+import engine.gameManager.PowersManager;
+import engine.math.Vector3fImmutable;
+import engine.net.Dispatch;
+import engine.net.DispatchMessage;
+import engine.net.client.msg.TrackArrowMsg;
+import engine.objects.AbstractWorldObject;
+import engine.objects.PlayerCharacter;
+import engine.powers.ActionsBase;
+import engine.powers.EffectsBase;
+import engine.powers.PowersBase;
+import engine.powers.poweractions.TrackPowerAction;
+import engine.server.MBServerStatics;
+
+import static engine.math.FastMath.sqr;
+
+public class TrackJob extends AbstractEffectJob {
+
+    private final TrackPowerAction tpa;
+
+    public TrackJob(AbstractWorldObject source, AbstractWorldObject target, String stackType, int trains, ActionsBase action, PowersBase power, EffectsBase eb, TrackPowerAction tpa) {
+        super(source, target, stackType, trains, action, power, eb);
+        this.tpa = tpa;
+    }
+
+    @Override
+    protected void doJob() {
+        
+        if (this.tpa == null || this.target == null || this.action == null || this.source == null || this.eb == null || !(this.source instanceof PlayerCharacter))
+            return;
+
+         if (this.target.isAlive() == false) {
+            sendTrackArrow(Float.intBitsToFloat(0x7E967699));
+            PowersManager.finishEffectTime(this.source, this.target, this.action, this.trains);
+            return;
+        }
+         
+            String stackType = action.getStackType();
+
+            float distanceSquared = this.target.getLoc().distanceSquared2D(this.source.getLoc());
+
+            int speed;
+            
+            if (distanceSquared < sqr(MBServerStatics.TRACK_ARROW_FAST_RANGE))
+                speed = MBServerStatics.TRACK_ARROW_SENSITIVITY_FAST;
+             else 
+                speed = MBServerStatics.TRACK_ARROW_SENSITIVITY;
+
+            this.source.addEffect(stackType, speed, this, this.eb, this.trains);
+
+            Vector3fImmutable dir = this.target.getLoc().subtract2D(this.source.getLoc());
+            dir = dir.normalize();
+
+            sendTrackArrow(dir.getRotation());
+
+    }
+
+    @Override
+    protected void _cancelJob() {
+        sendTrackArrow(Float.intBitsToFloat(0x7E967699));
+        PowersManager.cancelEffectTime(this.source, this.target, this.power, this.eb, this.action, this.trains, this);
+    }
+
+    private void sendTrackArrow(float rotation) {
+        
+        if (this.source != null && this.source instanceof PlayerCharacter) {
+            PlayerCharacter pc = (PlayerCharacter) this.source;
+
+            if (pc == null)
+                return;
+
+            // We send track arrows over primary channel
+
+            TrackArrowMsg tam = new TrackArrowMsg(rotation);
+            Dispatch dispatch = Dispatch.borrow(pc, tam);
+            DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.PRIMARY);
+
+        }
+}
+
+}
diff --git a/src/engine/jobs/TransferStatOTJob.java b/src/engine/jobs/TransferStatOTJob.java
new file mode 100644
index 00000000..62d54a6f
--- /dev/null
+++ b/src/engine/jobs/TransferStatOTJob.java
@@ -0,0 +1,74 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.jobs;
+
+import engine.gameManager.PowersManager;
+import engine.objects.AbstractCharacter;
+import engine.objects.AbstractWorldObject;
+import engine.powers.ActionsBase;
+import engine.powers.EffectsBase;
+import engine.powers.PowersBase;
+import engine.powers.poweractions.TransferStatOTPowerAction;
+
+public class TransferStatOTJob extends AbstractEffectJob {
+
+	private final TransferStatOTPowerAction dot;
+	private int iteration = 0;
+
+	public TransferStatOTJob(AbstractWorldObject source, AbstractWorldObject target, String stackType, int trains, ActionsBase action, PowersBase power, EffectsBase eb, TransferStatOTPowerAction dot) {
+		super(source, target, stackType, trains, action, power, eb);
+		this.dot = dot;
+		this.iteration = action.getDurationInSeconds(trains) / this.dot.getNumIterations();
+	}
+
+	@Override
+	protected void doJob() {
+		if (this.dot == null || this.target == null || this.action == null || this.source == null || this.eb == null || this.action == null || this.power == null)
+			return;
+		if (!this.target.isAlive()){
+			PowersManager.finishEffectTime(this.source, this.target, this.action, this.trains);
+			return;
+		}
+			
+		iteration--;
+		
+		if (iteration < 0){
+			PowersManager.finishEffectTime(this.source, this.target, this.action, this.trains);
+			return;
+		}
+		 
+			this.skipSendEffect = true;
+			
+			String stackType = action.getStackType();
+			stackType = (stackType.equals("IgnoreStack")) ? Integer.toString(action.getUUID()) : stackType;
+			this.target.addEffect(stackType, getTickLength(), this, this.eb, this.trains);
+			if (AbstractWorldObject.IsAbstractCharacter(source))
+				this.dot.runAction((AbstractCharacter)this.source, this.target, this.trains, this.action, this.power);
+			
+	}
+
+	@Override
+	protected void _cancelJob() {
+		PowersManager.cancelEffectTime(this.source, this.target, this.power, this.eb, this.action, this.trains, this);
+	}
+
+	public int getIteration() {
+		return this.iteration;
+	}
+
+	public int getTickLength() {
+		return this.dot.getNumIterations() * 1000;
+	}
+
+	public int inc() {
+		this.iteration++;
+		return this.iteration;
+	}
+}
diff --git a/src/engine/jobs/UpdateGroupJob.java b/src/engine/jobs/UpdateGroupJob.java
new file mode 100644
index 00000000..db391898
--- /dev/null
+++ b/src/engine/jobs/UpdateGroupJob.java
@@ -0,0 +1,51 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+package engine.jobs;
+
+import engine.gameManager.GroupManager;
+import engine.job.AbstractScheduleJob;
+import engine.job.JobScheduler;
+import engine.objects.Group;
+import engine.objects.PlayerCharacter;
+import engine.server.MBServerStatics;
+import org.pmw.tinylog.Logger;
+
+public class UpdateGroupJob extends AbstractScheduleJob {
+
+    private final Group group;
+
+    public UpdateGroupJob(Group group) {
+        super();
+        this.group = group;
+    }
+
+    @Override
+    protected void doJob() {
+        
+        if (this.group == null)
+            return;
+        
+        PlayerCharacter lead = group.getGroupLead();
+        
+        if (lead == null)
+            return;
+
+        try {
+            GroupManager.RefreshWholeGroupList(lead, lead.getClientConnection(), this.group);
+        } catch (Exception e) {
+            Logger.error( e);
+        }
+
+        JobScheduler.getInstance().scheduleJob(this, MBServerStatics.UPDATE_GROUP_RATE);
+    }
+
+    @Override
+    protected void _cancelJob() {
+    }
+}
diff --git a/src/engine/jobs/UpgradeBuildingJob.java b/src/engine/jobs/UpgradeBuildingJob.java
new file mode 100644
index 00000000..510308e6
--- /dev/null
+++ b/src/engine/jobs/UpgradeBuildingJob.java
@@ -0,0 +1,55 @@
+package engine.jobs;
+
+import engine.job.AbstractScheduleJob;
+import engine.objects.Building;
+import org.pmw.tinylog.Logger;
+
+/*
+ * This class handles upgrading of buildings, swapping the
+ * appropriate mesh according to the building's blueprint.
+ * @Author
+ */
+public class UpgradeBuildingJob extends AbstractScheduleJob {
+
+	private final Building rankingBuilding;
+
+	public UpgradeBuildingJob(Building building) {
+		super();
+		this.rankingBuilding = building;
+
+	}
+
+	@Override
+	protected void doJob() {
+
+
+
+		// Must have a building to rank!
+
+		if (rankingBuilding == null) {
+			Logger.error("Attempting to rank null building");
+			return;
+		}
+
+		// Make sure the building is currently set to upgrade
+		// (Duplicate job sanity check)
+
+		if (rankingBuilding.isRanking() == false)
+			return;
+
+		// SetCurrentRank also changes the mesh and maxhp
+		// accordingly for buildings with blueprints
+
+		rankingBuilding.setRank(rankingBuilding.getRank() + 1);
+
+		// Reload the object
+
+
+
+	}
+
+	@Override
+	protected void _cancelJob() {
+	}
+
+}
diff --git a/src/engine/jobs/UpgradeNPCJob.java b/src/engine/jobs/UpgradeNPCJob.java
new file mode 100644
index 00000000..23d0c5fb
--- /dev/null
+++ b/src/engine/jobs/UpgradeNPCJob.java
@@ -0,0 +1,64 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.jobs;
+
+import engine.Enum.GameObjectType;
+import engine.InterestManagement.WorldGrid;
+import engine.job.AbstractScheduleJob;
+import engine.objects.AbstractCharacter;
+import engine.objects.Mob;
+import engine.objects.NPC;
+
+public class UpgradeNPCJob extends AbstractScheduleJob {
+
+	private final AbstractCharacter rankingAC;
+	boolean success;
+
+	public UpgradeNPCJob(AbstractCharacter ac) {
+		super();
+		this.rankingAC = ac;
+	}
+
+	@Override
+	protected void doJob() {
+
+		int newRank;
+
+		if (this.rankingAC.getObjectType() == GameObjectType.NPC){
+
+
+			if (this.rankingAC == null)  //NPC could've been removed...
+				return;
+			newRank = (this.rankingAC.getRank() * 10) + 10;
+			
+			
+
+			((NPC)this.rankingAC).setRank(newRank);
+			((NPC)this.rankingAC).setUpgradeDateTime(null);
+		}else if (this.rankingAC.getObjectType() == GameObjectType.Mob){
+			if (this.rankingAC == null)  //NPC could've been removed...
+				return;
+			newRank = (this.rankingAC.getRank() * 10) + 10;
+
+
+
+			((Mob)this.rankingAC).setRank(newRank);
+			Mob.setUpgradeDateTime((Mob)this.rankingAC, null);
+			WorldGrid.updateObject(this.rankingAC);
+
+		}
+
+	}
+
+	@Override
+	protected void _cancelJob() {
+	}
+
+}
diff --git a/src/engine/jobs/UseItemJob.java b/src/engine/jobs/UseItemJob.java
new file mode 100644
index 00000000..5f2be44b
--- /dev/null
+++ b/src/engine/jobs/UseItemJob.java
@@ -0,0 +1,57 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.jobs;
+
+import engine.gameManager.PowersManager;
+import engine.job.AbstractScheduleJob;
+import engine.math.Vector3fImmutable;
+import engine.objects.AbstractCharacter;
+import engine.objects.AbstractWorldObject;
+import engine.powers.PowersBase;
+
+public class UseItemJob extends AbstractScheduleJob {
+
+    private final AbstractCharacter ac;
+    private final AbstractWorldObject target;
+    private final PowersBase pb;
+    private final int trains;
+    private final int liveCounter;
+
+    public UseItemJob(AbstractCharacter ac, AbstractWorldObject target, PowersBase pb, int trains, int liveCounter) {
+        super();
+        this.ac = ac;
+        this.target = target;
+        this.pb = pb;
+        this.trains = trains;
+        this.liveCounter = liveCounter;
+    }
+
+    @Override
+    protected void doJob() {
+        PowersManager.finishApplyPower(ac, target, Vector3fImmutable.ZERO, pb, trains, liveCounter);
+    }
+
+    @Override
+    protected void _cancelJob() {
+    	this.ac.setItemCasting(false);
+    }
+
+    public PowersBase getPowersBase() {
+        return this.pb;
+    }
+
+    public int getTrains() {
+        return this.trains;
+    }
+
+    public AbstractWorldObject getTarget() {
+        return this.target;
+    }
+}
diff --git a/src/engine/jobs/UseMobPowerJob.java b/src/engine/jobs/UseMobPowerJob.java
new file mode 100644
index 00000000..3b827a34
--- /dev/null
+++ b/src/engine/jobs/UseMobPowerJob.java
@@ -0,0 +1,57 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.jobs;
+
+import engine.gameManager.PowersManager;
+import engine.job.AbstractScheduleJob;
+import engine.net.client.msg.PerformActionMsg;
+import engine.objects.Mob;
+import engine.powers.PowersBase;
+
+public class UseMobPowerJob extends AbstractScheduleJob {
+
+    private final Mob caster;
+    private final PerformActionMsg msg;
+    private final int token;
+    private final PowersBase pb;
+    private final int casterLiveCounter;
+    private final int targetLiveCounter;
+
+    public UseMobPowerJob(Mob caster, PerformActionMsg msg, int token, PowersBase pb, int casterLiveCounter, int targetLiveCounter) {
+        super();
+        this.caster = caster;
+        this.msg = msg;
+        this.token = token;
+        this.pb = pb;
+        this.casterLiveCounter = casterLiveCounter;
+        this.targetLiveCounter = targetLiveCounter;
+    }
+
+    @Override
+    protected void doJob() {
+        PowersManager.finishUseMobPower(this.msg, this.caster, casterLiveCounter, targetLiveCounter);
+    }
+
+    @Override
+    protected void _cancelJob() {
+    }
+
+    public PowersBase getPowersBase() {
+        return this.pb;
+    }
+
+    public int getToken() {
+        return this.token;
+    }
+
+    public PerformActionMsg getMsg() {
+        return this.msg;
+    }
+}
diff --git a/src/engine/jobs/UsePowerJob.java b/src/engine/jobs/UsePowerJob.java
new file mode 100644
index 00000000..c9d4c18f
--- /dev/null
+++ b/src/engine/jobs/UsePowerJob.java
@@ -0,0 +1,59 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.jobs;
+
+import engine.gameManager.PowersManager;
+import engine.job.AbstractScheduleJob;
+import engine.net.client.msg.PerformActionMsg;
+import engine.objects.PlayerCharacter;
+import engine.powers.PowersBase;
+
+public class UsePowerJob extends AbstractScheduleJob {
+
+    private final PlayerCharacter pc;
+    private final PerformActionMsg msg;
+    private final int token;
+    private final PowersBase pb;
+    private final int casterLiveCounter;
+    private final int targetLiveCounter;
+
+    public UsePowerJob(PlayerCharacter pc, PerformActionMsg msg, int token, PowersBase pb, int casterLiveCounter, int targetLiveCounter) {
+        super();
+        this.pc = pc;
+        this.msg = msg;
+        this.token = token;
+        this.pb = pb;
+        this.casterLiveCounter = casterLiveCounter;
+        this.targetLiveCounter = targetLiveCounter;
+    }
+
+    @Override
+    protected void doJob() {
+        PowersManager.finishUsePower(this.msg, this.pc, casterLiveCounter, targetLiveCounter);
+    }
+
+    @Override
+    protected void _cancelJob() {
+        //cast stopped early, reset recycle timer
+        PowersManager.finishRecycleTime(this.msg, this.pc, true);
+    }
+
+    public PowersBase getPowersBase() {
+        return this.pb;
+    }
+
+    public int getToken() {
+        return this.token;
+    }
+
+    public PerformActionMsg getMsg() {
+        return this.msg;
+    }
+}
diff --git a/src/engine/loot/LootGroup.java b/src/engine/loot/LootGroup.java
new file mode 100644
index 00000000..8d9ab7bf
--- /dev/null
+++ b/src/engine/loot/LootGroup.java
@@ -0,0 +1,114 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+package engine.loot;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+/**
+ * Data storage object for Loot System.
+ * Holds row in the lootGroup database table
+ */
+public class LootGroup {
+
+    private final int groupID;
+    private final String groupName;
+    private final int minRoll;
+    private final int maxRoll;
+    private final int lootTableID;
+    private final String lootTableName;
+    private final int pMod;
+    private final int pModTableID;
+    private final int sMod;
+    private final int sModTableID;
+
+    public LootGroup(ResultSet rs) throws SQLException {
+        
+        this.groupID = rs.getInt("groupID");
+        this.groupName = rs.getString("groupName");
+        this.minRoll = rs.getInt("minRoll");
+        this.maxRoll = rs.getInt("maxRoll");
+        this.lootTableID = rs.getInt("lootTableID");
+        this.lootTableName = rs.getString("lootTableName");
+        this.pMod = rs.getInt("pMod");
+        this.pModTableID = rs.getInt("pModTableID");
+        this.sMod = rs.getInt("sMod");
+        this.sModTableID = rs.getInt("sModTableID");
+    }
+    /**
+     * @return the groupID
+     */
+    public int getGroupID() {
+        return groupID;
+    }
+
+    /**
+     * @return the groupName
+     */
+    public String getGroupName() {
+        return groupName;
+    }
+
+    /**
+     * @return the minRoll
+     */
+    public int getMinRoll() {
+        return minRoll;
+    }
+
+    /**
+     * @return the maxRoll
+     */
+    public int getMaxRoll() {
+        return maxRoll;
+    }
+
+    /**
+     * @return the lootTableID
+     */
+    public int getLootTableID() {
+        return lootTableID;
+    }
+
+    /**
+     * @return the lootTableName
+     */
+    public String getLootTableName() {
+        return lootTableName;
+    }
+
+    /**
+     * @return the pMod
+     */
+    public int getpMod() {
+        return pMod;
+    }
+
+    /**
+     * @return the pModTableID
+     */
+    public int getpModTableID() {
+        return pModTableID;
+    }
+
+    /**
+     * @return the sMod
+     */
+    public int getsMod() {
+        return sMod;
+    }
+
+    /**
+     * @return the sModTableID
+     */
+    public int getsModTableID() {
+        return sModTableID;
+    }
+
+}
diff --git a/src/engine/loot/LootManager.java b/src/engine/loot/LootManager.java
new file mode 100644
index 00000000..798dc55c
--- /dev/null
+++ b/src/engine/loot/LootManager.java
@@ -0,0 +1,231 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+package engine.loot;
+
+import engine.gameManager.DbManager;
+import engine.objects.Item;
+
+import java.util.HashMap;
+import java.util.TreeMap;
+import java.util.concurrent.ThreadLocalRandom;
+
+/**
+ * Class contains static methods for data from Magicbane's loot tables
+ */
+public class LootManager {
+
+    private static final HashMap<Integer, TreeMap<Integer, LootGroup>> _lootGroups = new HashMap<>();
+    private static final HashMap<Integer, TreeMap<Integer, LootTable>> _lootTables = new HashMap<>();
+    private static final HashMap<Integer, TreeMap<Integer, ModifierGroup>> _modGroups = new HashMap<>();
+    private static final HashMap<Integer, TreeMap> _modTables = new HashMap<>();
+
+    private LootManager() {
+
+    }
+
+// Method adds a lootGroup to the class's internal collection
+// and configures the treemap accordingly
+    
+    public static void addLootGroup(LootGroup lootGroup) {
+
+    // If entry for this lootGroup does not currently exist
+    // we need to create one.
+        
+        if (_lootGroups.containsKey(lootGroup.getGroupID()) == false)
+            _lootGroups.put(lootGroup.getGroupID(), new TreeMap<>());
+
+    // Add this lootgroup to the appropriate treemap
+        
+        _lootGroups.get(lootGroup.getGroupID()).put(lootGroup.getMaxRoll(), lootGroup);
+
+    }
+
+    public static void addLootTable(engine.loot.LootTable lootTable) {
+
+    // If entry for this lootTabe does not currently exist
+    // we need to create one.
+        
+        if (_lootTables.containsKey(lootTable.getLootTable()) == false)
+            _lootTables.put(lootTable.getLootTable(),
+                    new TreeMap<>());
+
+    // Add this lootTable to the appropriate treemap
+        
+        _lootTables.get(lootTable.getLootTable()).put(lootTable.getMaxRoll(), lootTable);
+
+    }
+
+    public static void addModifierGroup(ModifierGroup modGroup) {
+
+    // If entry for this lootTabe does not currently exist
+    // we need to create one.
+        
+        if (_modGroups.containsKey(modGroup.getModGroup()) == false)
+            _modGroups.put(modGroup.getModGroup(),
+                    new TreeMap<>());
+
+    // Add this lootTable to the appropriate treemap
+        
+        _modGroups.get(modGroup.getModGroup()).put(modGroup.getMaxRoll(), modGroup);
+
+    }
+
+    public static void addModifierTable(ModifierTable modTable) {
+
+    // If entry for this lootTabe does not currently exist
+    // we need to create one.
+        
+        if (_modTables.containsKey(modTable.getModTable()) == false)
+            _modTables.put(modTable.getModTable(),
+                    new TreeMap<Float, ModifierGroup>());
+
+    // Add this lootTable to the appropriate treemap
+        
+        _modTables.get(modTable.getModTable()).put(modTable.getMaxRoll(), modTable);
+
+    }
+
+    /* Mainline interfaces for this class.  Methods below retrieve
+     * entries from the loottable by random number and range.
+    */
+    
+    public static LootGroup getRandomLootGroup(int lootGroupID, int randomRoll) {
+
+        if ((randomRoll < 1) || (randomRoll > 100))
+            return null;
+
+    // Get random lootGroup for this roll
+        
+        return _lootGroups.get(lootGroupID).floorEntry(randomRoll).getValue();
+
+    }
+
+    public static engine.loot.LootTable getRandomLootTable(int lootTableID, int randomRoll) {
+
+        if ((randomRoll < 1) || (randomRoll > 100))
+            return null;
+
+    // Get random lootTable for this roll
+        
+        return _lootTables.get(lootTableID).floorEntry(randomRoll).getValue();
+
+    }
+
+    public static ModifierGroup getRandomModifierGroup(int modGroupID, int randomRoll) {
+
+        if ((randomRoll < 1) || (randomRoll > 100))
+            return null;
+
+    // Get random modGroup for this roll
+        
+        return _modGroups.get(modGroupID).floorEntry(randomRoll).getValue();
+
+    }
+
+    public static ModifierTable getRandomModifierTable(int modTableID, float randomRoll) {
+
+        if ((randomRoll < 1.0f))
+            return null;
+
+        // Roll is outside of range
+        
+        if (randomRoll > getMaxRangeForModifierTable(modTableID))
+            return null;
+
+    // Get random lootGroup for this roll
+        
+        return (ModifierTable) _modTables.get(modTableID).floorEntry(randomRoll).getValue();
+
+    }
+    
+    // Returns minmum rolling range for a particular modifier table entry
+    
+    public static float getMinRangeForModifierTable(int modTableID) {
+
+        ModifierTable outTable;
+
+        outTable = (ModifierTable) _modTables.get(modTableID).firstEntry();
+
+        return outTable.getMinRoll();
+
+    }
+
+    // Returns maximum rolling range for a particular modifier table entry
+    
+    public static float getMaxRangeForModifierTable(int modTableID) {
+
+        ModifierTable outTable;
+
+        outTable = (ModifierTable) _modTables.get(modTableID).lastEntry();
+
+        return outTable.getMaxRoll();
+    }
+
+    public static Item getRandomItemFromLootGroup(int lootGroupID, int randomRoll) {
+    
+        Item outItem = null;
+        LootGroup lootGroup;
+        LootTable lootTable;
+        ModifierGroup modGroup;
+        ModifierTable prefixTable;
+        ModifierTable suffixTable;
+        
+        // Retrieve a random loot group
+        
+        lootGroup = getRandomLootGroup(lootGroupID, randomRoll);
+        
+        if (lootGroup == null)
+            return null;
+        
+        // Retrieve a random loot table
+        
+        lootTable = getRandomLootTable(lootGroup.getLootTableID(), ThreadLocalRandom.current().nextInt(100));
+        
+        if (lootTable == null)
+            return null;
+        
+        // Retrieve a random prefix
+        
+        modGroup = getRandomModifierGroup(lootGroup.getpModTableID(), ThreadLocalRandom.current().nextInt(100));
+        
+        if (modGroup == null)
+            return null;
+        
+        prefixTable = getRandomModifierTable(modGroup.getSubTableID(), ThreadLocalRandom.current().nextFloat() * getMaxRangeForModifierTable(lootGroup.getpModTableID()));
+        
+        if (prefixTable == null)
+            return null;
+        
+        // Retrieve a random suffix
+        
+        modGroup = getRandomModifierGroup(lootGroup.getsModTableID(), ThreadLocalRandom.current().nextInt(100));
+        
+        if (modGroup == null)
+            return null;
+        
+        suffixTable = getRandomModifierTable(modGroup.getSubTableID(), ThreadLocalRandom.current().nextFloat() * getMaxRangeForModifierTable(lootGroup.getsModTableID()));
+        
+        if (suffixTable == null)
+            return null;
+        
+        // Create the item!
+        
+        return outItem;
+    }
+    
+    // Bootstrap routine to load loot data from database
+    
+    public static void loadLootData() {
+        DbManager.LootQueries.LOAD_ALL_LOOTGROUPS();
+        DbManager.LootQueries.LOAD_ALL_LOOTTABLES();
+        DbManager.LootQueries.LOAD_ALL_MODGROUPS();
+        DbManager.LootQueries.LOAD_ALL_MODTABLES();
+    }
+
+}
diff --git a/src/engine/loot/LootTable.java b/src/engine/loot/LootTable.java
new file mode 100644
index 00000000..b2bd29d1
--- /dev/null
+++ b/src/engine/loot/LootTable.java
@@ -0,0 +1,98 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+package engine.loot;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+/**
+ * Data storage object for Loot System.
+ * Holds row in the lootTable database table
+ */
+public class LootTable {
+
+    private final int lootTale;
+    private final String tableName;
+    private final String itemName;
+    private final int minRoll;
+    private final int maxRoll;
+    private final int itemBaseUUID;
+    private final int minSpawn;
+    private final int maxSpawn;
+
+    public LootTable(ResultSet rs) throws SQLException {
+        
+        this.lootTale = rs.getInt("lootTable");
+        this.tableName = rs.getString("tableName");
+        this.itemName = rs.getString("itemName");
+        this.minRoll = rs.getInt("minRoll");
+        this.maxRoll = rs.getInt("maxRoll");
+        this.itemBaseUUID = rs.getInt("itemBaseUUID");
+        this.minSpawn = rs.getInt("minSpawn");
+        this.maxSpawn = rs.getInt("maxSpawn");
+                        
+    }
+
+    /**
+     * @return the lootTale
+     */
+    public int getLootTable() {
+        return lootTale;
+    }
+
+    /**
+     * @return the tableName
+     */
+    public String getTableName() {
+        return tableName;
+    }
+
+    /**
+     * @return the itemName
+     */
+    public String getItemName() {
+        return itemName;
+    }
+
+    /**
+     * @return the minRoll
+     */
+    public int getMinRoll() {
+        return minRoll;
+    }
+
+    /**
+     * @return the maxRoll
+     */
+    public int getMaxRoll() {
+        return maxRoll;
+    }
+
+    /**
+     * @return the itemBaseUUID
+     */
+    public int getItemBaseUUID() {
+        return itemBaseUUID;
+    }
+
+    /**
+     * @return the minSpawn
+     */
+    public int getMinSpawn() {
+        return minSpawn;
+    }
+
+    /**
+     * @return the maxSpawn
+     */
+    public int getMaxSpawn() {
+        return maxSpawn;
+    }
+    
+}
diff --git a/src/engine/loot/ModifierGroup.java b/src/engine/loot/ModifierGroup.java
new file mode 100644
index 00000000..ad854bfe
--- /dev/null
+++ b/src/engine/loot/ModifierGroup.java
@@ -0,0 +1,78 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+package engine.loot;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+/**
+ * Data storage object for Loot System.
+ * Holds row in the modGroup database table
+ */
+public class ModifierGroup {
+
+    private final int modGroup;
+    private final String groupName;
+    private final int minRoll;
+    private final int maxRoll;
+    private final int subTableID;
+    private final String subTableName;
+
+    public ModifierGroup(ResultSet rs) throws SQLException {
+        
+        this.modGroup = rs.getInt("modGroup");
+        this.groupName = rs.getString("groupName");
+        this.minRoll = rs.getInt("minRoll");
+        this.maxRoll = rs.getInt("maxRoll");
+        this.subTableID = rs.getInt("subTableID");
+        this.subTableName = rs.getString("subTableName");                
+    }
+
+    /**
+     * @return the modGroup
+     */
+    public int getModGroup() {
+        return modGroup;
+    }
+
+    /**
+     * @return the groupName
+     */
+    public String getGroupName() {
+        return groupName;
+    }
+
+    /**
+     * @return the minRoll
+     */
+    public int getMinRoll() {
+        return minRoll;
+    }
+
+    /**
+     * @return the maxRoll
+     */
+    public int getMaxRoll() {
+        return maxRoll;
+    }
+
+    /**
+     * @return the subTableID
+     */
+    public int getSubTableID() {
+        return subTableID;
+    }
+
+    /**
+     * @return the subTableName
+     */
+    public String getSubTableName() {
+        return subTableName;
+    }
+}
\ No newline at end of file
diff --git a/src/engine/loot/ModifierTable.java b/src/engine/loot/ModifierTable.java
new file mode 100644
index 00000000..5ca83431
--- /dev/null
+++ b/src/engine/loot/ModifierTable.java
@@ -0,0 +1,88 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+package engine.loot;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+/**
+ * Data storage object for Loot System.
+ * Holds row in the modTables database table
+ */
+public class ModifierTable{
+
+    private final int modTable;
+    private final String tableName;
+    private final float minRoll;
+    private final float maxRoll;
+    private final String action;
+    private final int level;
+    private final int value;
+    
+    public ModifierTable(ResultSet rs) throws SQLException {
+        
+        this.modTable = rs.getInt("modTable");
+        this.tableName = rs.getString("tableName");
+        this.minRoll = rs.getInt("minRoll");
+        this.maxRoll = rs.getInt("maxRoll");
+        this.action = rs.getString("action"); 
+        this.level = rs.getInt("level");
+        this.value = rs.getInt("value");            
+    }
+
+    /**
+     * @return the modTable
+     */
+    public int getModTable() {
+        return modTable;
+    }
+
+    /**
+     * @return the tableName
+     */
+    public String getTableName() {
+        return tableName;
+    }
+
+    /**
+     * @return the minRoll
+     */
+    public float getMinRoll() {
+        return minRoll;
+    }
+
+    /**
+     * @return the maxRoll
+     */
+    public float getMaxRoll() {
+        return maxRoll;
+    }
+
+    /**
+     * @return the action
+     */
+    public String getAction() {
+        return action;
+    }
+
+    /**
+     * @return the level
+     */
+    public int getLevel() {
+        return level;
+    }
+
+    /**
+     * @return the value
+     */
+    public int getValue() {
+        return value;
+    }
+
+}
diff --git a/src/engine/math/AtomicFloat.java b/src/engine/math/AtomicFloat.java
new file mode 100644
index 00000000..b5a1a808
--- /dev/null
+++ b/src/engine/math/AtomicFloat.java
@@ -0,0 +1,101 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+package engine.math;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+public class AtomicFloat {
+
+    private final AtomicInteger fl;
+
+    public AtomicFloat() {
+        fl = new AtomicInteger(Float.floatToIntBits(0f));
+    }
+
+    public AtomicFloat(float value) {
+        fl = new AtomicInteger(Float.floatToIntBits(value));
+    }
+
+    public float addAndGet(float delta) {
+        int oldValue = fl.get();
+        while (!fl.compareAndSet(oldValue, Float.floatToIntBits(Float.intBitsToFloat(oldValue) + delta))) {
+            oldValue = fl.get();
+        }
+        return fl.get();
+    }
+
+    public boolean compareAndSet(float oldVal, float newVal) {
+        return fl.compareAndSet(Float.floatToIntBits(oldVal), Float.floatToIntBits(newVal));
+    }
+
+    public float decrementAndGet() {
+        int oldValue = fl.get();
+        while (!fl.compareAndSet(oldValue, Float.floatToIntBits(Float.intBitsToFloat(oldValue) - 1f))) {
+            oldValue = fl.get();
+        }
+        return fl.get();
+    }
+
+    public float get() {
+        return Float.intBitsToFloat(fl.get());
+    }
+
+    public float getAndAdd(float delta) {
+        int oldValue = fl.get();
+        while (!fl.compareAndSet(oldValue, Float.floatToIntBits(Float.intBitsToFloat(oldValue) + delta))) {
+            oldValue = fl.get();
+        }
+        return oldValue;
+    }
+
+    public float getAndIncrement() {
+        int oldValue = fl.get();
+        while (!fl.compareAndSet(oldValue, Float.floatToIntBits(Float.intBitsToFloat(oldValue) + 1f))) {
+            oldValue = fl.get();
+        }
+        return oldValue;
+    }
+
+    public float getAndDecrement(float delta) {
+        int oldValue = fl.get();
+        while (!fl.compareAndSet(oldValue, Float.floatToIntBits(Float.intBitsToFloat(oldValue) - 1f))) {
+            oldValue = fl.get();
+        }
+        return oldValue;
+    }
+
+    public float getAndSet(float value) {
+        return Float.intBitsToFloat(fl.getAndSet(Float.floatToIntBits(value)));
+    }
+
+    public float incrementAndGet() {
+        int oldValue = fl.get();
+        while (!fl.compareAndSet(oldValue, Float.floatToIntBits(Float.intBitsToFloat(oldValue) + 1f))) {
+            oldValue = fl.get();
+        }
+        return fl.get();
+    }
+
+    public void lazySet(float value) {
+        fl.lazySet(Float.floatToIntBits(value));
+    }
+
+    public void set(float value) {
+        fl.set(Float.floatToIntBits(value));
+    }
+
+    @Override
+    public String toString() {
+        return fl.toString();
+    }
+
+    public boolean weakCompareAndSet(float oldVal, float newVal) {
+        return fl.weakCompareAndSet(Float.floatToIntBits(oldVal), Float.floatToIntBits(newVal));
+    }
+}
diff --git a/src/engine/math/Bounds.java b/src/engine/math/Bounds.java
new file mode 100644
index 00000000..f295f1dc
--- /dev/null
+++ b/src/engine/math/Bounds.java
@@ -0,0 +1,583 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+package engine.math;
+
+import engine.InterestManagement.WorldGrid;
+import engine.gameManager.ZoneManager;
+import engine.net.client.msg.PlaceAssetMsg.PlacementInfo;
+import engine.objects.*;
+import engine.server.MBServerStatics;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.concurrent.LinkedBlockingQueue;
+
+/**
+ * This class contains all methods of storing bounds
+ * information within MagicBane and performing collision
+ * detection against them.
+ * <p>
+ * These objects are essentially an AABB, given rotations
+ * in MagicBane for placed objects come in a quantum of 90.
+ */
+
+public class Bounds {
+
+	private static final LinkedBlockingQueue<Bounds> boundsPool = new LinkedBlockingQueue<>();
+	public static HashMap<Integer,MeshBounds> meshBoundsCache = new HashMap<>();
+
+	private Vector2f origin = new Vector2f();
+	private Vector2f halfExtents = new Vector2f();
+	private float rotation;
+	private float rotationDegrees = 0;
+	private Quaternion quaternion;
+	private boolean flipExtents;
+
+	private ArrayList<Regions> regions = new ArrayList<>();
+	private ArrayList<Colliders> colliders = new ArrayList<>();
+
+	// Default constructor
+
+	public Bounds() {
+
+		origin.zero();
+		halfExtents.zero();
+		rotation = 0.0f;
+		flipExtents = false;
+	}
+
+	public static Bounds borrow() {
+		Bounds outBounds;
+
+		outBounds = boundsPool.poll();
+
+		if (outBounds == null)
+			outBounds = new Bounds();
+
+		return outBounds;
+	}
+
+	public void release() {
+		Bounds.zero(this);
+		boundsPool.add(this);
+
+	}
+
+	public void setBounds(Vector2f origin, Vector2f extents, float rotation) {
+
+		this.origin.set(origin);
+		this.halfExtents.set(extents);
+		this.rotation = rotation;
+		
+		this.flipExtents = Bounds.calculateFlipExtents(this);
+
+	}
+
+	public void setBounds(PlacementInfo sourceInfo) {
+
+		Blueprint sourceBlueprint;
+
+		sourceBlueprint = Blueprint.getBlueprint(sourceInfo.getBlueprintUUID());
+		this.origin.set(sourceInfo.getLoc().x, sourceInfo.getLoc().z);
+		this.halfExtents.set(sourceBlueprint.getExtents());
+		
+		this.quaternion = new Quaternion(sourceInfo.getRot().x, sourceInfo.getRot().y,sourceInfo.getRot().z,sourceInfo.getW());
+			this.rotation = sourceInfo.getRot().y;
+		this.flipExtents = Bounds.calculateFlipExtents(this);
+
+	}
+
+	public void setBounds(Bounds sourceBounds) {
+
+		origin.set(sourceBounds.origin);
+		halfExtents.set(sourceBounds.halfExtents);
+		this.rotation = sourceBounds.rotation;
+		this.flipExtents = sourceBounds.flipExtents;
+
+	}
+
+	public void setBounds(AbstractCharacter sourcePlayer) {
+
+		this.origin.set(sourcePlayer.getLoc().x, sourcePlayer.getLoc().z);
+		this.halfExtents.set(.5f, .5f);
+		this.rotation = 0;
+		this.flipExtents = false;
+
+	}
+
+	public void setBounds(Vector3fImmutable sourceLocation) {
+
+		this.origin.set(sourceLocation.x, sourceLocation.z);
+		this.halfExtents.set(.5f, .5f);
+		this.rotation = 0;
+		this.flipExtents = false;
+
+	}
+
+	public void setBounds(Vector3fImmutable sourceLocation, float halfExtent) {
+
+		this.origin.set(sourceLocation.x, sourceLocation.z);
+		this.halfExtents.set(halfExtent, halfExtent);
+		this.rotation = 0;
+		this.flipExtents = false;
+
+	}
+
+	public void setBounds(Building building) {
+
+		Blueprint blueprint;
+		MeshBounds meshBounds;
+		int halfExtentX;
+		int halfExtentY;
+		// Need a blueprint for proper bounds
+
+		blueprint = building.getBlueprint();
+		
+		this.quaternion = new Quaternion(building.getRot().x, building.getRot().y,building.getRot().z,building.getw());
+
+		// Calculate Bounds for non-blueprint objects
+
+		if (blueprint == null) {
+
+			// If a mesh is a non-blueprint structure then we calculate
+			// it's bounding box based upon defaults from original source
+			// lookup.
+
+            meshBounds = meshBoundsCache.get(building.getMeshUUID());
+			this.origin.set(building.getLoc().x, building.getLoc().z);
+          
+
+            // Magicbane uses half halfExtents
+
+            if (meshBounds == null){
+            	halfExtentX = 1;
+            	halfExtentY = 1;
+            }else{
+            	
+            	float halfExtent = Math.max((meshBounds.maxX - meshBounds.minX)/2, (meshBounds.maxZ - meshBounds.minZ) /2);
+            	halfExtentX = Math.round(halfExtent);
+    			halfExtentY = Math.round(halfExtent);
+            }
+			
+
+			// The rotation is reset after the new aabb is calculated.
+            
+			this.rotation = building.getRot().y;
+            // Caclculate and set the new half halfExtents for the rotated bounding box
+			// and reset the rotation to 0 for this bounds.
+			this.halfExtents.set(halfExtentX, (halfExtentY));
+			this.rotation = 0;
+
+			this.setRegions(building);
+			this.setColliders(building);
+			return;
+		}
+
+		this.origin.set(building.getLoc().x, building.getLoc().z);
+		this.rotation = building.getRot().y;
+		this.halfExtents.set(blueprint.getExtents());
+		this.flipExtents = Bounds.calculateFlipExtents(this);
+
+	
+		this.setRegions(building);
+		this.setColliders(building);
+
+	}
+
+
+
+
+
+
+	// Identity Bounds at location
+	public static void zero(Bounds bounds) {
+		bounds.origin.zero();
+		bounds.halfExtents.zero();
+		bounds.rotation = 0.0f;
+		bounds.flipExtents = false;
+	}
+
+	public static boolean collide(Vector3fImmutable location, Bounds targetBounds) {
+		
+		if (targetBounds == null)
+			return false;
+
+		boolean collisionState = false;
+		Bounds identityBounds =  Bounds.borrow();
+		identityBounds.setBounds(location);
+
+		collisionState = collide(targetBounds, identityBounds, 0.0f);
+		identityBounds.release();
+		return collisionState;
+	}
+
+
+	public static boolean collide(Vector3fImmutable location, Building targetBuilding) {
+
+		boolean collisionState = false;
+		Bounds targetBounds = targetBuilding.getBounds();
+		
+		if (targetBounds == null)
+			return false;
+		Bounds identityBounds =  Bounds.borrow();
+		identityBounds.setBounds(location);
+
+		collisionState = collide(targetBounds, identityBounds, 0.1f);
+		identityBounds.release();
+		return collisionState;
+	}
+
+	public static boolean collide(Bounds sourceBounds, Bounds targetBounds, float threshold) {
+
+		float deltaX;
+		float deltaY;
+		float extentX;
+		float extentY;
+		float sourceExtentX;
+		float sourceExtentY;
+		float targetExtentX;
+		float targetExtentY;
+
+		deltaX = Math.abs(sourceBounds.origin.x - targetBounds.origin.x);
+		deltaY = Math.abs(sourceBounds.origin.y - targetBounds.origin.y);
+
+		if (sourceBounds.flipExtents) {
+			sourceExtentX = sourceBounds.halfExtents.y;
+			sourceExtentY = sourceBounds.halfExtents.x;
+		}
+		else {
+			sourceExtentX = sourceBounds.halfExtents.x;
+			sourceExtentY = sourceBounds.halfExtents.y;
+		}
+		if (targetBounds.flipExtents) {
+			targetExtentX = targetBounds.halfExtents.y;
+			targetExtentY = targetBounds.halfExtents.x;
+		}
+		else {
+			targetExtentX = targetBounds.halfExtents.x;
+			targetExtentY = targetBounds.halfExtents.y;
+		}
+
+		extentX = sourceExtentX + targetExtentX;
+		extentY = sourceExtentY + targetExtentY;
+
+		// Return false on overlapping edge cases
+		if ((Math.abs(deltaX + threshold) < extentX))
+			if ((Math.abs(deltaY + threshold) < extentY))
+				return true;
+
+		return false;
+
+    }
+
+	// Method detects overlap of two given Bounds objects.
+	// Just your generic AABB collision algorythm.
+
+	public static boolean collide(PlacementInfo sourceInfo, Building targetBuilding) {
+
+		Bounds sourceBounds;
+		Bounds targetBounds;
+
+		boolean collisionState = false;
+
+		// Early exit sanity check.  Can't quite collide against nothing
+
+		if ((sourceInfo == null) || (targetBuilding == null))
+			return false;
+
+		sourceBounds = Bounds.borrow();
+		sourceBounds.setBounds(sourceInfo);
+
+		// WARNING: DO NOT EVER RELEASE THESE WORLDOBJECT BOUNDS
+		// THEY ARE NOT IMMUTABLE
+
+		targetBounds = targetBuilding.getBounds();
+
+		// If target building has no bounds, we certainly cannot collide.
+		// Note: We remove and release bounds objects to the pool when
+		// buildings are destroyed.
+
+		if (targetBounds == null)
+			return false;
+
+		collisionState = collide(sourceBounds, targetBounds,.1f);
+
+		// Release bounds and return collision state
+
+		sourceBounds.release();
+		return collisionState;
+	}
+	
+	public static boolean collide(Bounds bounds, Vector3fImmutable start, Vector3fImmutable end) {
+		boolean collide = false;
+		for (Colliders collider: bounds.colliders) {
+			
+			collide = linesTouching(collider.startX, collider.startY, collider.endX,collider.endY, start.x, start.z, end.x,end.z);
+			
+			if (collide)
+				break;
+			
+			
+		}
+		
+		return collide;
+	}
+	
+	//used for wall collision with players.
+	public static Vector3fImmutable PlayerBuildingCollisionPoint(PlayerCharacter player, Vector3fImmutable start, Vector3fImmutable end) {
+		Vector3fImmutable collidePoint = null;
+	
+		//player can fly over walls when at max altitude. skip collision checks.
+		if (player.getAltitude() >= 60)
+			return null;
+		
+		
+				float distance = player.getLoc().distance2D(end);
+				// Players should not be able to move more than 2000 units at a time, stop them dead in their tracks if they do. (hacks)
+				if (distance > 2000)
+					return player.getLoc();
+					
+
+				
+					HashSet<AbstractWorldObject> awoList = WorldGrid.getObjectsInRangePartial(player, distance + 1000, MBServerStatics.MASK_BUILDING);
+					float collideDistance = 0;
+					float lastDistance = -1;
+
+
+					for (AbstractWorldObject awo : awoList) {
+						
+						Building building = (Building)awo;
+						
+						
+
+						//player is inside building region, skip collision check. we only do collision from the outside. 
+						if (player.getRegion() != null && player.getRegion().parentBuildingID == building.getObjectUUID())
+							continue;
+						if (building.getBounds().colliders == null)
+							continue;
+						
+						for (Colliders collider: building.getBounds().colliders) {
+							
+							//links are what link together buildings, allow players to run through them only if they are in a building already.
+							if (collider.isLink() && player.getRegion() != null)
+								continue;
+							if (collider.getDoorID() != 0 && building.isDoorOpen(collider.getDoorID()))
+								continue;
+							
+							Vector3fImmutable tempCollidePoint = lineIntersection(collider.startX, collider.startY, collider.endX,collider.endY, start.x, start.z, end.x,end.z);
+							
+							//didnt collide, skip distance checks.
+							if (tempCollidePoint == null)
+								continue;
+				
+							//first collision detection, inititialize all variables.
+							if (lastDistance == -1) {
+								 collideDistance = start.distance2D(tempCollidePoint);
+								 lastDistance = collideDistance;
+								 collidePoint = tempCollidePoint;
+							}else
+								//get closest collide point.
+								 collideDistance = start.distance2D(tempCollidePoint);
+							
+							if (collideDistance < lastDistance) {
+								lastDistance = collideDistance;
+								 collidePoint = tempCollidePoint;
+							}
+						}
+					}
+						
+		 
+					
+					//
+					if (collidePoint != null) {
+						
+						if(collideDistance >= 2)
+						 collidePoint = player.getFaceDir().scaleAdd(-2f, new Vector3fImmutable((float) collidePoint.getX(), end.y, (float) collidePoint.getZ()));
+						else
+							collidePoint = player.getLoc();
+					}
+	
+		
+		return collidePoint;
+	}
+	
+	public static boolean linesTouching(float x1, float y1, float x2, float y2, float x3, float y3, float x4, float y4) {
+		  float denominator = ((x2 - x1) * (y4 - y3)) - ((y2 - y1) * (x4 - x3));
+		  float numerator1 = ((y1 - y3) * (x4 - x3)) - ((x1 - x3) * (y4 - y3));
+		  float numerator2 = ((y1 - y3) * (x2 - x1)) - ((x1 - x3) * (y2 - y1));
+
+		  // Detect coincident lines (has a problem, read below)
+		  if (denominator == 0) return numerator1 == 0 && numerator2 == 0;
+
+		  float r = numerator1 / denominator;
+		  float s = numerator2 / denominator;
+
+		  return (r >= 0 && r <= 1) && (s >= 0 && s <= 1);
+		}
+	
+	public static Vector3fImmutable lineIntersection(float x1, float y1, float x2, float y2, float x3, float y3, float x4, float y4) {
+
+		  // calculate the distance to intersection point
+		  float uA = ((x4-x3)*(y1-y3) - (y4-y3)*(x1-x3)) / ((y4-y3)*(x2-x1) - (x4-x3)*(y2-y1));
+		  float uB = ((x2-x1)*(y1-y3) - (y2-y1)*(x1-x3)) / ((y4-y3)*(x2-x1) - (x4-x3)*(y2-y1));
+
+		  // if uA and uB are between 0-1, lines are colliding
+		  if (uA >= 0 && uA <= 1 && uB >= 0 && uB <= 1) {
+		    return new Vector3fImmutable(x1 + (uA * (x2-x1)),0, y1 + (uA * (y2-y1)));
+		  }
+		  return null;
+		}
+	
+	
+
+	private static boolean calculateFlipExtents(Bounds bounds) {
+
+		int degrees;
+		double radian =0;
+        if (bounds.quaternion != null){
+            radian = bounds.quaternion.angleY;
+		}
+			 
+		degrees = (int) Math.toDegrees(radian);
+		bounds.rotationDegrees = degrees;
+		if (degrees < 0)
+			degrees += 360;
+        return (degrees >= 85 && degrees <= 95) ||
+                (degrees >= 265 && degrees <= 275);
+
+    }
+
+	public void modify(float x, float y, float extents) {
+		this.origin.x = x;
+		this.origin.y = y;
+		this.halfExtents.x = extents;
+		this.halfExtents.y = extents;
+	}
+
+
+	/**
+	 * @return the origin
+	 */
+	public Vector2f getOrigin() {
+		return origin;
+	}
+
+	/**
+	 * @return the halfExtents
+	 */
+	public Vector2f getHalfExtents() {
+		return halfExtents;
+	}
+
+	/**
+	 * @return the rotation
+	 */
+	public float getRotation() {
+		return rotation;
+	}
+
+	/**
+	 * @param rotation the rotation to set
+	 */
+	public void setRotation(float rotation) {
+		this.rotation = rotation;
+	}
+
+	
+
+
+	public void setRegions(Building building ){
+		//Collidables are for player movement collision
+		ArrayList<BuildingRegions> tempList = BuildingRegions._staticRegions.get(building.getMeshUUID());
+
+		ArrayList<Regions> tempRegions = new ArrayList<>();
+		if (tempList != null){
+				
+			for (BuildingRegions buildingRegion:tempList){
+
+				ArrayList<Vector3f> regionPoints = new ArrayList<>();
+
+				Vector3f centerPoint = ZoneManager.convertLocalToWorld(building, buildingRegion.center, this);
+					for (Vector3f point: buildingRegion.getRegionPoints()){
+						Vector3f rotatedPoint = ZoneManager.convertLocalToWorld(building, point,this);
+						regionPoints.add(rotatedPoint);
+					}
+					tempRegions.add(new Regions(regionPoints, buildingRegion.getLevel(),buildingRegion.getRoom(),buildingRegion.isOutside(),buildingRegion.isExitRegion(), buildingRegion.isStairs(), centerPoint,building.getObjectUUID()));
+				}
+			
+		}
+		
+		
+		this.regions = tempRegions;
+	}
+	
+	public void setColliders(Building building ){
+		//Collidables are for player movement collision
+		ArrayList<StaticColliders> tempList = StaticColliders._staticColliders.get(building.getMeshUUID());
+
+		ArrayList<Colliders> tempColliders = new ArrayList<>();
+		if (tempList != null){
+				
+			for (StaticColliders staticCollider :tempList){
+
+				ArrayList<Vector3f> regionPoints = new ArrayList<>();
+
+				Vector3f colliderStart = new Vector3f(staticCollider.getStartX(), 0, staticCollider.getStartY());
+				Vector3f colliderEnd = new Vector3f(staticCollider.getEndX(), 0, staticCollider.getEndY());
+				Vector3f worldStart = ZoneManager.convertLocalToWorld(building, colliderStart, this);
+				Vector3f worldEnd = ZoneManager.convertLocalToWorld(building, colliderEnd, this);
+				tempColliders.add(new Colliders(worldStart.x, worldStart.z, worldEnd.x, worldEnd.z, staticCollider.getDoorID(), staticCollider.isLink()));
+				}
+			
+		}
+		
+		
+		this.colliders = tempColliders;
+	}
+
+	public ArrayList<Regions> getRegions() {
+		return regions;
+	}
+
+	public void setRegions(ArrayList<Regions> regions) {
+		this.regions = regions;
+	}
+	
+	public static Vector3f getRotatedPoint(Vector3f point, float centerX, float centerZ, float angle){
+	
+		//TRANSLATE TO ORIGIN
+		float x1 = point.getX() - centerX;
+		float y1 = point.getZ() - centerZ;
+
+		//APPLY ROTATION
+		float temp_x1 = (float) (x1 * Math.cos(angle) - y1 * Math.sin(angle));
+		float temp_z1 = (float) (x1 * Math.sin(angle) + y1 * Math.cos(angle));
+		
+		temp_x1 += centerX;
+		temp_z1 += centerZ;
+		
+		return new Vector3f(temp_x1,point.y,temp_z1);
+		
+	}
+	
+
+	public float getRotationDegrees() {
+		return rotationDegrees;
+	}
+
+	public boolean isFlipExtents() {
+		return flipExtents;
+	}
+
+	public Quaternion getQuaternion() {
+		return quaternion;
+	}
+
+}
diff --git a/src/engine/math/FastMath.java b/src/engine/math/FastMath.java
new file mode 100644
index 00000000..067ef617
--- /dev/null
+++ b/src/engine/math/FastMath.java
@@ -0,0 +1,668 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+ package engine.math;
+
+import java.util.Calendar;
+import java.util.concurrent.ThreadLocalRandom;
+
+/**
+ * <code>FastMath</code> provides 'fast' math approximations and float
+ * equivalents of Math functions. These are all used as static values and
+ * functions.
+ *
+ * @author Various
+ */
+
+final public class FastMath {
+
+	private FastMath() {
+	}
+
+	/** A "close to zero" double epsilon value for use */
+	public static final double DBL_EPSILON = 2.220446049250313E-16d;
+
+	/** A "close to zero" float epsilon value for use */
+	public static final float FLT_EPSILON = 1.1920928955078125E-7f;
+
+	/** A "close to zero" float epsilon value for use */
+	public static final float ZERO_TOLERANCE = 0.0001f;
+
+	public static final float ONE_THIRD = 1f / 3f;
+
+	/** The value PI as a float. (180 degrees) */
+	public static final float PI = (float) Math.PI;
+
+	/** The value 2PI as a float. (360 degrees) */
+	public static final float TWO_PI = 2.0f * PI;
+
+	/** The value PI/2 as a float. (90 degrees) */
+	public static final float HALF_PI = 0.5f * PI;
+
+	/** The value PI/4 as a float. (45 degrees) */
+	public static final float QUARTER_PI = 0.25f * PI;
+
+	/** The value 1/PI as a float. */
+	public static final float INV_PI = 1.0f / PI;
+
+	/** The value 1/(2PI) as a float. */
+	public static final float INV_TWO_PI = 1.0f / TWO_PI;
+
+	/** A value to multiply a degree value by, to convert it to radians. */
+	public static final float DEG_TO_RAD = PI / 180.0f;
+
+	/** A value to multiply a radian value by, to convert it to degrees. */
+	public static final float RAD_TO_DEG = 180.0f / PI;
+
+	/**
+	 * Returns true if the number is a power of 2 (2,4,8,16...)
+	 *
+	 * A good implementation found on the Java boards. note: a number is a power
+	 * of two if and only if it is the smallest number with that number of
+	 * significant bits. Therefore, if you subtract 1, you know that the new
+	 * number will have fewer bits, so ANDing the original number with anything
+	 * less than it will give 0.
+	 *
+	 * @param number
+	 *            The number to test.
+	 * @return True if it is a power of two.
+	 */
+	public static boolean isPowerOfTwo(int number) {
+		return (number > 0) && (number & (number - 1)) == 0;
+	}
+
+	public static int nearestPowerOfTwo(int number) {
+		return (int) Math.pow(2, Math.ceil(Math.log(number) / Math.log(2)));
+	}
+
+	public static boolean between(float i, float minValueInclusive, float maxValueInclusive) {
+		return (i >= minValueInclusive && i <= maxValueInclusive);
+	}
+
+	/**
+	 * Linear interpolation from startValue to endValue by the given percent.
+	 * Basically: ((1 - percent) * startValue) + (percent * endValue)
+	 *
+	 * @param percent
+	 *            Percent value to use.
+	 * @param startValue
+	 *            Begining value. 0% of f
+	 * @param endValue
+	 *            ending value. 100% of f
+	 * @return The interpolated value between startValue and endValue.
+	 */
+	public static float LERP(float percent, float startValue, float endValue) {
+		if (startValue == endValue)
+			return startValue;
+		return ((1 - percent) * startValue) + (percent * endValue);
+	}
+
+	/**
+	 * Returns the arc cosine of an angle given in radians.<br>
+	 * Special cases:
+	 * <ul>
+	 * <li>If fValue is smaller than -1, then the result is PI.
+	 * <li>If the argument is greater than 1, then the result is 0.
+	 * </ul>
+	 *
+	 * @param fValue
+	 *            The angle, in radians.
+	 * @return fValue's acos
+	 * @see java.lang.Math#acos(double)
+	 */
+	public static float acos(float fValue) {
+		if (-1.0f < fValue) {
+			if (fValue < 1.0f)
+				return (float) Math.acos(fValue);
+
+			return 0.0f;
+		}
+
+		return PI;
+	}
+
+	/**
+	 * Returns the arc sine of an angle given in radians.<br>
+	 * Special cases:
+	 * <ul>
+	 * <li>If fValue is smaller than -1, then the result is -HALF_PI.
+	 * <li>If the argument is greater than 1, then the result is HALF_PI.
+	 * </ul>
+	 *
+	 * @param fValue
+	 *            The angle, in radians.
+	 * @return fValue's asin
+	 * @see java.lang.Math#asin(double)
+	 */
+	public static float asin(float fValue) {
+		if (-1.0f < fValue) {
+			if (fValue < 1.0f)
+				return (float) Math.asin(fValue);
+
+			return HALF_PI;
+		}
+
+		return -HALF_PI;
+	}
+
+	/**
+	 * Returns the arc tangent of an angle given in radians.<br>
+	 *
+	 * @param fValue
+	 *            The angle, in radians.
+	 * @return fValue's asin
+	 * @see java.lang.Math#atan(double)
+	 */
+	public static float atan(float fValue) {
+		return (float) Math.atan(fValue);
+	}
+
+	/**
+	 * A direct call to Math.atan2.
+	 *
+	 * @param fY
+	 * @param fX
+	 * @return Math.atan2(fY,fX)
+	 * @see java.lang.Math#atan2(double, double)
+	 */
+	public static float atan2(float fY, float fX) {
+		return (float) Math.atan2(fY, fX);
+	}
+
+	/**
+	 * Rounds a fValue up. A call to Math.ceil
+	 *
+	 * @param fValue
+	 *            The value.
+	 * @return The fValue rounded up
+	 * @see java.lang.Math#ceil(double)
+	 */
+	public static float ceil(float fValue) {
+		return (float) Math.ceil(fValue);
+	}
+
+	/**
+	 * Fast Trig functions for x86. This forces the trig functiosn to stay
+	 * within the safe area on the x86 processor (-45 degrees to +45 degrees)
+	 * The results may be very slightly off from what the Math and StrictMath
+	 * trig functions give due to rounding in the angle reduction but it will be
+	 * very very close.
+	 *
+	 */
+	public static float reduceSinAngle(float radians) {
+		radians %= TWO_PI; // put us in -2PI to +2PI space
+		if (Math.abs(radians) > PI) { // put us in -PI to +PI space
+            radians -= (TWO_PI);
+		}
+		if (Math.abs(radians) > HALF_PI) {// put us in -PI/2 to +PI/2 space
+			radians = PI - radians;
+		}
+
+		return radians;
+	}
+
+	/**
+	 * Returns sine of a value.
+	 *
+	 * @param fValue
+	 *            The value to sine, in radians.
+	 * @return The sine of fValue.
+	 * @see java.lang.Math#sin(double)
+	 */
+	public static float sin(float fValue) {
+		fValue = reduceSinAngle(fValue); // limits angle to between -PI/2 and
+		// +PI/2
+		if (Math.abs(fValue) <= Math.PI / 4) {
+			return (float) Math.sin(fValue);
+		}
+
+		return (float) Math.cos(Math.PI / 2 - fValue);
+	}
+
+	/**
+	 * Returns cos of a value.
+	 *
+	 * @param fValue
+	 *            The value to cosine, in radians.
+	 * @return The cosine of fValue.
+	 * @see java.lang.Math#cos(double)
+	 */
+	public static float cos(float fValue) {
+		return sin(fValue + HALF_PI);
+	}
+
+	/**
+	 * Returns E^fValue
+	 *
+	 * @param fValue
+	 *            Value to raise to a power.
+	 * @return The value E^fValue
+	 * @see java.lang.Math#exp(double)
+	 */
+	public static float exp(float fValue) {
+		return (float) Math.exp(fValue);
+	}
+
+	/**
+	 * Returns Absolute value of a float.
+	 *
+	 * @param fValue
+	 *            The value to abs.
+	 * @return The abs of the value.
+	 * @see java.lang.Math#abs(float)
+	 */
+	public static float abs(float fValue) {
+		if (fValue < 0)
+			return -fValue;
+		return fValue;
+	}
+
+	/**
+	 * Returns a number rounded down.
+	 *
+	 * @param fValue
+	 *            The value to round
+	 * @return The given number rounded down
+	 * @see java.lang.Math#floor(double)
+	 */
+	public static float floor(float fValue) {
+		return (float) Math.floor(fValue);
+	}
+
+	/**
+	 * Returns 1/sqrt(fValue)
+	 *
+	 * @param fValue
+	 *            The value to process.
+	 * @return 1/sqrt(fValue)
+	 * @see java.lang.Math#sqrt(double)
+	 */
+	public static float invSqrt(float fValue) {
+		return (float) (1.0f / Math.sqrt(fValue));
+	}
+
+	/**
+	 * Returns the log base E of a value.
+	 *
+	 * @param fValue
+	 *            The value to Logger.getInstance().
+	 * @return The log of fValue base E
+	 * @see java.lang.Math#log(double)
+	 */
+	public static float log(float fValue) {
+		return (float) Math.log(fValue);
+	}
+
+	/**
+	 * Returns the logarithm of value with given base, calculated as
+	 * log(value)/log(base), so that pow(base, return)==value (contributed by
+	 * vear)
+	 *
+	 * @param value
+	 *            The value to Logger.getInstance().
+	 * @param base
+	 *            Base of logarithm.
+	 * @return The logarithm of value with given base
+	 */
+	public static float log(float value, float base) {
+		return (float) (Math.log(value) / Math.log(base));
+	}
+
+	/**
+	 * Returns a number raised to an exponent power. fBase^fExponent
+	 *
+	 * @param fBase
+	 *            The base value (IE 2)
+	 * @param fExponent
+	 *            The exponent value (IE 3)
+	 * @return base raised to exponent (IE 8)
+	 * @see java.lang.Math#pow(double, double)
+	 */
+	public static float pow(float fBase, float fExponent) {
+		return (float) Math.pow(fBase, fExponent);
+	}
+
+	/**
+	 * Returns the value squared. fValue ^ 2
+	 *
+	 * @param fValue
+	 *            The vaule to square.
+	 * @return The square of the given value.
+	 */
+	public static float sqr(float fValue) {
+		return fValue * fValue;
+	}
+	
+	public static double sqr(double fValue) {
+		return fValue * fValue;
+	}
+
+	/**
+	 * Returns the square root of a given value.
+	 *
+	 * @param fValue
+	 *            The value to sqrt.
+	 * @return The square root of the given value.
+	 * @see java.lang.Math#sqrt(double)
+	 */
+	public static float sqrt(float fValue) {
+		return (float) Math.sqrt(fValue);
+	}
+
+	/**
+	 * Returns the tangent of a value. If USE_FAST_TRIG is enabled, an
+	 * approximate value is returned. Otherwise, a direct value is used.
+	 *
+	 * @param fValue
+	 *            The value to tangent, in radians.
+	 * @return The tangent of fValue.
+	 * @see java.lang.Math#tan(double)
+	 */
+	public static float tan(float fValue) {
+		return (float) Math.tan(fValue);
+	}
+
+	/**
+	 * Returns 1 if the number is positive, -1 if the number is negative, and 0
+	 * otherwise
+	 *
+	 * @param iValue
+	 *            The integer to examine.
+	 * @return The integer's sign.
+	 */
+	public static int sign(int iValue) {
+		if (iValue > 0)
+			return 1;
+
+		if (iValue < 0)
+			return -1;
+
+		return 0;
+	}
+
+	/**
+	 * Returns 1 if the number is positive, -1 if the number is negative, and 0
+	 * otherwise
+	 *
+	 * @param fValue
+	 *            The float to examine.
+	 * @return The float's sign.
+	 */
+	public static float sign(float fValue) {
+		return Math.signum(fValue);
+	}
+
+	/**
+	 * Given 3 points in a 2d plane, this function computes if the points going
+	 * from A-B-C are moving counter clock wise.
+	 *
+	 * @param p0
+	 *            Point 0.
+	 * @param p1
+	 *            Point 1.
+	 * @param p2
+	 *            Point 2.
+	 * @return 1 If they are CCW, -1 if they are not CCW, 0 if p2 is between p0
+	 *         and p1.
+	 */
+	public static int counterClockwise(Vector2f p0, Vector2f p1, Vector2f p2) {
+		float dx1, dx2, dy1, dy2;
+		dx1 = p1.x - p0.x;
+		dy1 = p1.y - p0.y;
+		dx2 = p2.x - p0.x;
+		dy2 = p2.y - p0.y;
+		if (dx1 * dy2 > dy1 * dx2)
+			return 1;
+		if (dx1 * dy2 < dy1 * dx2)
+			return -1;
+		if ((dx1 * dx2 < 0) || (dy1 * dy2 < 0))
+			return -1;
+		if ((dx1 * dx1 + dy1 * dy1) < (dx2 * dx2 + dy2 * dy2))
+			return 1;
+		return 0;
+	}
+
+	/**
+	 * Test if a point is inside a triangle. 1 if the point is on the ccw side,
+	 * -1 if the point is on the cw side, and 0 if it is on neither.
+	 *
+	 * @param t0
+	 *            First point of the triangle.
+	 * @param t1
+	 *            Second point of the triangle.
+	 * @param t2
+	 *            Third point of the triangle.
+	 * @param p
+	 *            The point to test.
+	 * @return Value 1 or -1 if inside triangle, 0 otherwise.
+	 */
+	public static int pointInsideTriangle(Vector2f t0, Vector2f t1,
+			Vector2f t2, Vector2f p) {
+		int val1 = counterClockwise(t0, t1, p);
+		if (val1 == 0)
+			return 1;
+		int val2 = counterClockwise(t1, t2, p);
+		if (val2 == 0)
+			return 1;
+		if (val2 != val1)
+			return 0;
+		int val3 = counterClockwise(t2, t0, p);
+		if (val3 == 0)
+			return 1;
+		if (val3 != val1)
+			return 0;
+		return val3;
+	}
+
+	/**
+	 * Returns the determinant of a 4x4 matrix.
+	 */
+	public static float determinant(double m00, double m01, double m02,
+			double m03, double m10, double m11, double m12, double m13,
+			double m20, double m21, double m22, double m23, double m30,
+			double m31, double m32, double m33) {
+
+		double det01 = m20 * m31 - m21 * m30;
+		double det02 = m20 * m32 - m22 * m30;
+		double det03 = m20 * m33 - m23 * m30;
+		double det12 = m21 * m32 - m22 * m31;
+		double det13 = m21 * m33 - m23 * m31;
+		double det23 = m22 * m33 - m23 * m32;
+		return (float) (m00 * (m11 * det23 - m12 * det13 + m13 * det12) - m01
+				* (m10 * det23 - m12 * det03 + m13 * det02) + m02
+				* (m10 * det13 - m11 * det03 + m13 * det01) - m03
+				* (m10 * det12 - m11 * det02 + m12 * det01));
+	}
+
+	/**
+	 * Converts a point from Spherical coordinates to Cartesian (using positive
+	 * Y as up) and stores the results in the store var.
+	 */
+	public static Vector3f sphericalToCartesian(Vector3f sphereCoords,
+			Vector3f store) {
+		store.y = sphereCoords.x * FastMath.sin(sphereCoords.z);
+		float a = sphereCoords.x * FastMath.cos(sphereCoords.z);
+		store.x = a * FastMath.cos(sphereCoords.y);
+		store.z = a * FastMath.sin(sphereCoords.y);
+
+		return store;
+	}
+
+	/**
+	 * Converts a point from Cartesian coordinates (using positive Y as up) to
+	 * Spherical and stores the results in the store var. (Radius, Azimuth,
+	 * Polar)
+	 */
+	public static Vector3f cartesianToSpherical(Vector3f cartCoords,
+			Vector3f store) {
+		if (cartCoords.x == 0)
+			cartCoords.x = FastMath.FLT_EPSILON;
+		store.x = FastMath
+				.sqrt((cartCoords.x * cartCoords.x)
+						+ (cartCoords.y * cartCoords.y)
+						+ (cartCoords.z * cartCoords.z));
+		store.y = FastMath.atan(cartCoords.z / cartCoords.x);
+		if (cartCoords.x < 0)
+			store.y += FastMath.PI;
+		store.z = FastMath.asin(cartCoords.y / store.x);
+		return store;
+	}
+
+	/**
+	 * Converts a point from Spherical coordinates to Cartesian (using positive
+	 * Z as up) and stores the results in the store var.
+	 */
+	public static Vector3f sphericalToCartesianZ(Vector3f sphereCoords,
+			Vector3f store) {
+		store.z = sphereCoords.x * FastMath.sin(sphereCoords.z);
+		float a = sphereCoords.x * FastMath.cos(sphereCoords.z);
+		store.x = a * FastMath.cos(sphereCoords.y);
+		store.y = a * FastMath.sin(sphereCoords.y);
+
+		return store;
+	}
+
+	/**
+	 * Converts a point from Cartesian coordinates (using positive Z as up) to
+	 * Spherical and stores the results in the store var. (Radius, Azimuth,
+	 * Polar)
+	 */
+	public static Vector3f cartesianZToSpherical(Vector3f cartCoords,
+			Vector3f store) {
+		if (cartCoords.x == 0)
+			cartCoords.x = FastMath.FLT_EPSILON;
+		store.x = FastMath
+				.sqrt((cartCoords.x * cartCoords.x)
+						+ (cartCoords.y * cartCoords.y)
+						+ (cartCoords.z * cartCoords.z));
+		store.z = FastMath.atan(cartCoords.z / cartCoords.x);
+		if (cartCoords.x < 0)
+			store.z += FastMath.PI;
+		store.y = FastMath.asin(cartCoords.y / store.x);
+		return store;
+	}
+
+	/**
+	 * Takes an value and expresses it in terms of min to max.
+	 *
+	 * @param val
+	 *            - the angle to normalize (in radians)
+	 * @return the normalized angle (also in radians)
+	 */
+	public static float normalize(float val, float min, float max) {
+		if (Float.isInfinite(val) || Float.isNaN(val))
+			return 0f;
+		float range = max - min;
+		while (val > max)
+			val -= range;
+		while (val < min)
+			val += range;
+		return val;
+	}
+
+	/**
+	 * @param x
+	 *            the value whose sign is to be adjusted.
+	 * @param y
+	 *            the value whose sign is to be used.
+	 * @return x with its sign changed to match the sign of y.
+	 */
+	public static float copysign(float x, float y) {
+		if (y >= 0 && x <= -0)
+			return -x;
+		else if (y < 0 && x >= 0)
+			return -x;
+		else
+			return x;
+	}
+
+	/**
+	 * Take a float input and clamp it between min and max.
+	 *
+	 * @param input
+	 * @param min
+	 * @param max
+	 * @return clamped input
+	 */
+	public static float clamp(float input, float min, float max) {
+		return (input < min) ? min : (input > max) ? max : input;
+	}
+
+
+	/**
+	 * Return a random 2D vector
+	 * this needs checked
+	 */
+	public static Vector3fImmutable randomVector2D() {
+		float x = ((ThreadLocalRandom.current().nextFloat() * 2) - 1);
+		float z = ((ThreadLocalRandom.current().nextFloat() * 2) - 1);
+		Vector3fImmutable ret = new Vector3fImmutable(x, 0, z);
+		return ret.normalize();
+	}
+
+	/**
+	 * Turns a String into an Integer hash that the client will recognize.
+	 *
+	 * @param s
+	 * 		The String to hash
+	 * @return the Integer hash
+	 */
+	public static int hash(String s) {
+		int out = 0, pad = 0;
+		for(char c : s.toCharArray()) {
+			if(c == ' ' || c == '\\') {
+				pad += 5;
+				continue;
+			}
+			if(c > 0x40 && c < 0x60)
+				out ^= _rotl((byte) 0x60, pad);
+			out ^= _rotl((byte) (c & 0xDF), pad);
+			pad += 5;
+		}
+		return out;
+	}
+
+	/**
+	 * Utility function for making hashes
+	 */
+	private static int _rotl(int value, int shift) {
+	    if ((shift &= 31) == 0)
+	      return value;
+	    return (value << shift) | (value >> (32 - shift));
+	}
+
+	/**
+	 * Gets number of seconds until next hours
+	 */
+
+	public static int secondsUntilNextHour() {
+		Calendar cal = Calendar.getInstance();
+		int minute = cal.get(Calendar.MINUTE);
+		int second = cal.get(Calendar.SECOND);
+		return 3600 - (minute * 60) - second;
+	}
+	
+	public static float area(float x1, float y1, float x2, float y2, float x3, float y3){
+		   return (float) Math.abs((x1*(y2-y3) + x2*(y3-y1)+ x3*(y1-y2))/2.0); //Change to *.5?
+		}
+	
+	public static float GetDegrees(float yRot){
+		double radian = Math.asin(yRot);
+		float degrees =  (float) Math.toDegrees(radian);
+
+		degrees *=2;
+		return degrees;
+	}
+	
+	public static float degreesToW(float degrees){
+		return (float) Math.abs(Math.cos(Math.toRadians(degrees)/2));
+	}
+	public static float degreesToYRotation(float degrees){
+		return (float)Math.sin(Math.toRadians(degrees)/2);
+	}
+}
\ No newline at end of file
diff --git a/src/engine/math/Matrix3f.java b/src/engine/math/Matrix3f.java
new file mode 100644
index 00000000..f0e1efe8
--- /dev/null
+++ b/src/engine/math/Matrix3f.java
@@ -0,0 +1,1220 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+ package engine.math;
+
+import org.pmw.tinylog.Logger;
+
+import java.nio.FloatBuffer;
+
+
+/**
+ * <code>Matrix3f</code> defines a 3x3 matrix. Matrix data is maintained
+ * internally and is accessible via the get and set methods. Convenience methods
+ * are used for matrix operations as well as generating a matrix from a given
+ * set of values.
+ * 
+ */
+public class Matrix3f {
+	public float m00, m01, m02;
+	public float m10, m11, m12;
+	public float m20, m21, m22;
+
+	/**
+	 * Constructor instantiates a new <code>Matrix3f</code> object. The initial
+	 * values for the matrix is that of the identity matrix.
+	 * 
+	 */
+	public Matrix3f() {
+		loadIdentity();
+	}
+
+	/**
+	 * constructs a matrix with the given values.
+	 * 
+	 * @param m00
+	 *            0x0 in the matrix.
+	 * @param m01
+	 *            0x1 in the matrix.
+	 * @param m02
+	 *            0x2 in the matrix.
+	 * @param m10
+	 *            1x0 in the matrix.
+	 * @param m11
+	 *            1x1 in the matrix.
+	 * @param m12
+	 *            1x2 in the matrix.
+	 * @param m20
+	 *            2x0 in the matrix.
+	 * @param m21
+	 *            2x1 in the matrix.
+	 * @param m22
+	 *            2x2 in the matrix.
+	 */
+	public Matrix3f(float m00, float m01, float m02, float m10, float m11,
+			float m12, float m20, float m21, float m22) {
+
+		this.m00 = m00;
+		this.m01 = m01;
+		this.m02 = m02;
+		this.m10 = m10;
+		this.m11 = m11;
+		this.m12 = m12;
+		this.m20 = m20;
+		this.m21 = m21;
+		this.m22 = m22;
+	}
+
+	/**
+	 * Copy constructor that creates a new <code>Matrix3f</code> object that is
+	 * the same as the provided matrix.
+	 * 
+	 * @param mat
+	 *            the matrix to copy.
+	 */
+	public Matrix3f(Matrix3f mat) {
+		copy(mat);
+	}
+
+	/**
+	 * <code>copy</code> transfers the contents of a given matrix to this
+	 * matrix. If a null matrix is supplied, this matrix is set to the identity
+	 * matrix.
+	 * 
+	 * @param matrix
+	 *            the matrix to copy.
+	 */
+	public void copy(Matrix3f matrix) {
+		if (null == matrix) {
+			loadIdentity();
+		} else {
+			m00 = matrix.m00;
+			m01 = matrix.m01;
+			m02 = matrix.m02;
+			m10 = matrix.m10;
+			m11 = matrix.m11;
+			m12 = matrix.m12;
+			m20 = matrix.m20;
+			m21 = matrix.m21;
+			m22 = matrix.m22;
+		}
+	}
+
+	/**
+	 * <code>get</code> retrieves a value from the matrix at the given position.
+	 * If the position is invalid a <code>Exception</code> is thrown.
+	 * 
+	 * @param i
+	 *            the row index.
+	 * @param j
+	 *            the column index.
+	 * @return the value at (i, j).
+	 * @throws Exception
+	 */
+	public float get(int i, int j) throws Exception {
+		switch (i) {
+		case 0:
+			switch (j) {
+			case 0:
+				return m00;
+			case 1:
+				return m01;
+			case 2:
+				return m02;
+			}
+		case 1:
+			switch (j) {
+			case 0:
+				return m10;
+			case 1:
+				return m11;
+			case 2:
+				return m12;
+			}
+		case 2:
+			switch (j) {
+			case 0:
+				return m20;
+			case 1:
+				return m21;
+			case 2:
+				return m22;
+			}
+		}
+		throw new Exception("Invalid indices into matrix.");
+	}
+
+	/**
+	 * <code>get(float[])</code> returns the matrix in row-major or column-major
+	 * order.
+	 * 
+	 * @param data
+	 *            The array to return the data into. This array can be 9 or 16
+	 *            floats in size. Only the upper 3x3 are assigned to in the case
+	 *            of a 16 element array.
+	 * @param rowMajor
+	 *            True for row major storage in the array (translation in
+	 *            elements 3, 7, 11 for a 4x4), false for column major
+	 *            (translation in elements 12, 13, 14 for a 4x4).
+	 * @throws Exception
+	 */
+	public void get(float[] data, boolean rowMajor) throws Exception {
+		if (data.length == 9) {
+			if (rowMajor) {
+				data[0] = m00;
+				data[1] = m01;
+				data[2] = m02;
+				data[3] = m10;
+				data[4] = m11;
+				data[5] = m12;
+				data[6] = m20;
+				data[7] = m21;
+				data[8] = m22;
+			} else {
+				data[0] = m00;
+				data[1] = m10;
+				data[2] = m20;
+				data[3] = m01;
+				data[4] = m11;
+				data[5] = m21;
+				data[6] = m02;
+				data[7] = m12;
+				data[8] = m22;
+			}
+		} else if (data.length == 16) {
+			if (rowMajor) {
+				data[0] = m00;
+				data[1] = m01;
+				data[2] = m02;
+				data[4] = m10;
+				data[5] = m11;
+				data[6] = m12;
+				data[8] = m20;
+				data[9] = m21;
+				data[10] = m22;
+			} else {
+				data[0] = m00;
+				data[1] = m10;
+				data[2] = m20;
+				data[4] = m01;
+				data[5] = m11;
+				data[6] = m21;
+				data[8] = m02;
+				data[9] = m12;
+				data[10] = m22;
+			}
+		} else {
+			throw new Exception("Array size must be 9 or 16 in Matrix3f.get().");
+		}
+	}
+
+	/**
+	 * <code>getColumn</code> returns one of three columns specified by the
+	 * parameter. This column is returned as a <code>Vector3f</code> object.
+	 * 
+	 * @param i
+	 *            the column to retrieve. Must be between 0 and 2.
+	 * @return the column specified by the index.
+	 * @throws Exception
+	 */
+	public Vector3f getColumn(int i) throws Exception {
+		return getColumn(i, null);
+	}
+
+	/**
+	 * <code>getColumn</code> returns one of three columns specified by the
+	 * parameter. This column is returned as a <code>Vector3f</code> object.
+	 * 
+	 * @param i
+	 *            the column to retrieve. Must be between 0 and 2.
+	 * @param store
+	 *            the vector object to store the result in. if null, a new one
+	 *            is created.
+	 * @return the column specified by the index.
+	 * @throws Exception
+	 */
+	public Vector3f getColumn(int i, Vector3f store) throws Exception {
+		if (store == null)
+			store = new Vector3f();
+		switch (i) {
+		case 0:
+			store.x = m00;
+			store.y = m10;
+			store.z = m20;
+			break;
+		case 1:
+			store.x = m01;
+			store.y = m11;
+			store.z = m21;
+			break;
+		case 2:
+			store.x = m02;
+			store.y = m12;
+			store.z = m22;
+			break;
+		default:
+			throw new Exception("Invalid column index. " + i);
+		}
+		return store;
+	}
+
+	/**
+	 * <code>getColumn</code> returns one of three rows as specified by the
+	 * parameter. This row is returned as a <code>Vector3f</code> object.
+	 * 
+	 * @param i
+	 *            the row to retrieve. Must be between 0 and 2.
+	 * @return the row specified by the index.
+	 * @throws Exception
+	 */
+	public Vector3f getRow(int i) throws Exception {
+		return getRow(i, null);
+	}
+
+	/**
+	 * <code>getRow</code> returns one of three rows as specified by the
+	 * parameter. This row is returned as a <code>Vector3f</code> object.
+	 * 
+	 * @param i
+	 *            the row to retrieve. Must be between 0 and 2.
+	 * @param store
+	 *            the vector object to store the result in. if null, a new one
+	 *            is created.
+	 * @return the row specified by the index.
+	 * @throws Exception
+	 */
+	public Vector3f getRow(int i, Vector3f store) throws Exception {
+		if (store == null)
+			store = new Vector3f();
+		switch (i) {
+		case 0:
+			store.x = m00;
+			store.y = m01;
+			store.z = m02;
+			break;
+		case 1:
+			store.x = m10;
+			store.y = m11;
+			store.z = m12;
+			break;
+		case 2:
+			store.x = m20;
+			store.y = m21;
+			store.z = m22;
+			break;
+		default:
+			throw new Exception("Invalid row index. " + i);
+		}
+		return store;
+	}
+
+	/**
+	 * <code>fillFloatBuffer</code> fills a FloatBuffer object with the matrix
+	 * data.
+	 * 
+	 * @param fb
+	 *            the buffer to fill, starting at current position. Must have
+	 *            room for 9 more floats.
+	 * @return matrix data as a FloatBuffer. (position is advanced by 9 and any
+	 *         limit set is not changed).
+	 */
+	public FloatBuffer fillFloatBuffer(FloatBuffer fb) {
+		fb.put(m00).put(m01).put(m02);
+		fb.put(m10).put(m11).put(m12);
+		fb.put(m20).put(m21).put(m22);
+		return fb;
+	}
+
+	/**
+	 * 
+	 * <code>setColumn</code> sets a particular column of this matrix to that
+	 * represented by the provided vector.
+	 * 
+	 * @param i
+	 *            the column to set.
+	 * @param column
+	 *            the data to set.
+	 * @throws Exception
+	 */
+	public void setColumn(int i, Vector3f column) throws Exception {
+
+		if (column == null) {
+			return;
+		}
+		switch (i) {
+		case 0:
+			m00 = column.x;
+			m10 = column.y;
+			m20 = column.z;
+			break;
+		case 1:
+			m01 = column.x;
+			m11 = column.y;
+			m21 = column.z;
+			break;
+		case 2:
+			m02 = column.x;
+			m12 = column.y;
+			m22 = column.z;
+			break;
+		default:
+			throw new Exception("Invalid column index. " + i);
+		}
+	}
+
+	/**
+	 * 
+	 * <code>setRow</code> sets a particular row of this matrix to that
+	 * represented by the provided vector.
+	 * 
+	 * @param i
+	 *            the row to set.
+	 * @param row
+	 *            the data to set.
+	 * @throws Exception
+	 */
+	public void setRow(int i, Vector3f row) throws Exception {
+
+		if (row == null) {
+			return;
+		}
+		switch (i) {
+		case 0:
+			m00 = row.x;
+			m01 = row.y;
+			m02 = row.z;
+			break;
+		case 1:
+			m10 = row.x;
+			m11 = row.y;
+			m12 = row.z;
+			break;
+		case 2:
+			m20 = row.x;
+			m21 = row.y;
+			m22 = row.z;
+			break;
+		default:
+			throw new Exception("Invalid row index. " + i);
+		}
+	}
+
+	/**
+	 * <code>set</code> places a given value into the matrix at the given
+	 * position. If the position is invalid a <code>Exception</code> is thrown.
+	 * 
+	 * @param i
+	 *            the row index.
+	 * @param j
+	 *            the column index.
+	 * @param value
+	 *            the value for (i, j).
+	 * @throws Exception
+	 */
+	public void set(int i, int j, float value) throws Exception {
+		switch (i) {
+		case 0:
+			switch (j) {
+			case 0:
+				m00 = value;
+				return;
+			case 1:
+				m01 = value;
+				return;
+			case 2:
+				m02 = value;
+				return;
+			}
+		case 1:
+			switch (j) {
+			case 0:
+				m10 = value;
+				return;
+			case 1:
+				m11 = value;
+				return;
+			case 2:
+				m12 = value;
+				return;
+			}
+		case 2:
+			switch (j) {
+			case 0:
+				m20 = value;
+				return;
+			case 1:
+				m21 = value;
+				return;
+			case 2:
+				m22 = value;
+				return;
+			}
+		}
+		throw new Exception("Invalid indices into matrix.");
+	}
+
+	/**
+	 * 
+	 * <code>set</code> sets the values of the matrix to those supplied by the
+	 * 3x3 two dimenion array.
+	 * 
+	 * @param matrix
+	 *            the new values of the matrix.
+	 * @throws Exception
+	 *             if the array is not of size 9.
+	 */
+	public void set(float[][] matrix) throws Exception {
+		if (matrix.length != 3 || matrix[0].length != 3) {
+			throw new Exception("Array must be of size 9.");
+		}
+
+		m00 = matrix[0][0];
+		m01 = matrix[0][1];
+		m02 = matrix[0][2];
+		m10 = matrix[1][0];
+		m11 = matrix[1][1];
+		m12 = matrix[1][2];
+		m20 = matrix[2][0];
+		m21 = matrix[2][1];
+		m22 = matrix[2][2];
+	}
+
+	/**
+	 * Recreate Matrix using the provided axis.
+	 * 
+	 * @param uAxis
+	 *            Vector3f
+	 * @param vAxis
+	 *            Vector3f
+	 * @param wAxis
+	 *            Vector3f
+	 */
+	public void fromAxes(Vector3f uAxis, Vector3f vAxis, Vector3f wAxis) {
+		m00 = uAxis.x;
+		m10 = uAxis.y;
+		m20 = uAxis.z;
+
+		m01 = vAxis.x;
+		m11 = vAxis.y;
+		m21 = vAxis.z;
+
+		m02 = wAxis.x;
+		m12 = wAxis.y;
+		m22 = wAxis.z;
+	}
+
+	/**
+	 * <code>set</code> sets the values of this matrix from an array of values
+	 * assuming that the data is rowMajor order;
+	 * 
+	 * @param matrix
+	 *            the matrix to set the value to.
+	 * @throws Exception
+	 */
+	public void set(float[] matrix) throws Exception {
+		set(matrix, true);
+	}
+
+	/**
+	 * <code>set</code> sets the values of this matrix from an array of values;
+	 * 
+	 * @param matrix
+	 *            the matrix to set the value to.
+	 * @param rowMajor
+	 *            whether the incoming data is in row or column major order.
+	 */
+	public void set(float[] matrix, boolean rowMajor) throws Exception {
+		if (matrix.length != 9)
+			throw new Exception("Array must be of size 9.");
+
+		if (rowMajor) {
+			m00 = matrix[0];
+			m01 = matrix[1];
+			m02 = matrix[2];
+			m10 = matrix[3];
+			m11 = matrix[4];
+			m12 = matrix[5];
+			m20 = matrix[6];
+			m21 = matrix[7];
+			m22 = matrix[8];
+		} else {
+			m00 = matrix[0];
+			m01 = matrix[3];
+			m02 = matrix[6];
+			m10 = matrix[1];
+			m11 = matrix[4];
+			m12 = matrix[7];
+			m20 = matrix[2];
+			m21 = matrix[5];
+			m22 = matrix[8];
+		}
+	}
+
+	/**
+	 * 
+	 * <code>set</code> defines the values of the matrix based on a supplied
+	 * <code>Quaternion</code>. It should be noted that all previous values will
+	 * be overridden.
+	 * 
+	 * @param quaternion
+	 *            the quaternion to create a rotational matrix from.
+	 */
+	public void set(Quaternion quaternion) {
+		quaternion.toRotationMatrix(this);
+	}
+
+	/**
+	 * <code>loadIdentity</code> sets this matrix to the identity matrix. Where
+	 * all values are zero except those along the diagonal which are one.
+	 * 
+	 */
+	public void loadIdentity() {
+		m01 = m02 = m10 = m12 = m20 = m21 = 0;
+		m00 = m11 = m22 = 1;
+	}
+
+	/**
+	 * @return true if this matrix is identity
+	 */
+	public boolean isIdentity() {
+		return (m00 == 1 && m01 == 0 && m02 == 0)
+				&& (m10 == 0 && m11 == 1 && m12 == 0)
+				&& (m20 == 0 && m21 == 0 && m22 == 1);
+	}
+
+	/**
+	 * <code>fromAngleAxis</code> sets this matrix4f to the values specified by
+	 * an angle and an axis of rotation. This method creates an object, so use
+	 * fromAngleNormalAxis if your axis is already normalized.
+	 * 
+	 * @param angle
+	 *            the angle to rotate (in radians).
+	 * @param axis
+	 *            the axis of rotation.
+	 */
+	public void fromAngleAxis(float angle, Vector3f axis) {
+		Vector3f normAxis = axis.normalize();
+		fromAngleNormalAxis(angle, normAxis);
+	}
+
+	/**
+	 * <code>fromAngleNormalAxis</code> sets this matrix4f to the values
+	 * specified by an angle and a normalized axis of rotation.
+	 * 
+	 * @param angle
+	 *            the angle to rotate (in radians).
+	 * @param axis
+	 *            the axis of rotation (already normalized).
+	 */
+	public void fromAngleNormalAxis(float angle, Vector3f axis) {
+		float fCos = FastMath.cos(angle);
+		float fSin = FastMath.sin(angle);
+		float fOneMinusCos = ((float) 1.0) - fCos;
+		float fX2 = axis.x * axis.x;
+		float fY2 = axis.y * axis.y;
+		float fZ2 = axis.z * axis.z;
+		float fXYM = axis.x * axis.y * fOneMinusCos;
+		float fXZM = axis.x * axis.z * fOneMinusCos;
+		float fYZM = axis.y * axis.z * fOneMinusCos;
+		float fXSin = axis.x * fSin;
+		float fYSin = axis.y * fSin;
+		float fZSin = axis.z * fSin;
+
+		m00 = fX2 * fOneMinusCos + fCos;
+		m01 = fXYM - fZSin;
+		m02 = fXZM + fYSin;
+		m10 = fXYM + fZSin;
+		m11 = fY2 * fOneMinusCos + fCos;
+		m12 = fYZM - fXSin;
+		m20 = fXZM - fYSin;
+		m21 = fYZM + fXSin;
+		m22 = fZ2 * fOneMinusCos + fCos;
+	}
+
+	/**
+	 * <code>mult</code> multiplies this matrix by a given matrix. The result
+	 * matrix is returned as a new object. If the given matrix is null, a null
+	 * matrix is returned.
+	 * 
+	 * @param mat
+	 *            the matrix to multiply this matrix by.
+	 * @return the result matrix.
+	 */
+	public Matrix3f mult(Matrix3f mat) {
+		return mult(mat, null);
+	}
+
+	/**
+	 * <code>mult</code> multiplies this matrix by a given matrix. The result
+	 * matrix is returned as a new object.
+	 * 
+	 * @param mat
+	 *            the matrix to multiply this matrix by.
+	 * @param product
+	 *            the matrix to store the result in. if null, a new matrix3f is
+	 *            created. It is safe for mat and product to be the same object.
+	 * @return a matrix3f object containing the result of this operation
+	 */
+	public Matrix3f mult(Matrix3f mat, Matrix3f product) {
+
+		float temp00, temp01, temp02;
+		float temp10, temp11, temp12;
+		float temp20, temp21, temp22;
+
+		if (product == null)
+			product = new Matrix3f();
+		temp00 = m00 * mat.m00 + m01 * mat.m10 + m02 * mat.m20;
+		temp01 = m00 * mat.m01 + m01 * mat.m11 + m02 * mat.m21;
+		temp02 = m00 * mat.m02 + m01 * mat.m12 + m02 * mat.m22;
+		temp10 = m10 * mat.m00 + m11 * mat.m10 + m12 * mat.m20;
+		temp11 = m10 * mat.m01 + m11 * mat.m11 + m12 * mat.m21;
+		temp12 = m10 * mat.m02 + m11 * mat.m12 + m12 * mat.m22;
+		temp20 = m20 * mat.m00 + m21 * mat.m10 + m22 * mat.m20;
+		temp21 = m20 * mat.m01 + m21 * mat.m11 + m22 * mat.m21;
+		temp22 = m20 * mat.m02 + m21 * mat.m12 + m22 * mat.m22;
+
+		product.m00 = temp00;
+		product.m01 = temp01;
+		product.m02 = temp02;
+		product.m10 = temp10;
+		product.m11 = temp11;
+		product.m12 = temp12;
+		product.m20 = temp20;
+		product.m21 = temp21;
+		product.m22 = temp22;
+
+		return product;
+	}
+
+	/**
+	 * <code>mult</code> multiplies this matrix by a given <code>Vector3f</code>
+	 * object. The result vector is returned. If the given vector is null, null
+	 * will be returned.
+	 * 
+	 * @param vec
+	 *            the vector to multiply this matrix by.
+	 * @return the result vector.
+	 */
+	public Vector3f mult(Vector3f vec) {
+		return mult(vec, null);
+	}
+
+	/**
+	 * Multiplies this 3x3 matrix by the 1x3 Vector vec and stores the result in
+	 * product.
+	 * 
+	 * @param vec
+	 *            The Vector3f to multiply.
+	 * @param product
+	 *            The Vector3f to store the result, it is safe for this to be
+	 *            the same as vec.
+	 * @return The given product vector.
+	 */
+	public Vector3f mult(Vector3f vec, Vector3f product) {
+
+		if (null == product) {
+			product = new Vector3f();
+		}
+
+		float x = vec.x;
+		float y = vec.y;
+		float z = vec.z;
+
+		product.x = m00 * x + m01 * y + m02 * z;
+		product.y = m10 * x + m11 * y + m12 * z;
+		product.z = m20 * x + m21 * y + m22 * z;
+		return product;
+	}
+
+	/**
+	 * <code>multLocal</code> multiplies this matrix internally by a given float
+	 * scale factor.
+	 * 
+	 * @param scale
+	 *            the value to scale by.
+	 * @return this Matrix3f
+	 */
+	public Matrix3f multLocal(float scale) {
+		m00 *= scale;
+		m01 *= scale;
+		m02 *= scale;
+		m10 *= scale;
+		m11 *= scale;
+		m12 *= scale;
+		m20 *= scale;
+		m21 *= scale;
+		m22 *= scale;
+		return this;
+	}
+
+	/**
+	 * <code>multLocal</code> multiplies this matrix by a given
+	 * <code>Vector3f</code> object. The result vector is stored inside the
+	 * passed vector, then returned . If the given vector is null, null will be
+	 * returned.
+	 * 
+	 * @param vec
+	 *            the vector to multiply this matrix by.
+	 * @return The passed vector after multiplication
+	 */
+	public Vector3f multLocal(Vector3f vec) {
+		if (vec == null)
+			return null;
+		float x = vec.x;
+		float y = vec.y;
+		vec.x = m00 * x + m01 * y + m02 * vec.z;
+		vec.y = m10 * x + m11 * y + m12 * vec.z;
+		vec.z = m20 * x + m21 * y + m22 * vec.z;
+		return vec;
+	}
+
+	/**
+	 * <code>mult</code> multiplies this matrix by a given matrix. The result
+	 * matrix is saved in the current matrix. If the given matrix is null,
+	 * nothing happens. The current matrix is returned. This is equivalent to
+	 * this*=mat
+	 * 
+	 * @param mat
+	 *            the matrix to multiply this matrix by.
+	 * @return This matrix, after the multiplication
+	 */
+	public Matrix3f multLocal(Matrix3f mat) {
+
+		return mult(mat, this);
+	}
+
+	/**
+	 * Transposes this matrix in place. Returns this matrix for chaining
+	 * 
+	 * @return This matrix after transpose
+	 * @throws Exception
+	 */
+	public Matrix3f transposeLocal() throws Exception {
+		float[] tmp = new float[9];
+		get(tmp, false);
+		set(tmp, true);
+		return this;
+	}
+
+	/**
+	 * Inverts this matrix as a new Matrix3f.
+	 * 
+	 * @return The new inverse matrix
+	 */
+	public Matrix3f invert() {
+		return invert(null);
+	}
+
+	/**
+	 * Inverts this matrix and stores it in the given store.
+	 * 
+	 * @return The store
+	 */
+	public Matrix3f invert(Matrix3f store) {
+		if (store == null)
+			store = new Matrix3f();
+
+		float det = determinant();
+		if (FastMath.abs(det) <= 0)
+			return store.zero();
+
+		store.m00 = m11 * m22 - m12 * m21;
+		store.m01 = m02 * m21 - m01 * m22;
+		store.m02 = m01 * m12 - m02 * m11;
+		store.m10 = m12 * m20 - m10 * m22;
+		store.m11 = m00 * m22 - m02 * m20;
+		store.m12 = m02 * m10 - m00 * m12;
+		store.m20 = m10 * m21 - m11 * m20;
+		store.m21 = m01 * m20 - m00 * m21;
+		store.m22 = m00 * m11 - m01 * m10;
+
+		store.multLocal(1f / det);
+		return store;
+	}
+
+	/**
+	 * Inverts this matrix locally.
+	 * 
+	 * @return this
+	 */
+	public Matrix3f invertLocal() {
+		float det = determinant();
+		if (FastMath.abs(det) <= FastMath.FLT_EPSILON)
+			return zero();
+
+		float f00 = m11 * m22 - m12 * m21;
+		float f01 = m02 * m21 - m01 * m22;
+		float f02 = m01 * m12 - m02 * m11;
+		float f10 = m12 * m20 - m10 * m22;
+		float f11 = m00 * m22 - m02 * m20;
+		float f12 = m02 * m10 - m00 * m12;
+		float f20 = m10 * m21 - m11 * m20;
+		float f21 = m01 * m20 - m00 * m21;
+		float f22 = m00 * m11 - m01 * m10;
+
+		m00 = f00;
+		m01 = f01;
+		m02 = f02;
+		m10 = f10;
+		m11 = f11;
+		m12 = f12;
+		m20 = f20;
+		m21 = f21;
+		m22 = f22;
+
+		multLocal(1f / det);
+		return this;
+	}
+
+	/**
+	 * Returns a new matrix representing the adjoint of this matrix.
+	 * 
+	 * @return The adjoint matrix
+	 */
+	public Matrix3f adjoint() {
+		return adjoint(null);
+	}
+
+	/**
+	 * Places the adjoint of this matrix in store (creates store if null.)
+	 * 
+	 * @param store
+	 *            The matrix to store the result in. If null, a new matrix is
+	 *            created.
+	 * @return store
+	 */
+	public Matrix3f adjoint(Matrix3f store) {
+		if (store == null)
+			store = new Matrix3f();
+
+		store.m00 = m11 * m22 - m12 * m21;
+		store.m01 = m02 * m21 - m01 * m22;
+		store.m02 = m01 * m12 - m02 * m11;
+		store.m10 = m12 * m20 - m10 * m22;
+		store.m11 = m00 * m22 - m02 * m20;
+		store.m12 = m02 * m10 - m00 * m12;
+		store.m20 = m10 * m21 - m11 * m20;
+		store.m21 = m01 * m20 - m00 * m21;
+		store.m22 = m00 * m11 - m01 * m10;
+
+		return store;
+	}
+
+	/**
+	 * <code>determinant</code> generates the determinate of this matrix.
+	 * 
+	 * @return the determinate
+	 */
+	public float determinant() {
+		float fCo00 = m11 * m22 - m12 * m21;
+		float fCo10 = m12 * m20 - m10 * m22;
+		float fCo20 = m10 * m21 - m11 * m20;
+        return m00 * fCo00 + m01 * fCo10 + m02 * fCo20;
+	}
+
+	/**
+	 * Sets all of the values in this matrix to zero.
+	 * 
+	 * @return this matrix
+	 */
+	public Matrix3f zero() {
+		m00 = m01 = m02 = m10 = m11 = m12 = m20 = m21 = m22 = 0.0f;
+		return this;
+	}
+
+	/**
+	 * <code>add</code> adds the values of a parameter matrix to this matrix.
+	 * 
+	 * @param mat
+	 *            the matrix to add to this.
+	 */
+	public void add(Matrix3f mat) {
+		m00 += mat.m00;
+		m01 += mat.m01;
+		m02 += mat.m02;
+		m10 += mat.m10;
+		m11 += mat.m11;
+		m12 += mat.m12;
+		m20 += mat.m20;
+		m21 += mat.m21;
+		m22 += mat.m22;
+	}
+
+	/**
+	 * <code>transpose</code> <b>locally</b> transposes this Matrix. This is
+	 * inconsistent with general value vs local semantics, but is preserved for
+	 * backwards compatibility. Use transposeNew() to transpose to a new object
+	 * (value).
+	 * 
+	 * @return this object for chaining.
+	 * @throws Exception
+	 */
+	public Matrix3f transpose() throws Exception {
+		return transposeLocal();
+	}
+
+	/**
+	 * <code>transposeNew</code> returns a transposed version of this matrix.
+	 * 
+	 * @return The new Matrix3f object.
+	 */
+	public Matrix3f transposeNew() {
+        return new Matrix3f(m00, m10, m20, m01, m11, m21, m02, m12, m22);
+	}
+
+	/**
+	 * <code>toString</code> returns the string representation of this object.
+	 * It is in a format of a 3x3 matrix. For example, an identity matrix would
+	 * be represented by the following string. com.jme.math.Matrix3f <br>
+	 * [<br>
+	 * 1.0 0.0 0.0 <br>
+	 * 0.0 1.0 0.0 <br>
+	 * 0.0 0.0 1.0 <br>
+	 * ]<br>
+	 * 
+	 * @return the string representation of this object.
+	 */
+	@Override
+	public String toString() {
+		StringBuffer result = new StringBuffer("com.jme.math.Matrix3f\n[\n");
+		result.append(' ');
+		result.append(m00);
+		result.append("  ");
+		result.append(m01);
+		result.append("  ");
+		result.append(m02);
+		result.append(" \n");
+		result.append(' ');
+		result.append(m10);
+		result.append("  ");
+		result.append(m11);
+		result.append("  ");
+		result.append(m12);
+		result.append(" \n");
+		result.append(' ');
+		result.append(m20);
+		result.append("  ");
+		result.append(m21);
+		result.append("  ");
+		result.append(m22);
+		result.append(" \n]");
+		return result.toString();
+	}
+
+	/**
+	 * 
+	 * <code>hashCode</code> returns the hash code value as an integer and is
+	 * supported for the benefit of hashing based collection classes such as
+	 * Hashtable, HashMap, HashSet etc.
+	 * 
+	 * @return the hashcode for this instance of Matrix4f.
+	 * @see java.lang.Object#hashCode()
+	 */
+	@Override
+	public int hashCode() {
+		int hash = 37;
+		hash = 37 * hash + Float.floatToIntBits(m00);
+		hash = 37 * hash + Float.floatToIntBits(m01);
+		hash = 37 * hash + Float.floatToIntBits(m02);
+
+		hash = 37 * hash + Float.floatToIntBits(m10);
+		hash = 37 * hash + Float.floatToIntBits(m11);
+		hash = 37 * hash + Float.floatToIntBits(m12);
+
+		hash = 37 * hash + Float.floatToIntBits(m20);
+		hash = 37 * hash + Float.floatToIntBits(m21);
+		hash = 37 * hash + Float.floatToIntBits(m22);
+
+		return hash;
+	}
+
+	/**
+	 * are these two matrices the same? they are is they both have the same mXX
+	 * values.
+	 * 
+	 * @param o
+	 *            the object to compare for equality
+	 * @return true if they are equal
+	 */
+	@Override
+	public boolean equals(Object o) {
+		if (!(o instanceof Matrix3f) || o == null) {
+			return false;
+		}
+
+		if (this == o) {
+			return true;
+		}
+
+		Matrix3f comp = (Matrix3f) o;
+		if (Float.compare(m00, comp.m00) != 0)
+			return false;
+		if (Float.compare(m01, comp.m01) != 0)
+			return false;
+		if (Float.compare(m02, comp.m02) != 0)
+			return false;
+
+		if (Float.compare(m10, comp.m10) != 0)
+			return false;
+		if (Float.compare(m11, comp.m11) != 0)
+			return false;
+		if (Float.compare(m12, comp.m12) != 0)
+			return false;
+
+		if (Float.compare(m20, comp.m20) != 0)
+			return false;
+		if (Float.compare(m21, comp.m21) != 0)
+			return false;
+		return Float.compare(m22, comp.m22) == 0;
+	}
+
+	/**
+	 * A function for creating a rotation matrix that rotates a vector called
+	 * "start" into another vector called "end".
+	 * 
+	 * @param start
+	 *            normalized non-zero starting vector
+	 * @param end
+	 *            normalized non-zero ending vector
+	 * @throws Exception
+	 * @see "Tomas Miller, John Hughes \"Efficiently Building a Matrix to Rotate
+	 *      \ One Vector to Another\" Journal of Graphics Tools, 4(4):1-4, 1999"
+	 */
+	public void fromStartEndVectors(Vector3f start, Vector3f end)
+			throws Exception {
+		Vector3f v = new Vector3f();
+		float e, h, f;
+
+		start.cross(end, v);
+		e = start.dot(end);
+		f = (e < 0) ? -e : e;
+
+		// if "from" and "to" vectors are nearly parallel
+		if (f > 1.0f - FastMath.ZERO_TOLERANCE) {
+			Vector3f u = new Vector3f();
+			Vector3f x = new Vector3f();
+			float c1, c2, c3; /* coefficients for later use */
+			int i, j;
+
+			x.x = (start.x > 0.0) ? start.x : -start.x;
+			x.y = (start.y > 0.0) ? start.y : -start.y;
+			x.z = (start.z > 0.0) ? start.z : -start.z;
+
+			if (x.x < x.y) {
+				if (x.x < x.z) {
+					x.x = 1.0f;
+					x.y = x.z = 0.0f;
+				} else {
+					x.z = 1.0f;
+					x.x = x.y = 0.0f;
+				}
+			} else {
+				if (x.y < x.z) {
+					x.y = 1.0f;
+					x.x = x.z = 0.0f;
+				} else {
+					x.z = 1.0f;
+					x.x = x.y = 0.0f;
+				}
+			}
+
+			u.x = x.x - start.x;
+			u.y = x.y - start.y;
+			u.z = x.z - start.z;
+			v.x = x.x - end.x;
+			v.y = x.y - end.y;
+			v.z = x.z - end.z;
+
+			c1 = 2.0f / u.dot(u);
+			c2 = 2.0f / v.dot(v);
+			c3 = c1 * c2 * u.dot(v);
+
+			for (i = 0; i < 3; i++) {
+				for (j = 0; j < 3; j++) {
+					float val = -c1 * u.get(i) * u.get(j) - c2 * v.get(i)
+							* v.get(j) + c3 * v.get(i) * u.get(j);
+					set(i, j, val);
+				}
+				float val = get(i, i);
+				set(i, i, val + 1.0f);
+			}
+		} else {
+			// the most common case, unless "start"="end", or "start"=-"end"
+			float hvx, hvz, hvxy, hvxz, hvyz;
+			h = 1.0f / (1.0f + e);
+			hvx = h * v.x;
+			hvz = h * v.z;
+			hvxy = hvx * v.y;
+			hvxz = hvx * v.z;
+			hvyz = hvz * v.y;
+			set(0, 0, e + hvx * v.x);
+			set(0, 1, hvxy - v.z);
+			set(0, 2, hvxz + v.y);
+
+			set(1, 0, hvxy + v.z);
+			set(1, 1, e + h * v.y * v.y);
+			set(1, 2, hvyz - v.x);
+
+			set(2, 0, hvxz - v.y);
+			set(2, 1, hvyz + v.x);
+			set(2, 2, e + hvz * v.z);
+		}
+	}
+
+	/**
+	 * <code>scale</code> scales the operation performed by this matrix on a
+	 * per-component basis.
+	 * 
+	 * @param scale
+	 *            The scale applied to each of the X, Y and Z output values.
+	 */
+	public void scale(Vector3f scale) {
+		m00 *= scale.x;
+		m10 *= scale.x;
+		m20 *= scale.x;
+		m01 *= scale.y;
+		m11 *= scale.y;
+		m21 *= scale.y;
+		m02 *= scale.z;
+		m12 *= scale.z;
+		m22 *= scale.z;
+	}
+
+	static boolean equalIdentity(Matrix3f mat) {
+		if (Math.abs(mat.m00 - 1) > 1e-4)
+			return false;
+		if (Math.abs(mat.m11 - 1) > 1e-4)
+			return false;
+		if (Math.abs(mat.m22 - 1) > 1e-4)
+			return false;
+
+		if (Math.abs(mat.m01) > 1e-4)
+			return false;
+		if (Math.abs(mat.m02) > 1e-4)
+			return false;
+
+		if (Math.abs(mat.m10) > 1e-4)
+			return false;
+		if (Math.abs(mat.m12) > 1e-4)
+			return false;
+
+		if (Math.abs(mat.m20) > 1e-4)
+			return false;
+		return !(Math.abs(mat.m21) > 1e-4);
+	}
+
+	@Override
+	public Matrix3f clone() {
+		try {
+			return (Matrix3f) super.clone();
+		} catch (CloneNotSupportedException e) {
+			Logger.error( e);
+			throw new AssertionError(); // can not happen
+		}
+	}
+}
\ No newline at end of file
diff --git a/src/engine/math/Matrix4f.java b/src/engine/math/Matrix4f.java
new file mode 100644
index 00000000..e0b18b00
--- /dev/null
+++ b/src/engine/math/Matrix4f.java
@@ -0,0 +1,1791 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+package engine.math;
+
+import org.pmw.tinylog.Logger;
+
+import java.nio.FloatBuffer;
+
+
+/**
+ * <code>Matrix4f</code> defines and maintains a 4x4 matrix in row major order.
+ * This matrix is intended for use in a translation and rotational capacity. It
+ * provides convenience methods for creating the matrix from a multitude of
+ * sources.
+ * 
+ * Matrices are stored assuming column vectors on the right, with the
+ * translation in the rightmost column. Element numbering is row,column, so m03
+ * is the zeroth row, third column, which is the "x" translation part. This
+ * means that the implicit storage order is column major. However, the get() and
+ * set() functions on float arrays default to row major order!
+ * 
+ */
+public class Matrix4f{
+
+	public float m00, m01, m02, m03;
+
+	public float m10, m11, m12, m13;
+
+	public float m20, m21, m22, m23;
+
+	public float m30, m31, m32, m33;
+
+	/**
+	 * Constructor instantiates a new <code>Matrix</code> that is set to the
+	 * identity matrix.
+	 * 
+	 */
+	public Matrix4f() {
+		loadIdentity();
+	}
+
+	/**
+	 * constructs a matrix with the given values.
+	 */
+	public Matrix4f(float m00, float m01, float m02, float m03, float m10, float m11, float m12, float m13, float m20, float m21,
+			float m22, float m23, float m30, float m31, float m32, float m33) {
+
+		this.m00 = m00;
+		this.m01 = m01;
+		this.m02 = m02;
+		this.m03 = m03;
+		this.m10 = m10;
+		this.m11 = m11;
+		this.m12 = m12;
+		this.m13 = m13;
+		this.m20 = m20;
+		this.m21 = m21;
+		this.m22 = m22;
+		this.m23 = m23;
+		this.m30 = m30;
+		this.m31 = m31;
+		this.m32 = m32;
+		this.m33 = m33;
+	}
+
+	/**
+	 * Create a new Matrix4f, given data in column-major format.
+	 * 
+	 * @param array
+	 *            An array of 16 floats in column-major format (translation in
+	 *            elements 12, 13 and 14).
+	 * @throws Exception
+	 */
+	public Matrix4f(float[] array) throws Exception {
+		set(array, false);
+	}
+
+	/**
+	 * Constructor instantiates a new <code>Matrix</code> that is set to the
+	 * provided matrix. This constructor copies a given Matrix. If the provided
+	 * matrix is null, the constructor sets the matrix to the identity.
+	 * 
+	 * @param mat
+	 *            the matrix to copy.
+	 */
+	public Matrix4f(Matrix4f mat) {
+		copy(mat);
+	}
+
+	/**
+	 * <code>copy</code> transfers the contents of a given matrix to this
+	 * matrix. If a null matrix is supplied, this matrix is set to the identity
+	 * matrix.
+	 * 
+	 * @param matrix
+	 *            the matrix to copy.
+	 */
+	public void copy(Matrix4f matrix) {
+		if (null == matrix) {
+			loadIdentity();
+		} else {
+			m00 = matrix.m00;
+			m01 = matrix.m01;
+			m02 = matrix.m02;
+			m03 = matrix.m03;
+			m10 = matrix.m10;
+			m11 = matrix.m11;
+			m12 = matrix.m12;
+			m13 = matrix.m13;
+			m20 = matrix.m20;
+			m21 = matrix.m21;
+			m22 = matrix.m22;
+			m23 = matrix.m23;
+			m30 = matrix.m30;
+			m31 = matrix.m31;
+			m32 = matrix.m32;
+			m33 = matrix.m33;
+		}
+	}
+
+	/**
+	 * <code>get</code> retrieves the values of this object into a float array
+	 * in row-major order.
+	 * 
+	 * @param matrix
+	 *            the matrix to set the values into.
+	 * @throws Exception
+	 */
+	public void get(float[] matrix) throws Exception {
+		get(matrix, true);
+	}
+
+	/**
+	 * <code>set</code> retrieves the values of this object into a float array.
+	 * 
+	 * @param matrix
+	 *            the matrix to set the values into.
+	 * @param rowMajor
+	 *            whether the outgoing data is in row or column major order.
+	 * @throws Exception
+	 */
+	public void get(float[] matrix, boolean rowMajor) throws Exception {
+		if (matrix.length != 16)
+			throw new Exception("Array must be of size 16.");
+
+		if (rowMajor) {
+			matrix[0] = m00;
+			matrix[1] = m01;
+			matrix[2] = m02;
+			matrix[3] = m03;
+			matrix[4] = m10;
+			matrix[5] = m11;
+			matrix[6] = m12;
+			matrix[7] = m13;
+			matrix[8] = m20;
+			matrix[9] = m21;
+			matrix[10] = m22;
+			matrix[11] = m23;
+			matrix[12] = m30;
+			matrix[13] = m31;
+			matrix[14] = m32;
+			matrix[15] = m33;
+		} else {
+			matrix[0] = m00;
+			matrix[4] = m01;
+			matrix[8] = m02;
+			matrix[12] = m03;
+			matrix[1] = m10;
+			matrix[5] = m11;
+			matrix[9] = m12;
+			matrix[13] = m13;
+			matrix[2] = m20;
+			matrix[6] = m21;
+			matrix[10] = m22;
+			matrix[14] = m23;
+			matrix[3] = m30;
+			matrix[7] = m31;
+			matrix[11] = m32;
+			matrix[15] = m33;
+		}
+	}
+
+	/**
+	 * <code>get</code> retrieves a value from the matrix at the given position.
+	 * If the position is invalid a <code>Exception</code> is thrown.
+	 * 
+	 * @param i
+	 *            the row index.
+	 * @param j
+	 *            the column index.
+	 * @return the value at (i, j).
+	 * @throws Exception
+	 */
+	public float get(int i, int j) throws Exception {
+		switch (i) {
+		case 0:
+			switch (j) {
+			case 0:
+				return m00;
+			case 1:
+				return m01;
+			case 2:
+				return m02;
+			case 3:
+				return m03;
+			}
+		case 1:
+			switch (j) {
+			case 0:
+				return m10;
+			case 1:
+				return m11;
+			case 2:
+				return m12;
+			case 3:
+				return m13;
+			}
+		case 2:
+			switch (j) {
+			case 0:
+				return m20;
+			case 1:
+				return m21;
+			case 2:
+				return m22;
+			case 3:
+				return m23;
+			}
+		case 3:
+			switch (j) {
+			case 0:
+				return m30;
+			case 1:
+				return m31;
+			case 2:
+				return m32;
+			case 3:
+				return m33;
+			}
+		}
+
+		throw new Exception("Invalid indices into matrix.");
+	}
+
+	/**
+	 * <code>getColumn</code> returns one of three columns specified by the
+	 * parameter. This column is returned as a float array of length 4.
+	 * 
+	 * @param i
+	 *            the column to retrieve. Must be between 0 and 3.
+	 * @return the column specified by the index.
+	 * @throws Exception
+	 */
+	public float[] getColumn(int i) throws Exception {
+		return getColumn(i, null);
+	}
+
+	/**
+	 * <code>getColumn</code> returns one of three columns specified by the
+	 * parameter. This column is returned as a float[4].
+	 * 
+	 * @param i
+	 *            the column to retrieve. Must be between 0 and 3.
+	 * @param store
+	 *            the float array to store the result in. if null, a new one is
+	 *            created.
+	 * @return the column specified by the index.
+	 */
+	public float[] getColumn(int i, float[] store) throws Exception {
+		if (store == null)
+			store = new float[4];
+		switch (i) {
+		case 0:
+			store[0] = m00;
+			store[1] = m10;
+			store[2] = m20;
+			store[3] = m30;
+			break;
+		case 1:
+			store[0] = m01;
+			store[1] = m11;
+			store[2] = m21;
+			store[3] = m31;
+			break;
+		case 2:
+			store[0] = m02;
+			store[1] = m12;
+			store[2] = m22;
+			store[3] = m32;
+			break;
+		case 3:
+			store[0] = m03;
+			store[1] = m13;
+			store[2] = m23;
+			store[3] = m33;
+			break;
+		default:
+			throw new Exception("Invalid column index. " + i);
+		}
+		return store;
+	}
+
+	/**
+	 * 
+	 * <code>setColumn</code> sets a particular column of this matrix to that
+	 * represented by the provided vector.
+	 * 
+	 * @param i
+	 *            the column to set.
+	 * @param column
+	 *            the data to set.
+	 */
+	public void setColumn(int i, float[] column) throws Exception {
+
+		if (column == null) {
+			return;
+		}
+		switch (i) {
+		case 0:
+			m00 = column[0];
+			m10 = column[1];
+			m20 = column[2];
+			m30 = column[3];
+			break;
+		case 1:
+			m01 = column[0];
+			m11 = column[1];
+			m21 = column[2];
+			m31 = column[3];
+			break;
+		case 2:
+			m02 = column[0];
+			m12 = column[1];
+			m22 = column[2];
+			m32 = column[3];
+			break;
+		case 3:
+			m03 = column[0];
+			m13 = column[1];
+			m23 = column[2];
+			m33 = column[3];
+			break;
+		default:
+			throw new Exception("Invalid column index. " + i);
+		}
+	}
+
+	/**
+	 * <code>set</code> places a given value into the matrix at the given
+	 * position. If the position is invalid a <code>Exception</code> is thrown.
+	 * 
+	 * @param i
+	 *            the row index.
+	 * @param j
+	 *            the column index.
+	 * @param value
+	 *            the value for (i, j).
+	 */
+	public void set(int i, int j, float value) throws Exception {
+		switch (i) {
+		case 0:
+			switch (j) {
+			case 0:
+				m00 = value;
+				return;
+			case 1:
+				m01 = value;
+				return;
+			case 2:
+				m02 = value;
+				return;
+			case 3:
+				m03 = value;
+				return;
+			}
+		case 1:
+			switch (j) {
+			case 0:
+				m10 = value;
+				return;
+			case 1:
+				m11 = value;
+				return;
+			case 2:
+				m12 = value;
+				return;
+			case 3:
+				m13 = value;
+				return;
+			}
+		case 2:
+			switch (j) {
+			case 0:
+				m20 = value;
+				return;
+			case 1:
+				m21 = value;
+				return;
+			case 2:
+				m22 = value;
+				return;
+			case 3:
+				m23 = value;
+				return;
+			}
+		case 3:
+			switch (j) {
+			case 0:
+				m30 = value;
+				return;
+			case 1:
+				m31 = value;
+				return;
+			case 2:
+				m32 = value;
+				return;
+			case 3:
+				m33 = value;
+				return;
+			}
+		}
+		throw new Exception("Invalid indices into matrix.");
+	}
+
+	/**
+	 * <code>set</code> sets the values of this matrix from an array of values.
+	 * 
+	 * @param matrix
+	 *            the matrix to set the value to.
+	 * @throws Exception
+	 *             if the array is not of size 16.
+	 */
+	public void set(float[][] matrix) throws Exception {
+		if (matrix.length != 4 || matrix[0].length != 4) {
+			throw new Exception("Array must be of size 16.");
+		}
+
+		m00 = matrix[0][0];
+		m01 = matrix[0][1];
+		m02 = matrix[0][2];
+		m03 = matrix[0][3];
+		m10 = matrix[1][0];
+		m11 = matrix[1][1];
+		m12 = matrix[1][2];
+		m13 = matrix[1][3];
+		m20 = matrix[2][0];
+		m21 = matrix[2][1];
+		m22 = matrix[2][2];
+		m23 = matrix[2][3];
+		m30 = matrix[3][0];
+		m31 = matrix[3][1];
+		m32 = matrix[3][2];
+		m33 = matrix[3][3];
+	}
+
+	/**
+	 * <code>set</code> sets the values of this matrix from another matrix.
+	 * 
+	 * @param matrix
+	 *            the matrix to read the value from.
+	 */
+	public Matrix4f set(Matrix4f matrix) {
+		m00 = matrix.m00;
+		m01 = matrix.m01;
+		m02 = matrix.m02;
+		m03 = matrix.m03;
+		m10 = matrix.m10;
+		m11 = matrix.m11;
+		m12 = matrix.m12;
+		m13 = matrix.m13;
+		m20 = matrix.m20;
+		m21 = matrix.m21;
+		m22 = matrix.m22;
+		m23 = matrix.m23;
+		m30 = matrix.m30;
+		m31 = matrix.m31;
+		m32 = matrix.m32;
+		m33 = matrix.m33;
+		return this;
+	}
+
+	/**
+	 * <code>set</code> sets the values of this matrix from an array of values
+	 * assuming that the data is rowMajor order;
+	 * 
+	 * @param matrix
+	 *            the matrix to set the value to.
+	 * @throws Exception
+	 */
+	public void set(float[] matrix) throws Exception {
+		set(matrix, true);
+	}
+
+	/**
+	 * <code>set</code> sets the values of this matrix from an array of values;
+	 * 
+	 * @param matrix
+	 *            the matrix to set the value to.
+	 * @param rowMajor
+	 *            whether the incoming data is in row or column major order.
+	 * @throws Exception
+	 */
+	public void set(float[] matrix, boolean rowMajor) throws Exception {
+		if (matrix.length != 16)
+			throw new Exception("Array must be of size 16.");
+
+		if (rowMajor) {
+			m00 = matrix[0];
+			m01 = matrix[1];
+			m02 = matrix[2];
+			m03 = matrix[3];
+			m10 = matrix[4];
+			m11 = matrix[5];
+			m12 = matrix[6];
+			m13 = matrix[7];
+			m20 = matrix[8];
+			m21 = matrix[9];
+			m22 = matrix[10];
+			m23 = matrix[11];
+			m30 = matrix[12];
+			m31 = matrix[13];
+			m32 = matrix[14];
+			m33 = matrix[15];
+		} else {
+			m00 = matrix[0];
+			m01 = matrix[4];
+			m02 = matrix[8];
+			m03 = matrix[12];
+			m10 = matrix[1];
+			m11 = matrix[5];
+			m12 = matrix[9];
+			m13 = matrix[13];
+			m20 = matrix[2];
+			m21 = matrix[6];
+			m22 = matrix[10];
+			m23 = matrix[14];
+			m30 = matrix[3];
+			m31 = matrix[7];
+			m32 = matrix[11];
+			m33 = matrix[15];
+		}
+	}
+
+	public Matrix4f transpose() throws Exception {
+		float[] tmp = new float[16];
+		get(tmp, true);
+        return new Matrix4f(tmp);
+	}
+
+	/**
+	 * <code>transpose</code> locally transposes this Matrix.
+	 * 
+	 * @return this object for chaining.
+	 */
+	public Matrix4f transposeLocal() {
+		float tmp = m01;
+		m01 = m10;
+		m10 = tmp;
+
+		tmp = m02;
+		m02 = m20;
+		m20 = tmp;
+
+		tmp = m03;
+		m03 = m30;
+		m30 = tmp;
+
+		tmp = m12;
+		m12 = m21;
+		m21 = tmp;
+
+		tmp = m13;
+		m13 = m31;
+		m31 = tmp;
+
+		tmp = m23;
+		m23 = m32;
+		m32 = tmp;
+
+		return this;
+	}
+
+	/**
+	 * <code>fillFloatBuffer</code> fills a FloatBuffer object with the matrix
+	 * data.
+	 * 
+	 * @param fb
+	 *            the buffer to fill, must be correct size
+	 * @return matrix data as a FloatBuffer.
+	 */
+	public FloatBuffer fillFloatBuffer(FloatBuffer fb) {
+		return fillFloatBuffer(fb, false);
+	}
+
+	/**
+	 * <code>fillFloatBuffer</code> fills a FloatBuffer object with the matrix
+	 * data.
+	 * 
+	 * @param fb
+	 *            the buffer to fill, starting at current position. Must have
+	 *            room for 16 more floats.
+	 * @param columnMajor
+	 *            if true, this buffer should be filled with column major data,
+	 *            otherwise it will be filled row major.
+	 * @return matrix data as a FloatBuffer. (position is advanced by 16 and any
+	 *         limit set is not changed).
+	 */
+	public FloatBuffer fillFloatBuffer(FloatBuffer fb, boolean columnMajor) {
+		if (columnMajor) {
+			fb.put(m00).put(m10).put(m20).put(m30);
+			fb.put(m01).put(m11).put(m21).put(m31);
+			fb.put(m02).put(m12).put(m22).put(m32);
+			fb.put(m03).put(m13).put(m23).put(m33);
+		} else {
+			fb.put(m00).put(m01).put(m02).put(m03);
+			fb.put(m10).put(m11).put(m12).put(m13);
+			fb.put(m20).put(m21).put(m22).put(m23);
+			fb.put(m30).put(m31).put(m32).put(m33);
+		}
+		return fb;
+	}
+
+	/**
+	 * <code>readFloatBuffer</code> reads value for this matrix from a
+	 * FloatBuffer.
+	 * 
+	 * @param fb
+	 *            the buffer to read from, must be correct size
+	 * @return this data as a FloatBuffer.
+	 */
+	public Matrix4f readFloatBuffer(FloatBuffer fb) {
+		return readFloatBuffer(fb, false);
+	}
+
+	/**
+	 * <code>readFloatBuffer</code> reads value for this matrix from a
+	 * FloatBuffer.
+	 * 
+	 * @param fb
+	 *            the buffer to read from, must be correct size
+	 * @param columnMajor
+	 *            if true, this buffer should be filled with column major data,
+	 *            otherwise it will be filled row major.
+	 * @return this data as a FloatBuffer.
+	 */
+	public Matrix4f readFloatBuffer(FloatBuffer fb, boolean columnMajor) {
+
+		if (columnMajor) {
+			m00 = fb.get();
+			m10 = fb.get();
+			m20 = fb.get();
+			m30 = fb.get();
+			m01 = fb.get();
+			m11 = fb.get();
+			m21 = fb.get();
+			m31 = fb.get();
+			m02 = fb.get();
+			m12 = fb.get();
+			m22 = fb.get();
+			m32 = fb.get();
+			m03 = fb.get();
+			m13 = fb.get();
+			m23 = fb.get();
+			m33 = fb.get();
+		} else {
+			m00 = fb.get();
+			m01 = fb.get();
+			m02 = fb.get();
+			m03 = fb.get();
+			m10 = fb.get();
+			m11 = fb.get();
+			m12 = fb.get();
+			m13 = fb.get();
+			m20 = fb.get();
+			m21 = fb.get();
+			m22 = fb.get();
+			m23 = fb.get();
+			m30 = fb.get();
+			m31 = fb.get();
+			m32 = fb.get();
+			m33 = fb.get();
+		}
+		return this;
+	}
+
+	/**
+	 * Legacy wrapper. This name implies that an identity matrix is "loaded",
+	 * but one is not. Instead, the elements of 'this' identity are set.
+	 */
+	public void loadIdentity() {
+		setIdentity();
+	}
+
+	/**
+	 * Sets this matrix to the identity matrix, namely all zeros with ones along
+	 * the diagonal.
+	 * 
+	 */
+	public void setIdentity() {
+		m01 = m02 = m03 = m10 = m12 = m13 = m20 = m21 = m23 = m30 = m31 = m32 = 0.0f;
+		m00 = m11 = m22 = m33 = 1.0f;
+	}
+
+	/**
+	 * <code>fromAngleAxis</code> sets this matrix4f to the values specified by
+	 * an angle and an axis of rotation. This method creates an object, so use
+	 * fromAngleNormalAxis if your axis is already normalized.
+	 * 
+	 * @param angle
+	 *            the angle to rotate (in radians).
+	 * @param axis
+	 *            the axis of rotation.
+	 */
+	public void fromAngleAxis(float angle, Vector3f axis) {
+		Vector3f normAxis = axis.normalize();
+		fromAngleNormalAxis(angle, normAxis);
+	}
+
+	/**
+	 * <code>fromAngleNormalAxis</code> sets this matrix4f to the values
+	 * specified by an angle and a normalized axis of rotation.
+	 * 
+	 * @param angle
+	 *            the angle to rotate (in radians).
+	 * @param axis
+	 *            the axis of rotation (already normalized).
+	 */
+	public void fromAngleNormalAxis(float angle, Vector3f axis) {
+		zero();
+		m33 = 1;
+
+		float fCos = FastMath.cos(angle);
+		float fSin = FastMath.sin(angle);
+		float fOneMinusCos = ((float) 1.0) - fCos;
+		float fX2 = axis.x * axis.x;
+		float fY2 = axis.y * axis.y;
+		float fZ2 = axis.z * axis.z;
+		float fXYM = axis.x * axis.y * fOneMinusCos;
+		float fXZM = axis.x * axis.z * fOneMinusCos;
+		float fYZM = axis.y * axis.z * fOneMinusCos;
+		float fXSin = axis.x * fSin;
+		float fYSin = axis.y * fSin;
+		float fZSin = axis.z * fSin;
+
+		m00 = fX2 * fOneMinusCos + fCos;
+		m01 = fXYM - fZSin;
+		m02 = fXZM + fYSin;
+		m10 = fXYM + fZSin;
+		m11 = fY2 * fOneMinusCos + fCos;
+		m12 = fYZM - fXSin;
+		m20 = fXZM - fYSin;
+		m21 = fYZM + fXSin;
+		m22 = fZ2 * fOneMinusCos + fCos;
+	}
+
+	/**
+	 * <code>mult</code> multiplies this matrix by a scalar.
+	 * 
+	 * @param scalar
+	 *            the scalar to multiply this matrix by.
+	 */
+	public void multLocal(float scalar) {
+		m00 *= scalar;
+		m01 *= scalar;
+		m02 *= scalar;
+		m03 *= scalar;
+		m10 *= scalar;
+		m11 *= scalar;
+		m12 *= scalar;
+		m13 *= scalar;
+		m20 *= scalar;
+		m21 *= scalar;
+		m22 *= scalar;
+		m23 *= scalar;
+		m30 *= scalar;
+		m31 *= scalar;
+		m32 *= scalar;
+		m33 *= scalar;
+	}
+
+	public Matrix4f mult(float scalar) {
+		Matrix4f out = new Matrix4f();
+		out.set(this);
+		out.multLocal(scalar);
+		return out;
+	}
+
+	public Matrix4f mult(float scalar, Matrix4f store) {
+		store.set(this);
+		store.multLocal(scalar);
+		return store;
+	}
+
+	/**
+	 * <code>mult</code> multiplies this matrix with another matrix. The result
+	 * matrix will then be returned. This matrix will be on the left hand side,
+	 * while the parameter matrix will be on the right.
+	 * 
+	 * @param in2
+	 *            the matrix to multiply this matrix by.
+	 * @return the resultant matrix
+	 */
+	public Matrix4f mult(Matrix4f in2) {
+		return mult(in2, null);
+	}
+
+	/**
+	 * <code>mult</code> multiplies this matrix with another matrix. The result
+	 * matrix will then be returned. This matrix will be on the left hand side,
+	 * while the parameter matrix will be on the right.
+	 * 
+	 * @param in2
+	 *            the matrix to multiply this matrix by.
+	 * @param store
+	 *            where to store the result. It is safe for in2 and store to be
+	 *            the same object.
+	 * @return the resultant matrix
+	 */
+	public Matrix4f mult(Matrix4f in2, Matrix4f store) {
+		if (store == null)
+			store = new Matrix4f();
+
+		float temp00, temp01, temp02, temp03;
+		float temp10, temp11, temp12, temp13;
+		float temp20, temp21, temp22, temp23;
+		float temp30, temp31, temp32, temp33;
+
+		temp00 = m00 * in2.m00 + m01 * in2.m10 + m02 * in2.m20 + m03 * in2.m30;
+		temp01 = m00 * in2.m01 + m01 * in2.m11 + m02 * in2.m21 + m03 * in2.m31;
+		temp02 = m00 * in2.m02 + m01 * in2.m12 + m02 * in2.m22 + m03 * in2.m32;
+		temp03 = m00 * in2.m03 + m01 * in2.m13 + m02 * in2.m23 + m03 * in2.m33;
+
+		temp10 = m10 * in2.m00 + m11 * in2.m10 + m12 * in2.m20 + m13 * in2.m30;
+		temp11 = m10 * in2.m01 + m11 * in2.m11 + m12 * in2.m21 + m13 * in2.m31;
+		temp12 = m10 * in2.m02 + m11 * in2.m12 + m12 * in2.m22 + m13 * in2.m32;
+		temp13 = m10 * in2.m03 + m11 * in2.m13 + m12 * in2.m23 + m13 * in2.m33;
+
+		temp20 = m20 * in2.m00 + m21 * in2.m10 + m22 * in2.m20 + m23 * in2.m30;
+		temp21 = m20 * in2.m01 + m21 * in2.m11 + m22 * in2.m21 + m23 * in2.m31;
+		temp22 = m20 * in2.m02 + m21 * in2.m12 + m22 * in2.m22 + m23 * in2.m32;
+		temp23 = m20 * in2.m03 + m21 * in2.m13 + m22 * in2.m23 + m23 * in2.m33;
+
+		temp30 = m30 * in2.m00 + m31 * in2.m10 + m32 * in2.m20 + m33 * in2.m30;
+		temp31 = m30 * in2.m01 + m31 * in2.m11 + m32 * in2.m21 + m33 * in2.m31;
+		temp32 = m30 * in2.m02 + m31 * in2.m12 + m32 * in2.m22 + m33 * in2.m32;
+		temp33 = m30 * in2.m03 + m31 * in2.m13 + m32 * in2.m23 + m33 * in2.m33;
+
+		store.m00 = temp00;
+		store.m01 = temp01;
+		store.m02 = temp02;
+		store.m03 = temp03;
+		store.m10 = temp10;
+		store.m11 = temp11;
+		store.m12 = temp12;
+		store.m13 = temp13;
+		store.m20 = temp20;
+		store.m21 = temp21;
+		store.m22 = temp22;
+		store.m23 = temp23;
+		store.m30 = temp30;
+		store.m31 = temp31;
+		store.m32 = temp32;
+		store.m33 = temp33;
+
+		return store;
+	}
+
+	/**
+	 * <code>mult</code> multiplies this matrix with another matrix. The results
+	 * are stored internally and a handle to this matrix will then be returned.
+	 * This matrix will be on the left hand side, while the parameter matrix
+	 * will be on the right.
+	 * 
+	 * @param in2
+	 *            the matrix to multiply this matrix by.
+	 * @return the resultant matrix
+	 */
+	public Matrix4f multLocal(Matrix4f in2) {
+
+		return mult(in2, this);
+	}
+
+	/**
+	 * <code>mult</code> multiplies a vector about a rotation matrix. The
+	 * resulting vector is returned as a new Vector3f.
+	 * 
+	 * @param vec
+	 *            vec to multiply against.
+	 * @return the rotated vector.
+	 */
+	public Vector3f mult(Vector3f vec) {
+		return mult(vec, null);
+	}
+
+	/**
+	 * <code>mult</code> multiplies a vector about a rotation matrix and adds
+	 * translation. The resulting vector is returned.
+	 * 
+	 * @param vec
+	 *            vec to multiply against.
+	 * @param store
+	 *            a vector to store the result in. Created if null is passed.
+	 * @return the rotated vector.
+	 */
+	public Vector3f mult(Vector3f vec, Vector3f store) {
+		if (store == null)
+			store = new Vector3f();
+
+		float vx = vec.x, vy = vec.y, vz = vec.z;
+		store.x = m00 * vx + m01 * vy + m02 * vz + m03;
+		store.y = m10 * vx + m11 * vy + m12 * vz + m13;
+		store.z = m20 * vx + m21 * vy + m22 * vz + m23;
+
+		return store;
+	}
+
+	/**
+	 * <code>mult</code> multiplies a vector about a rotation matrix. The
+	 * resulting vector is returned.
+	 * 
+	 * @param vec
+	 *            vec to multiply against.
+	 * @param store
+	 *            a vector to store the result in. created if null is passed.
+	 * @return the rotated vector.
+	 */
+	public Vector3f multAcross(Vector3f vec, Vector3f store) {
+		if (null == vec) {
+			return null;
+		}
+		if (store == null)
+			store = new Vector3f();
+
+		float vx = vec.x, vy = vec.y, vz = vec.z;
+		store.x = m00 * vx + m10 * vy + m20 * vz + m30 * 1;
+		store.y = m01 * vx + m11 * vy + m21 * vz + m31 * 1;
+		store.z = m02 * vx + m12 * vy + m22 * vz + m32 * 1;
+
+		return store;
+	}
+
+	/**
+	 * <code>mult</code> multiplies a quaternion about a matrix. The resulting
+	 * vector is returned.
+	 * 
+	 * @param vec
+	 *            vec to multiply against.
+	 * @param store
+	 *            a quaternion to store the result in. created if null is
+	 *            passed.
+	 * @return store = this * vec
+	 */
+	public Quaternion mult(Quaternion vec, Quaternion store) {
+
+		if (null == vec) {
+			return null;
+		}
+		if (store == null)
+			store = new Quaternion();
+
+		float x = m00 * vec.x + m10 * vec.y + m20 * vec.z + m30 * vec.w;
+		float y = m01 * vec.x + m11 * vec.y + m21 * vec.z + m31 * vec.w;
+		float z = m02 * vec.x + m12 * vec.y + m22 * vec.z + m32 * vec.w;
+		float w = m03 * vec.x + m13 * vec.y + m23 * vec.z + m33 * vec.w;
+		store.x = x;
+		store.y = y;
+		store.z = z;
+		store.w = w;
+
+		return store;
+	}
+
+	/**
+	 * <code>mult</code> multiplies an array of 4 floats against this rotation
+	 * matrix. The results are stored directly in the array. (vec4f x mat4f)
+	 * 
+	 * @param vec4f
+	 *            float array (size 4) to multiply against the matrix.
+	 * @return the vec4f for chaining.
+	 */
+	public float[] mult(float[] vec4f) {
+		if (null == vec4f || vec4f.length != 4) {
+			return null;
+		}
+
+		float x = vec4f[0], y = vec4f[1], z = vec4f[2], w = vec4f[3];
+
+		vec4f[0] = m00 * x + m01 * y + m02 * z + m03 * w;
+		vec4f[1] = m10 * x + m11 * y + m12 * z + m13 * w;
+		vec4f[2] = m20 * x + m21 * y + m22 * z + m23 * w;
+		vec4f[3] = m30 * x + m31 * y + m32 * z + m33 * w;
+
+		return vec4f;
+	}
+
+	/**
+	 * <code>mult</code> multiplies an array of 4 floats against this rotation
+	 * matrix. The results are stored directly in the array. (vec4f x mat4f)
+	 * 
+	 * @param vec4f
+	 *            float array (size 4) to multiply against the matrix.
+	 * @return the vec4f for chaining.
+	 */
+	public float[] multAcross(float[] vec4f) {
+		if (null == vec4f || vec4f.length != 4) {
+			return null;
+		}
+
+		float x = vec4f[0], y = vec4f[1], z = vec4f[2], w = vec4f[3];
+
+		vec4f[0] = m00 * x + m10 * y + m20 * z + m30 * w;
+		vec4f[1] = m01 * x + m11 * y + m21 * z + m31 * w;
+		vec4f[2] = m02 * x + m12 * y + m22 * z + m32 * w;
+		vec4f[3] = m03 * x + m13 * y + m23 * z + m33 * w;
+
+		return vec4f;
+	}
+
+	/**
+	 * Inverts this matrix as a new Matrix4f.
+	 * 
+	 * @return The new inverse matrix
+	 */
+	public Matrix4f invert() {
+		return invert(null);
+	}
+
+	/**
+	 * Inverts this matrix and stores it in the given store.
+	 * 
+	 * @return The store
+	 */
+	public Matrix4f invert(Matrix4f store) {
+		if (store == null)
+			store = new Matrix4f();
+
+		float fA0 = m00 * m11 - m01 * m10;
+		float fA1 = m00 * m12 - m02 * m10;
+		float fA2 = m00 * m13 - m03 * m10;
+		float fA3 = m01 * m12 - m02 * m11;
+		float fA4 = m01 * m13 - m03 * m11;
+		float fA5 = m02 * m13 - m03 * m12;
+		float fB0 = m20 * m31 - m21 * m30;
+		float fB1 = m20 * m32 - m22 * m30;
+		float fB2 = m20 * m33 - m23 * m30;
+		float fB3 = m21 * m32 - m22 * m31;
+		float fB4 = m21 * m33 - m23 * m31;
+		float fB5 = m22 * m33 - m23 * m32;
+		float fDet = fA0 * fB5 - fA1 * fB4 + fA2 * fB3 + fA3 * fB2 - fA4 * fB1 + fA5 * fB0;
+
+		if (FastMath.abs(fDet) <= 0)
+			return store.zero();
+
+		store.m00 = +m11 * fB5 - m12 * fB4 + m13 * fB3;
+		store.m10 = -m10 * fB5 + m12 * fB2 - m13 * fB1;
+		store.m20 = +m10 * fB4 - m11 * fB2 + m13 * fB0;
+		store.m30 = -m10 * fB3 + m11 * fB1 - m12 * fB0;
+		store.m01 = -m01 * fB5 + m02 * fB4 - m03 * fB3;
+		store.m11 = +m00 * fB5 - m02 * fB2 + m03 * fB1;
+		store.m21 = -m00 * fB4 + m01 * fB2 - m03 * fB0;
+		store.m31 = +m00 * fB3 - m01 * fB1 + m02 * fB0;
+		store.m02 = +m31 * fA5 - m32 * fA4 + m33 * fA3;
+		store.m12 = -m30 * fA5 + m32 * fA2 - m33 * fA1;
+		store.m22 = +m30 * fA4 - m31 * fA2 + m33 * fA0;
+		store.m32 = -m30 * fA3 + m31 * fA1 - m32 * fA0;
+		store.m03 = -m21 * fA5 + m22 * fA4 - m23 * fA3;
+		store.m13 = +m20 * fA5 - m22 * fA2 + m23 * fA1;
+		store.m23 = -m20 * fA4 + m21 * fA2 - m23 * fA0;
+		store.m33 = +m20 * fA3 - m21 * fA1 + m22 * fA0;
+
+		float fInvDet = 1.0f / fDet;
+		store.multLocal(fInvDet);
+
+		return store;
+	}
+
+	/**
+	 * Inverts this matrix locally.
+	 * 
+	 * @return this
+	 */
+	public Matrix4f invertLocal() {
+
+		float fA0 = m00 * m11 - m01 * m10;
+		float fA1 = m00 * m12 - m02 * m10;
+		float fA2 = m00 * m13 - m03 * m10;
+		float fA3 = m01 * m12 - m02 * m11;
+		float fA4 = m01 * m13 - m03 * m11;
+		float fA5 = m02 * m13 - m03 * m12;
+		float fB0 = m20 * m31 - m21 * m30;
+		float fB1 = m20 * m32 - m22 * m30;
+		float fB2 = m20 * m33 - m23 * m30;
+		float fB3 = m21 * m32 - m22 * m31;
+		float fB4 = m21 * m33 - m23 * m31;
+		float fB5 = m22 * m33 - m23 * m32;
+		float fDet = fA0 * fB5 - fA1 * fB4 + fA2 * fB3 + fA3 * fB2 - fA4 * fB1 + fA5 * fB0;
+
+		if (FastMath.abs(fDet) <= FastMath.FLT_EPSILON)
+			return zero();
+
+		float f00 = +m11 * fB5 - m12 * fB4 + m13 * fB3;
+		float f10 = -m10 * fB5 + m12 * fB2 - m13 * fB1;
+		float f20 = +m10 * fB4 - m11 * fB2 + m13 * fB0;
+		float f30 = -m10 * fB3 + m11 * fB1 - m12 * fB0;
+		float f01 = -m01 * fB5 + m02 * fB4 - m03 * fB3;
+		float f11 = +m00 * fB5 - m02 * fB2 + m03 * fB1;
+		float f21 = -m00 * fB4 + m01 * fB2 - m03 * fB0;
+		float f31 = +m00 * fB3 - m01 * fB1 + m02 * fB0;
+		float f02 = +m31 * fA5 - m32 * fA4 + m33 * fA3;
+		float f12 = -m30 * fA5 + m32 * fA2 - m33 * fA1;
+		float f22 = +m30 * fA4 - m31 * fA2 + m33 * fA0;
+		float f32 = -m30 * fA3 + m31 * fA1 - m32 * fA0;
+		float f03 = -m21 * fA5 + m22 * fA4 - m23 * fA3;
+		float f13 = +m20 * fA5 - m22 * fA2 + m23 * fA1;
+		float f23 = -m20 * fA4 + m21 * fA2 - m23 * fA0;
+		float f33 = +m20 * fA3 - m21 * fA1 + m22 * fA0;
+
+		m00 = f00;
+		m01 = f01;
+		m02 = f02;
+		m03 = f03;
+		m10 = f10;
+		m11 = f11;
+		m12 = f12;
+		m13 = f13;
+		m20 = f20;
+		m21 = f21;
+		m22 = f22;
+		m23 = f23;
+		m30 = f30;
+		m31 = f31;
+		m32 = f32;
+		m33 = f33;
+
+		float fInvDet = 1.0f / fDet;
+		multLocal(fInvDet);
+
+		return this;
+	}
+
+	/**
+	 * Returns a new matrix representing the adjoint of this matrix.
+	 * 
+	 * @return The adjoint matrix
+	 */
+	public Matrix4f adjoint() {
+		return adjoint(null);
+	}
+
+	/**
+	 * Places the adjoint of this matrix in store (creates store if null.)
+	 * 
+	 * @param store
+	 *            The matrix to store the result in. If null, a new matrix is
+	 *            created.
+	 * @return store
+	 */
+	public Matrix4f adjoint(Matrix4f store) {
+		if (store == null)
+			store = new Matrix4f();
+
+		float fA0 = m00 * m11 - m01 * m10;
+		float fA1 = m00 * m12 - m02 * m10;
+		float fA2 = m00 * m13 - m03 * m10;
+		float fA3 = m01 * m12 - m02 * m11;
+		float fA4 = m01 * m13 - m03 * m11;
+		float fA5 = m02 * m13 - m03 * m12;
+		float fB0 = m20 * m31 - m21 * m30;
+		float fB1 = m20 * m32 - m22 * m30;
+		float fB2 = m20 * m33 - m23 * m30;
+		float fB3 = m21 * m32 - m22 * m31;
+		float fB4 = m21 * m33 - m23 * m31;
+		float fB5 = m22 * m33 - m23 * m32;
+
+		store.m00 = +m11 * fB5 - m12 * fB4 + m13 * fB3;
+		store.m10 = -m10 * fB5 + m12 * fB2 - m13 * fB1;
+		store.m20 = +m10 * fB4 - m11 * fB2 + m13 * fB0;
+		store.m30 = -m10 * fB3 + m11 * fB1 - m12 * fB0;
+		store.m01 = -m01 * fB5 + m02 * fB4 - m03 * fB3;
+		store.m11 = +m00 * fB5 - m02 * fB2 + m03 * fB1;
+		store.m21 = -m00 * fB4 + m01 * fB2 - m03 * fB0;
+		store.m31 = +m00 * fB3 - m01 * fB1 + m02 * fB0;
+		store.m02 = +m31 * fA5 - m32 * fA4 + m33 * fA3;
+		store.m12 = -m30 * fA5 + m32 * fA2 - m33 * fA1;
+		store.m22 = +m30 * fA4 - m31 * fA2 + m33 * fA0;
+		store.m32 = -m30 * fA3 + m31 * fA1 - m32 * fA0;
+		store.m03 = -m21 * fA5 + m22 * fA4 - m23 * fA3;
+		store.m13 = +m20 * fA5 - m22 * fA2 + m23 * fA1;
+		store.m23 = -m20 * fA4 + m21 * fA2 - m23 * fA0;
+		store.m33 = +m20 * fA3 - m21 * fA1 + m22 * fA0;
+
+		return store;
+	}
+
+	/**
+	 * <code>determinant</code> generates the determinate of this matrix.
+	 * 
+	 * @return the determinate
+	 */
+	public float determinant() {
+		float fA0 = m00 * m11 - m01 * m10;
+		float fA1 = m00 * m12 - m02 * m10;
+		float fA2 = m00 * m13 - m03 * m10;
+		float fA3 = m01 * m12 - m02 * m11;
+		float fA4 = m01 * m13 - m03 * m11;
+		float fA5 = m02 * m13 - m03 * m12;
+		float fB0 = m20 * m31 - m21 * m30;
+		float fB1 = m20 * m32 - m22 * m30;
+		float fB2 = m20 * m33 - m23 * m30;
+		float fB3 = m21 * m32 - m22 * m31;
+		float fB4 = m21 * m33 - m23 * m31;
+		float fB5 = m22 * m33 - m23 * m32;
+        return fA0 * fB5 - fA1 * fB4 + fA2 * fB3 + fA3 * fB2 - fA4 * fB1 + fA5 * fB0;
+	}
+
+	/**
+	 * Sets all of the values in this matrix to zero.
+	 * 
+	 * @return this matrix
+	 */
+	public Matrix4f zero() {
+		m00 = m01 = m02 = m03 = m10 = m11 = m12 = m13 = m20 = m21 = m22 = m23 = m30 = m31 = m32 = m33 = 0.0f;
+		return this;
+	}
+
+	public Matrix4f add(Matrix4f mat) {
+		Matrix4f result = new Matrix4f();
+		result.m00 = this.m00 + mat.m00;
+		result.m01 = this.m01 + mat.m01;
+		result.m02 = this.m02 + mat.m02;
+		result.m03 = this.m03 + mat.m03;
+		result.m10 = this.m10 + mat.m10;
+		result.m11 = this.m11 + mat.m11;
+		result.m12 = this.m12 + mat.m12;
+		result.m13 = this.m13 + mat.m13;
+		result.m20 = this.m20 + mat.m20;
+		result.m21 = this.m21 + mat.m21;
+		result.m22 = this.m22 + mat.m22;
+		result.m23 = this.m23 + mat.m23;
+		result.m30 = this.m30 + mat.m30;
+		result.m31 = this.m31 + mat.m31;
+		result.m32 = this.m32 + mat.m32;
+		result.m33 = this.m33 + mat.m33;
+		return result;
+	}
+
+	/**
+	 * <code>add</code> adds the values of a parameter matrix to this matrix.
+	 * 
+	 * @param mat
+	 *            the matrix to add to this.
+	 */
+	public void addLocal(Matrix4f mat) {
+		m00 += mat.m00;
+		m01 += mat.m01;
+		m02 += mat.m02;
+		m03 += mat.m03;
+		m10 += mat.m10;
+		m11 += mat.m11;
+		m12 += mat.m12;
+		m13 += mat.m13;
+		m20 += mat.m20;
+		m21 += mat.m21;
+		m22 += mat.m22;
+		m23 += mat.m23;
+		m30 += mat.m30;
+		m31 += mat.m31;
+		m32 += mat.m32;
+		m33 += mat.m33;
+	}
+
+	public Vector3f toTranslationVector() {
+		return new Vector3f(m03, m13, m23);
+	}
+
+	public void toTranslationVector(Vector3f vector) {
+		vector.set(m03, m13, m23);
+	}
+
+	public Quaternion toRotationQuat() {
+		Quaternion quat = new Quaternion();
+		quat.fromRotationMatrix(toRotationMatrix());
+		return quat;
+	}
+
+	public void toRotationQuat(Quaternion q) {
+		q.fromRotationMatrix(toRotationMatrix());
+	}
+
+	public Matrix3f toRotationMatrix() {
+		return new Matrix3f(m00, m01, m02, m10, m11, m12, m20, m21, m22);
+
+	}
+
+	public void toRotationMatrix(Matrix3f mat) {
+		mat.m00 = m00;
+		mat.m01 = m01;
+		mat.m02 = m02;
+		mat.m10 = m10;
+		mat.m11 = m11;
+		mat.m12 = m12;
+		mat.m20 = m20;
+		mat.m21 = m21;
+		mat.m22 = m22;
+
+	}
+
+	/**
+	 * <code>setTranslation</code> will set the matrix's translation values.
+	 * 
+	 * @param translation
+	 *            the new values for the translation.
+	 * @throws Exception
+	 *             if translation is not size 3.
+	 */
+	public void setTranslation(float[] translation) throws Exception {
+		if (translation.length != 3) {
+			throw new Exception("Translation size must be 3.");
+		}
+		m03 = translation[0];
+		m13 = translation[1];
+		m23 = translation[2];
+	}
+
+	/**
+	 * <code>setTranslation</code> will set the matrix's translation values.
+	 * 
+	 * @param x
+	 *            value of the translation on the x axis
+	 * @param y
+	 *            value of the translation on the y axis
+	 * @param z
+	 *            value of the translation on the z axis
+	 */
+	public void setTranslation(float x, float y, float z) {
+		m03 = x;
+		m13 = y;
+		m23 = z;
+	}
+
+	/**
+	 * <code>setTranslation</code> will set the matrix's translation values.
+	 * 
+	 * @param translation
+	 *            the new values for the translation.
+	 */
+	public void setTranslation(Vector3f translation) {
+		m03 = translation.x;
+		m13 = translation.y;
+		m23 = translation.z;
+	}
+
+	/**
+	 * <code>setInverseTranslation</code> will set the matrix's inverse
+	 * translation values.
+	 * 
+	 * @param translation
+	 *            the new values for the inverse translation.
+	 * @throws Exception
+	 *             if translation is not size 3.
+	 */
+	public void setInverseTranslation(float[] translation) throws Exception {
+		if (translation.length != 3) {
+			throw new Exception("Translation size must be 3.");
+		}
+		m03 = -translation[0];
+		m13 = -translation[1];
+		m23 = -translation[2];
+	}
+
+	/**
+	 * <code>angleRotation</code> sets this matrix to that of a rotation about
+	 * three axes (x, y, z). Where each axis has a specified rotation in
+	 * degrees. These rotations are expressed in a single <code>Vector3f</code>
+	 * object.
+	 * 
+	 * @param angles
+	 *            the angles to rotate.
+	 */
+	public void angleRotation(Vector3f angles) {
+		float angle;
+		float sr, sp, sy, cr, cp, cy;
+
+		angle = (angles.z * FastMath.DEG_TO_RAD);
+		sy = FastMath.sin(angle);
+		cy = FastMath.cos(angle);
+		angle = (angles.y * FastMath.DEG_TO_RAD);
+		sp = FastMath.sin(angle);
+		cp = FastMath.cos(angle);
+		angle = (angles.x * FastMath.DEG_TO_RAD);
+		sr = FastMath.sin(angle);
+		cr = FastMath.cos(angle);
+
+		// matrix = (Z * Y) * X
+		m00 = cp * cy;
+		m10 = cp * sy;
+		m20 = -sp;
+		m01 = sr * sp * cy + cr * -sy;
+		m11 = sr * sp * sy + cr * cy;
+		m21 = sr * cp;
+		m02 = (cr * sp * cy + -sr * -sy);
+		m12 = (cr * sp * sy + -sr * cy);
+		m22 = cr * cp;
+		m03 = 0.0f;
+		m13 = 0.0f;
+		m23 = 0.0f;
+	}
+
+	/**
+	 * <code>setRotationQuaternion</code> builds a rotation from a
+	 * <code>Quaternion</code>.
+	 * 
+	 * @param quat
+	 *            the quaternion to build the rotation from.
+	 * @throws NullPointerException
+	 *             if quat is null.
+	 */
+	public void setRotationQuaternion(Quaternion quat) {
+		quat.toRotationMatrix(this);
+	}
+
+	/**
+	 * <code>setInverseRotationRadians</code> builds an inverted rotation from
+	 * Euler angles that are in radians.
+	 * 
+	 * @param angles
+	 *            the Euler angles in radians.
+	 * @throws Exception
+	 *             if angles is not size 3.
+	 */
+	public void setInverseRotationRadians(float[] angles) throws Exception {
+		if (angles.length != 3) {
+			throw new Exception("Angles must be of size 3.");
+		}
+		double cr = FastMath.cos(angles[0]);
+		double sr = FastMath.sin(angles[0]);
+		double cp = FastMath.cos(angles[1]);
+		double sp = FastMath.sin(angles[1]);
+		double cy = FastMath.cos(angles[2]);
+		double sy = FastMath.sin(angles[2]);
+
+		m00 = (float) (cp * cy);
+		m10 = (float) (cp * sy);
+		m20 = (float) (-sp);
+
+		double srsp = sr * sp;
+		double crsp = cr * sp;
+
+		m01 = (float) (srsp * cy - cr * sy);
+		m11 = (float) (srsp * sy + cr * cy);
+		m21 = (float) (sr * cp);
+
+		m02 = (float) (crsp * cy + sr * sy);
+		m12 = (float) (crsp * sy - sr * cy);
+		m22 = (float) (cr * cp);
+	}
+
+	/**
+	 * <code>setInverseRotationDegrees</code> builds an inverted rotation from
+	 * Euler angles that are in degrees.
+	 * 
+	 * @param angles
+	 *            the Euler angles in degrees.
+	 * @throws Exception
+	 *             if angles is not size 3.
+	 */
+	public void setInverseRotationDegrees(float[] angles) throws Exception {
+		if (angles.length != 3) {
+			throw new Exception("Angles must be of size 3.");
+		}
+		float vec[] = new float[3];
+		vec[0] = (angles[0] * FastMath.RAD_TO_DEG);
+		vec[1] = (angles[1] * FastMath.RAD_TO_DEG);
+		vec[2] = (angles[2] * FastMath.RAD_TO_DEG);
+		setInverseRotationRadians(vec);
+	}
+
+	/**
+	 * 
+	 * <code>inverseTranslateVect</code> translates a given Vector3f by the
+	 * translation part of this matrix.
+	 * 
+	 * @param vec
+	 *            the Vector3f data to be translated.
+	 * @throws Exception
+	 *             if the size of the Vector3f is not 3.
+	 */
+	public void inverseTranslateVect(float[] vec) throws Exception {
+		if (vec.length != 3) {
+			throw new Exception("vec must be of size 3.");
+		}
+
+        vec[0] -= m03;
+        vec[1] -= m13;
+        vec[2] -= m23;
+	}
+
+	/**
+	 * 
+	 * <code>inverseTranslateVect</code> translates a given Vector3f by the
+	 * translation part of this matrix.
+	 * 
+	 * @param data
+	 *            the Vector3f to be translated.
+	 * @throws Exception
+	 *             if the size of the Vector3f is not 3.
+	 */
+	public void inverseTranslateVect(Vector3f data) {
+		data.x -= m03;
+		data.y -= m13;
+		data.z -= m23;
+	}
+
+	/**
+	 * 
+	 * <code>inverseTranslateVect</code> translates a given Vector3f by the
+	 * translation part of this matrix.
+	 * 
+	 * @param data
+	 *            the Vector3f to be translated.
+	 * @throws Exception
+	 *             if the size of the Vector3f is not 3.
+	 */
+	public void translateVect(Vector3f data) {
+		data.x += m03;
+		data.y += m13;
+		data.z += m23;
+	}
+
+	/**
+	 * 
+	 * <code>inverseRotateVect</code> rotates a given Vector3f by the rotation
+	 * part of this matrix.
+	 * 
+	 * @param vec
+	 *            the Vector3f to be rotated.
+	 */
+	public void inverseRotateVect(Vector3f vec) {
+		float vx = vec.x, vy = vec.y, vz = vec.z;
+
+		vec.x = vx * m00 + vy * m10 + vz * m20;
+		vec.y = vx * m01 + vy * m11 + vz * m21;
+		vec.z = vx * m02 + vy * m12 + vz * m22;
+	}
+
+	public void rotateVect(Vector3f vec) {
+		float vx = vec.x, vy = vec.y, vz = vec.z;
+
+		vec.x = vx * m00 + vy * m01 + vz * m02;
+		vec.y = vx * m10 + vy * m11 + vz * m12;
+		vec.z = vx * m20 + vy * m21 + vz * m22;
+	}
+
+	/**
+	 * <code>toString</code> returns the string representation of this object.
+	 * It is in a format of a 4x4 matrix. For example, an identity matrix would
+	 * be represented by the following string. com.jme.math.Matrix3f <br>
+	 * [<br>
+	 * 1.0 0.0 0.0 0.0 <br>
+	 * 0.0 1.0 0.0 0.0 <br>
+	 * 0.0 0.0 1.0 0.0 <br>
+	 * 0.0 0.0 0.0 1.0 <br>
+	 * ]<br>
+	 * 
+	 * @return the string representation of this object.
+	 */
+	@Override
+	public String toString() {
+		StringBuffer result = new StringBuffer("com.jme.math.Matrix4f\n[\n");
+		result.append(' ');
+		result.append(m00);
+		result.append("  ");
+		result.append(m01);
+		result.append("  ");
+		result.append(m02);
+		result.append("  ");
+		result.append(m03);
+		result.append(" \n");
+		result.append(' ');
+		result.append(m10);
+		result.append("  ");
+		result.append(m11);
+		result.append("  ");
+		result.append(m12);
+		result.append("  ");
+		result.append(m13);
+		result.append(" \n");
+		result.append(' ');
+		result.append(m20);
+		result.append("  ");
+		result.append(m21);
+		result.append("  ");
+		result.append(m22);
+		result.append("  ");
+		result.append(m23);
+		result.append(" \n");
+		result.append(' ');
+		result.append(m30);
+		result.append("  ");
+		result.append(m31);
+		result.append("  ");
+		result.append(m32);
+		result.append("  ");
+		result.append(m33);
+		result.append(" \n]");
+		return result.toString();
+	}
+
+	/**
+	 * 
+	 * <code>hashCode</code> returns the hash code value as an integer and is
+	 * supported for the benefit of hashing based collection classes such as
+	 * Hashtable, HashMap, HashSet etc.
+	 * 
+	 * @return the hashcode for this instance of Matrix4f.
+	 * @see java.lang.Object#hashCode()
+	 */
+	@Override
+	public int hashCode() {
+		int hash = 37;
+		hash = 37 * hash + Float.floatToIntBits(m00);
+		hash = 37 * hash + Float.floatToIntBits(m01);
+		hash = 37 * hash + Float.floatToIntBits(m02);
+		hash = 37 * hash + Float.floatToIntBits(m03);
+
+		hash = 37 * hash + Float.floatToIntBits(m10);
+		hash = 37 * hash + Float.floatToIntBits(m11);
+		hash = 37 * hash + Float.floatToIntBits(m12);
+		hash = 37 * hash + Float.floatToIntBits(m13);
+
+		hash = 37 * hash + Float.floatToIntBits(m20);
+		hash = 37 * hash + Float.floatToIntBits(m21);
+		hash = 37 * hash + Float.floatToIntBits(m22);
+		hash = 37 * hash + Float.floatToIntBits(m23);
+
+		hash = 37 * hash + Float.floatToIntBits(m30);
+		hash = 37 * hash + Float.floatToIntBits(m31);
+		hash = 37 * hash + Float.floatToIntBits(m32);
+		hash = 37 * hash + Float.floatToIntBits(m33);
+
+		return hash;
+	}
+
+	/**
+	 * are these two matrices the same? they are is they both have the same mXX
+	 * values.
+	 * 
+	 * @param o
+	 *            the object to compare for equality
+	 * @return true if they are equal
+	 */
+	@Override
+	public boolean equals(Object o) {
+		if (!(o instanceof Matrix4f) || o == null) {
+			return false;
+		}
+
+		if (this == o) {
+			return true;
+		}
+
+		Matrix4f comp = (Matrix4f) o;
+		if (Float.compare(m00, comp.m00) != 0)
+			return false;
+		if (Float.compare(m01, comp.m01) != 0)
+			return false;
+		if (Float.compare(m02, comp.m02) != 0)
+			return false;
+		if (Float.compare(m03, comp.m03) != 0)
+			return false;
+
+		if (Float.compare(m10, comp.m10) != 0)
+			return false;
+		if (Float.compare(m11, comp.m11) != 0)
+			return false;
+		if (Float.compare(m12, comp.m12) != 0)
+			return false;
+		if (Float.compare(m13, comp.m13) != 0)
+			return false;
+
+		if (Float.compare(m20, comp.m20) != 0)
+			return false;
+		if (Float.compare(m21, comp.m21) != 0)
+			return false;
+		if (Float.compare(m22, comp.m22) != 0)
+			return false;
+		if (Float.compare(m23, comp.m23) != 0)
+			return false;
+
+		if (Float.compare(m30, comp.m30) != 0)
+			return false;
+		if (Float.compare(m31, comp.m31) != 0)
+			return false;
+		if (Float.compare(m32, comp.m32) != 0)
+			return false;
+		return Float.compare(m33, comp.m33) == 0;
+	}
+
+	/**
+	 * @return true if this matrix is identity
+	 */
+	public boolean isIdentity() {
+		return (m00 == 1 && m01 == 0 && m02 == 0 && m03 == 0) && (m10 == 0 && m11 == 1 && m12 == 0 && m13 == 0)
+				&& (m20 == 0 && m21 == 0 && m22 == 1 && m23 == 0) && (m30 == 0 && m31 == 0 && m32 == 0 && m33 == 1);
+	}
+
+	/**
+	 * Apply a scale to this matrix.
+	 * 
+	 * @param scale
+	 *            the scale to apply
+	 */
+	public void scale(Vector3f scale) {
+		m00 *= scale.getX();
+		m10 *= scale.getX();
+		m20 *= scale.getX();
+		m30 *= scale.getX();
+		m01 *= scale.getY();
+		m11 *= scale.getY();
+		m21 *= scale.getY();
+		m31 *= scale.getY();
+		m02 *= scale.getZ();
+		m12 *= scale.getZ();
+		m22 *= scale.getZ();
+		m32 *= scale.getZ();
+	}
+
+	static boolean equalIdentity(Matrix4f mat) {
+		if (Math.abs(mat.m00 - 1) > 1e-4)
+			return false;
+		if (Math.abs(mat.m11 - 1) > 1e-4)
+			return false;
+		if (Math.abs(mat.m22 - 1) > 1e-4)
+			return false;
+		if (Math.abs(mat.m33 - 1) > 1e-4)
+			return false;
+
+		if (Math.abs(mat.m01) > 1e-4)
+			return false;
+		if (Math.abs(mat.m02) > 1e-4)
+			return false;
+		if (Math.abs(mat.m03) > 1e-4)
+			return false;
+
+		if (Math.abs(mat.m10) > 1e-4)
+			return false;
+		if (Math.abs(mat.m12) > 1e-4)
+			return false;
+		if (Math.abs(mat.m13) > 1e-4)
+			return false;
+
+		if (Math.abs(mat.m20) > 1e-4)
+			return false;
+		if (Math.abs(mat.m21) > 1e-4)
+			return false;
+		if (Math.abs(mat.m23) > 1e-4)
+			return false;
+
+		if (Math.abs(mat.m30) > 1e-4)
+			return false;
+		if (Math.abs(mat.m31) > 1e-4)
+			return false;
+		return !(Math.abs(mat.m32) > 1e-4);
+	}
+
+	// XXX: This tests more solid than converting the q to a matrix and
+	// multiplying... why?
+	public void multLocal(Quaternion rotation) {
+		Vector3f axis = new Vector3f();
+		float angle = rotation.toAngleAxis(axis);
+		Matrix4f matrix4f = new Matrix4f();
+		matrix4f.fromAngleAxis(angle, axis);
+		multLocal(matrix4f);
+	}
+
+	@Override
+	public Matrix4f clone() {
+		try {
+			return (Matrix4f) super.clone();
+		} catch (CloneNotSupportedException e) {
+			Logger.error( e);
+			throw new AssertionError(); // can not happen
+		}
+	}
+}
diff --git a/src/engine/math/Quaternion.java b/src/engine/math/Quaternion.java
new file mode 100644
index 00000000..d5016a86
--- /dev/null
+++ b/src/engine/math/Quaternion.java
@@ -0,0 +1,1264 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+package engine.math;
+
+import org.pmw.tinylog.Logger;
+
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+
+
+/**
+ * <code>Quaternion</code> defines a single example of a more general class of
+ * hypercomplex numbers. Quaternions extends a rotation in three dimensions to a
+ * rotation in four dimensions. This avoids "gimbal lock" and allows for smooth
+ * continuous rotation.
+ * 
+ * <code>Quaternion</code> is defined by four floating point numbers: {x y z w}.
+ * 
+ */
+
+public class Quaternion {
+
+	public float x, y, z, w;
+	public float angleX, angleY,angleZ;
+
+	/**
+	 * Constructor instantiates a new <code>Quaternion</code> object
+	 * initializing all values to zero, except w which is initialized to 1.
+	 * 
+	 */
+	public Quaternion() {
+		x = 0;
+		y = 0;
+		z = 0;
+		w = 1;
+	}
+
+	/**
+	 * Constructor instantiates a new <code>Quaternion</code> object from the
+	 * given list of parameters.
+	 * 
+	 * @param x
+	 *            the x value of the quaternion.
+	 * @param y
+	 *            the y value of the quaternion.
+	 * @param z
+	 *            the z value of the quaternion.
+	 * @param w
+	 *            the w value of the quaternion.
+	 */
+	public Quaternion(float x, float y, float z, float w) {
+		this.x = x;
+		this.y = y;
+		this.z = z;
+		this.w = w;
+		float[] angles = new float[3];
+		 angles = this.toAngles(angles);
+		 angleX = angles[0];
+		 angleY = angles[1];
+		 angleZ = angles[2];
+	}
+
+	/**
+	 * sets the data in a <code>Quaternion</code> object from the given list of
+	 * parameters.
+	 * 
+	 * @param x
+	 *            the x value of the quaternion.
+	 * @param y
+	 *            the y value of the quaternion.
+	 * @param z
+	 *            the z value of the quaternion.
+	 * @param w
+	 *            the w value of the quaternion.
+	 */
+	public void set(float x, float y, float z, float w) {
+		this.x = x;
+		this.y = y;
+		this.z = z;
+		this.w = w;
+	}
+
+	/**
+	 * Sets the data in this <code>Quaternion</code> object to be equal to the
+	 * passed <code>Quaternion</code> object. The values are copied producing a
+	 * new object.
+	 * 
+	 * @param q
+	 *            The Quaternion to copy values from.
+	 * @return this for chaining
+	 */
+	public Quaternion set(Quaternion q) {
+		this.x = q.x;
+		this.y = q.y;
+		this.z = q.z;
+		this.w = q.w;
+		return this;
+	}
+
+	/**
+	 * Constructor instantiates a new <code>Quaternion</code> object from a
+	 * collection of rotation angles.
+	 * 
+	 * @param angles
+	 *            the angles of rotation (x, y, z) that will define the
+	 *            <code>Quaternion</code>.
+	 */
+	public Quaternion(float[] angles) {
+		fromAngles(angles);
+	}
+
+	/**
+	 * Constructor instantiates a new <code>Quaternion</code> object from an
+	 * interpolation between two other quaternions.
+	 * 
+	 * @param q1
+	 *            the first quaternion.
+	 * @param q2
+	 *            the second quaternion.
+	 * @param interp
+	 *            the amount to interpolate between the two quaternions.
+	 */
+	public Quaternion(Quaternion q1, Quaternion q2, float interp) {
+		slerp(q1, q2, interp);
+	}
+
+	/**
+	 * Constructor instantiates a new <code>Quaternion</code> object from an
+	 * existing quaternion, creating a copy.
+	 * 
+	 * @param q
+	 *            the quaternion to copy.
+	 */
+	public Quaternion(Quaternion q) {
+		this.x = q.x;
+		this.y = q.y;
+		this.z = q.z;
+		this.w = q.w;
+	}
+
+	/**
+	 * Sets this Quaternion to {0, 0, 0, 1}. Same as calling set(0,0,0,1).
+	 */
+	public void loadIdentity() {
+		x = y = z = 0;
+		w = 1;
+	}
+
+	/**
+	 * @return true if this Quaternion is {0,0,0,1}
+	 */
+	public boolean isIdentity() {
+        return x == 0 && y == 0 && z == 0 && w == 1;
+	}
+
+	/**
+	 * <code>fromAngles</code> builds a quaternion from the Euler rotation
+	 * angles (y,r,p).
+	 * 
+	 * @param angles
+	 *            the Euler angles of rotation (in radians).
+	 */
+	public void fromAngles(float[] angles) {
+		if (angles.length != 3)
+			throw new IllegalArgumentException(
+					"Angles array must have three elements");
+
+		fromAngles(angles[0], angles[1], angles[2]);
+	}
+
+	/**
+	 * <code>fromAngles</code> builds a Quaternion from the Euler rotation
+	 * angles (y,r,p). Note that we are applying in order: roll, pitch, yaw but
+	 * we've ordered them in x, y, and z for convenience. See:
+	 * http://www.euclideanspace
+	 * .com/maths/geometry/rotations/conversions/eulerToQuaternion/index.htm
+	 * 
+	 * @param yaw
+	 *            the Euler yaw of rotation (in radians). (aka Bank, often rot
+	 *            around x)
+	 * @param roll
+	 *            the Euler roll of rotation (in radians). (aka Heading, often
+	 *            rot around y)
+	 * @param pitch
+	 *            the Euler pitch of rotation (in radians). (aka Attitude, often
+	 *            rot around z)
+	 */
+	public Quaternion fromAngles(float yaw, float roll, float pitch) {
+		float angle;
+		float sinRoll, sinPitch, sinYaw, cosRoll, cosPitch, cosYaw;
+		angle = pitch * 0.5f;
+		sinPitch = FastMath.sin(angle);
+		cosPitch = FastMath.cos(angle);
+		angle = roll * 0.5f;
+		sinRoll = FastMath.sin(angle);
+		cosRoll = FastMath.cos(angle);
+		angle = yaw * 0.5f;
+		sinYaw = FastMath.sin(angle);
+		cosYaw = FastMath.cos(angle);
+
+		// variables used to reduce multiplication calls.
+		float cosRollXcosPitch = cosRoll * cosPitch;
+		float sinRollXsinPitch = sinRoll * sinPitch;
+		float cosRollXsinPitch = cosRoll * sinPitch;
+		float sinRollXcosPitch = sinRoll * cosPitch;
+
+		w = (cosRollXcosPitch * cosYaw - sinRollXsinPitch * sinYaw);
+		x = (cosRollXcosPitch * sinYaw + sinRollXsinPitch * cosYaw);
+		y = (sinRollXcosPitch * cosYaw + cosRollXsinPitch * sinYaw);
+		z = (cosRollXsinPitch * cosYaw - sinRollXcosPitch * sinYaw);
+
+		normalize();
+		return this;
+	}
+
+	/**
+	 * <code>toAngles</code> returns this quaternion converted to Euler rotation
+	 * angles (yaw,roll,pitch).<br/>
+	 * See http://www.euclideanspace.com/maths/geometry/rotations/conversions/
+	 * quaternionToEuler/index.htm
+	 * 
+	 * @param angles
+	 *            the float[] in which the angles should be stored, or null if
+	 *            you want a new float[] to be created
+	 * @return the float[] in which the angles are stored.
+	 */
+	public float[] toAngles(float[] angles) {
+		if (angles == null)
+			angles = new float[3];
+		else if (angles.length != 3)
+			throw new IllegalArgumentException(
+					"Angles array must have three elements");
+
+		float sqw = w * w;
+		float sqx = x * x;
+		float sqy = y * y;
+		float sqz = z * z;
+		float unit = sqx + sqy + sqz + sqw; // if normalized is one, otherwise
+		// is correction factor
+		float test = x * y + z * w;
+		if (test > 0.499 * unit) { // singularity at north pole
+			angles[1] = 2 * FastMath.atan2(x, w);
+			angles[2] = FastMath.HALF_PI;
+			angles[0] = 0;
+		} else if (test < -0.499 * unit) { // singularity at south pole
+			angles[1] = -2 * FastMath.atan2(x, w);
+			angles[2] = -FastMath.HALF_PI;
+			angles[0] = 0;
+		} else {
+			angles[1] = FastMath.atan2(2 * y * w - 2 * x * z, sqx - sqy - sqz
+					+ sqw); // roll or heading
+			angles[2] = FastMath.asin(2 * test / unit); // pitch or attitude
+			angles[0] = FastMath.atan2(2 * x * w - 2 * y * z, -sqx + sqy - sqz
+					+ sqw); // yaw or bank
+		}
+		return angles;
+	}
+
+	/**
+	 * 
+	 * <code>fromRotationMatrix</code> generates a quaternion from a supplied
+	 * matrix. This matrix is assumed to be a rotational matrix.
+	 * 
+	 * @param matrix
+	 *            the matrix that defines the rotation.
+	 */
+	public Quaternion fromRotationMatrix(Matrix3f matrix) {
+		return fromRotationMatrix(matrix.m00, matrix.m01, matrix.m02,
+				matrix.m10, matrix.m11, matrix.m12, matrix.m20, matrix.m21,
+				matrix.m22);
+	}
+
+	public Quaternion fromRotationMatrix(float m00, float m01, float m02,
+			float m10, float m11, float m12, float m20, float m21, float m22) {
+		// Use the Graphics Gems code, from
+		// ftp://ftp.cis.upenn.edu/pub/graphics/shoemake/quatut.ps.Z
+		// *NOT* the "Matrix and Quaternions FAQ", which has errors!
+
+		// the trace is the sum of the diagonal elements; see
+		// http://mathworld.wolfram.com/MatrixTrace.html
+		float t = m00 + m11 + m22;
+
+		// we protect the division by s by ensuring that s>=1
+		if (t >= 0) { // |w| >= .5
+			float s = FastMath.sqrt(t + 1); // |s|>=1 ...
+			w = 0.5f * s;
+			s = 0.5f / s; // so this division isn't bad
+			x = (m21 - m12) * s;
+			y = (m02 - m20) * s;
+			z = (m10 - m01) * s;
+		} else if ((m00 > m11) && (m00 > m22)) {
+			float s = FastMath.sqrt(1.0f + m00 - m11 - m22); // |s|>=1
+			x = s * 0.5f; // |x| >= .5
+			s = 0.5f / s;
+			y = (m10 + m01) * s;
+			z = (m02 + m20) * s;
+			w = (m21 - m12) * s;
+		} else if (m11 > m22) {
+			float s = FastMath.sqrt(1.0f + m11 - m00 - m22); // |s|>=1
+			y = s * 0.5f; // |y| >= .5
+			s = 0.5f / s;
+			x = (m10 + m01) * s;
+			z = (m21 + m12) * s;
+			w = (m02 - m20) * s;
+		} else {
+			float s = FastMath.sqrt(1.0f + m22 - m00 - m11); // |s|>=1
+			z = s * 0.5f; // |z| >= .5
+			s = 0.5f / s;
+			x = (m02 + m20) * s;
+			y = (m21 + m12) * s;
+			w = (m10 - m01) * s;
+		}
+
+		return this;
+	}
+
+	/**
+	 * <code>toRotationMatrix</code> converts this quaternion to a rotational
+	 * matrix. Note: the result is created from a normalized version of this
+	 * quat.
+	 * 
+	 * @return the rotation matrix representation of this quaternion.
+	 */
+	public Matrix3f toRotationMatrix() {
+		Matrix3f matrix = new Matrix3f();
+		return toRotationMatrix(matrix);
+	}
+
+	/**
+	 * <code>toRotationMatrix</code> converts this quaternion to a rotational
+	 * matrix. The result is stored in result.
+	 * 
+	 * @param result
+	 *            The Matrix3f to store the result in.
+	 * @return the rotation matrix representation of this quaternion.
+	 */
+	public Matrix3f toRotationMatrix(Matrix3f result) {
+
+		float norm = norm();
+		// we explicitly test norm against one here, saving a division
+		// at the cost of a test and branch. Is it worth it?
+		float s = (norm == 1f) ? 2f : (norm > 0f) ? 2f / norm : 0;
+
+		// compute xs/ys/zs first to save 6 multiplications, since xs/ys/zs
+		// will be used 2-4 times each.
+		float xs = x * s;
+		float ys = y * s;
+		float zs = z * s;
+		float xx = x * xs;
+		float xy = x * ys;
+		float xz = x * zs;
+		float xw = w * xs;
+		float yy = y * ys;
+		float yz = y * zs;
+		float yw = w * ys;
+		float zz = z * zs;
+		float zw = w * zs;
+
+		// using s=2/norm (instead of 1/norm) saves 9 multiplications by 2 here
+		result.m00 = 1 - (yy + zz);
+		result.m01 = (xy - zw);
+		result.m02 = (xz + yw);
+		result.m10 = (xy + zw);
+		result.m11 = 1 - (xx + zz);
+		result.m12 = (yz - xw);
+		result.m20 = (xz - yw);
+		result.m21 = (yz + xw);
+		result.m22 = 1 - (xx + yy);
+
+		return result;
+	}
+
+	/**
+	 * <code>toRotationMatrix</code> converts this quaternion to a rotational
+	 * matrix. The result is stored in result. 4th row and 4th column values are
+	 * untouched. Note: the result is created from a normalized version of this
+	 * quat.
+	 * 
+	 * @param result
+	 *            The Matrix4f to store the result in.
+	 * @return the rotation matrix representation of this quaternion.
+	 */
+	public Matrix4f toRotationMatrix(Matrix4f result) {
+
+		float norm = norm();
+		// we explicitly test norm against one here, saving a division
+		// at the cost of a test and branch. Is it worth it?
+		float s = (norm == 1f) ? 2f : (norm > 0f) ? 2f / norm : 0;
+
+		// compute xs/ys/zs first to save 6 multiplications, since xs/ys/zs
+		// will be used 2-4 times each.
+		float xs = x * s;
+		float ys = y * s;
+		float zs = z * s;
+		float xx = x * xs;
+		float xy = x * ys;
+		float xz = x * zs;
+		float xw = w * xs;
+		float yy = y * ys;
+		float yz = y * zs;
+		float yw = w * ys;
+		float zz = z * zs;
+		float zw = w * zs;
+
+		// using s=2/norm (instead of 1/norm) saves 9 multiplications by 2 here
+		result.m00 = 1 - (yy + zz);
+		result.m01 = (xy - zw);
+		result.m02 = (xz + yw);
+		result.m10 = (xy + zw);
+		result.m11 = 1 - (xx + zz);
+		result.m12 = (yz - xw);
+		result.m20 = (xz - yw);
+		result.m21 = (yz + xw);
+		result.m22 = 1 - (xx + yy);
+
+		return result;
+	}
+
+	/**
+	 * <code>getRotationColumn</code> returns one of three columns specified by
+	 * the parameter. This column is returned as a <code>Vector3f</code> object.
+	 * 
+	 * @param i
+	 *            the column to retrieve. Must be between 0 and 2.
+	 * @return the column specified by the index.
+	 * @throws Exception
+	 */
+	public Vector3f getRotationColumn(int i) throws Exception {
+		return getRotationColumn(i, null);
+	}
+
+	/**
+	 * <code>getRotationColumn</code> returns one of three columns specified by
+	 * the parameter. This column is returned as a <code>Vector3f</code> object.
+	 * The value is retrieved as if this quaternion was first normalized.
+	 * 
+	 * @param i
+	 *            the column to retrieve. Must be between 0 and 2.
+	 * @param store
+	 *            the vector object to store the result in. if null, a new one
+	 *            is created.
+	 * @return the column specified by the index.
+	 * @throws Exception
+	 */
+	public Vector3f getRotationColumn(int i, Vector3f store) throws Exception {
+		if (store == null)
+			store = new Vector3f();
+
+		float norm = norm();
+		if (norm != 1.0f) {
+			norm = FastMath.invSqrt(norm);
+		}
+
+		float xx = x * x * norm;
+		float xy = x * y * norm;
+		float xz = x * z * norm;
+		float xw = x * w * norm;
+		float yy = y * y * norm;
+		float yz = y * z * norm;
+		float yw = y * w * norm;
+		float zz = z * z * norm;
+		float zw = z * w * norm;
+
+		switch (i) {
+		case 0:
+			store.x = 1 - 2 * (yy + zz);
+			store.y = 2 * (xy + zw);
+			store.z = 2 * (xz - yw);
+			break;
+		case 1:
+			store.x = 2 * (xy - zw);
+			store.y = 1 - 2 * (xx + zz);
+			store.z = 2 * (yz + xw);
+			break;
+		case 2:
+			store.x = 2 * (xz + yw);
+			store.y = 2 * (yz - xw);
+			store.z = 1 - 2 * (xx + yy);
+			break;
+		default:
+			throw new Exception("Invalid column index. " + i);
+		}
+
+		return store;
+	}
+
+	/**
+	 * <code>fromAngleAxis</code> sets this quaternion to the values specified
+	 * by an angle and an axis of rotation. This method creates an object, so
+	 * use fromAngleNormalAxis if your axis is already normalized.
+	 * 
+	 * @param angle
+	 *            the angle to rotate (in radians).
+	 * @param axis
+	 *            the axis of rotation.
+	 * @return this quaternion
+	 */
+	public Quaternion fromAngleAxis(float angle, Vector3f axis) {
+		Vector3f normAxis = axis.normalize();
+		fromAngleNormalAxis(angle, normAxis);
+		return this;
+	}
+
+	/**
+	 * <code>fromAngleNormalAxis</code> sets this quaternion to the values
+	 * specified by an angle and a normalized axis of rotation.
+	 * 
+	 * @param angle
+	 *            the angle to rotate (in radians).
+	 * @param axis
+	 *            the axis of rotation (already normalized).
+	 */
+	public Quaternion fromAngleNormalAxis(float angle, Vector3f axis) {
+		if (axis.x == 0 && axis.y == 0 && axis.z == 0) {
+			loadIdentity();
+		} else {
+			float halfAngle = 0.5f * angle;
+			float sin = FastMath.sin(halfAngle);
+			w = FastMath.cos(halfAngle);
+			x = sin * axis.x;
+			y = sin * axis.y;
+			z = sin * axis.z;
+		}
+		return this;
+	}
+
+	/**
+	 * <code>toAngleAxis</code> sets a given angle and axis to that represented
+	 * by the current quaternion. The values are stored as following: The axis
+	 * is provided as a parameter and built by the method, the angle is returned
+	 * as a float.
+	 * 
+	 * @param axisStore
+	 *            the object we'll store the computed axis in.
+	 * @return the angle of rotation in radians.
+	 */
+	public float toAngleAxis(Vector3f axisStore) {
+		float sqrLength = x * x + y * y + z * z;
+		float angle;
+		if (sqrLength == 0.0f) {
+			angle = 0.0f;
+			if (axisStore != null) {
+				axisStore.x = 1.0f;
+				axisStore.y = 0.0f;
+				axisStore.z = 0.0f;
+			}
+		} else {
+			angle = (2.0f * FastMath.acos(w));
+			if (axisStore != null) {
+				float invLength = (1.0f / FastMath.sqrt(sqrLength));
+				axisStore.x = x * invLength;
+				axisStore.y = y * invLength;
+				axisStore.z = z * invLength;
+			}
+		}
+
+		return angle;
+	}
+
+	/**
+	 * <code>slerp</code> sets this quaternion's value as an interpolation
+	 * between two other quaternions.
+	 * 
+	 * @param q1
+	 *            the first quaternion.
+	 * @param q2
+	 *            the second quaternion.
+	 * @param t
+	 *            the amount to interpolate between the two quaternions.
+	 */
+	public Quaternion slerp(Quaternion q1, Quaternion q2, float t) {
+		// Create a local quaternion to store the interpolated quaternion
+		if (q1.x == q2.x && q1.y == q2.y && q1.z == q2.z && q1.w == q2.w) {
+			this.set(q1);
+			return this;
+		}
+
+		float result = (q1.x * q2.x) + (q1.y * q2.y) + (q1.z * q2.z)
+				+ (q1.w * q2.w);
+
+		if (result < 0.0f) {
+			// Negate the second quaternion and the result of the dot product
+			q2.x = -q2.x;
+			q2.y = -q2.y;
+			q2.z = -q2.z;
+			q2.w = -q2.w;
+			result = -result;
+		}
+
+		// Set the first and second scale for the interpolation
+		float scale0 = 1 - t;
+		float scale1 = t;
+
+		// Check if the angle between the 2 quaternions was big enough to
+		// warrant such calculations
+		if ((1 - result) > 0.1f) {// Get the angle between the 2 quaternions,
+			// and then store the sin() of that angle
+			float theta = FastMath.acos(result);
+			float invSinTheta = 1f / FastMath.sin(theta);
+
+			// Calculate the scale for q1 and q2, according to the angle and
+			// it's sine value
+			scale0 = FastMath.sin((1 - t) * theta) * invSinTheta;
+			scale1 = FastMath.sin((t * theta)) * invSinTheta;
+		}
+
+		// Calculate the x, y, z and w values for the quaternion by using a
+		// special
+		// form of linear interpolation for quaternions.
+		this.x = (scale0 * q1.x) + (scale1 * q2.x);
+		this.y = (scale0 * q1.y) + (scale1 * q2.y);
+		this.z = (scale0 * q1.z) + (scale1 * q2.z);
+		this.w = (scale0 * q1.w) + (scale1 * q2.w);
+
+		// Return the interpolated quaternion
+		return this;
+	}
+
+	/**
+	 * Sets the values of this quaternion to the slerp from itself to q2 by
+	 * changeAmnt
+	 * 
+	 * @param q2
+	 *            Final interpolation value
+	 * @param changeAmnt
+	 *            The amount difference
+	 */
+	public void slerp(Quaternion q2, float changeAmnt) {
+		if (this.x == q2.x && this.y == q2.y && this.z == q2.z
+				&& this.w == q2.w) {
+			return;
+		}
+
+		float result = (this.x * q2.x) + (this.y * q2.y) + (this.z * q2.z)
+				+ (this.w * q2.w);
+
+		if (result < 0.0f) {
+			// Negate the second quaternion and the result of the dot product
+			q2.x = -q2.x;
+			q2.y = -q2.y;
+			q2.z = -q2.z;
+			q2.w = -q2.w;
+			result = -result;
+		}
+
+		// Set the first and second scale for the interpolation
+		float scale0 = 1 - changeAmnt;
+		float scale1 = changeAmnt;
+
+		// Check if the angle between the 2 quaternions was big enough to
+		// warrant such calculations
+		if ((1 - result) > 0.1f) {
+			// Get the angle between the 2 quaternions, and then store the sin()
+			// of that angle
+			float theta = FastMath.acos(result);
+			float invSinTheta = 1f / FastMath.sin(theta);
+
+			// Calculate the scale for q1 and q2, according to the angle and
+			// it's sine value
+			scale0 = FastMath.sin((1 - changeAmnt) * theta) * invSinTheta;
+			scale1 = FastMath.sin((changeAmnt * theta)) * invSinTheta;
+		}
+
+		// Calculate the x, y, z and w values for the quaternion by using a
+		// special
+		// form of linear interpolation for quaternions.
+		this.x = (scale0 * this.x) + (scale1 * q2.x);
+		this.y = (scale0 * this.y) + (scale1 * q2.y);
+		this.z = (scale0 * this.z) + (scale1 * q2.z);
+		this.w = (scale0 * this.w) + (scale1 * q2.w);
+	}
+
+	/**
+	 * <code>add</code> adds the values of this quaternion to those of the
+	 * parameter quaternion. The result is returned as a new quaternion.
+	 * 
+	 * @param q
+	 *            the quaternion to add to this.
+	 * @return the new quaternion.
+	 */
+	public Quaternion add(Quaternion q) {
+		return new Quaternion(x + q.x, y + q.y, z + q.z, w + q.w);
+	}
+
+	/**
+	 * <code>add</code> adds the values of this quaternion to those of the
+	 * parameter quaternion. The result is stored in this Quaternion.
+	 * 
+	 * @param q
+	 *            the quaternion to add to this.
+	 * @return This Quaternion after addition.
+	 */
+	public Quaternion addLocal(Quaternion q) {
+		this.x += q.x;
+		this.y += q.y;
+		this.z += q.z;
+		this.w += q.w;
+		return this;
+	}
+
+	/**
+	 * <code>subtract</code> subtracts the values of the parameter quaternion
+	 * from those of this quaternion. The result is returned as a new
+	 * quaternion.
+	 * 
+	 * @param q
+	 *            the quaternion to subtract from this.
+	 * @return the new quaternion.
+	 */
+	public Quaternion subtract(Quaternion q) {
+		return new Quaternion(x - q.x, y - q.y, z - q.z, w - q.w);
+	}
+
+	/**
+	 * <code>subtract</code> subtracts the values of the parameter quaternion
+	 * from those of this quaternion. The result is stored in this Quaternion.
+	 * 
+	 * @param q
+	 *            the quaternion to subtract from this.
+	 * @return This Quaternion after subtraction.
+	 */
+	public Quaternion subtractLocal(Quaternion q) {
+		this.x -= q.x;
+		this.y -= q.y;
+		this.z -= q.z;
+		this.w -= q.w;
+		return this;
+	}
+
+	/**
+	 * <code>mult</code> multiplies this quaternion by a parameter quaternion.
+	 * The result is returned as a new quaternion. It should be noted that
+	 * quaternion multiplication is not cummulative so q * p != p * q.
+	 * 
+	 * @param q
+	 *            the quaternion to multiply this quaternion by.
+	 * @return the new quaternion.
+	 */
+	public Quaternion mult(Quaternion q) {
+		return mult(q, null);
+	}
+
+	/**
+	 * <code>mult</code> multiplies this quaternion by a parameter quaternion
+	 * (q). 'this' is not modified. It should be noted that quaternion
+	 * multiplication is not cummulative so q * p != p * q.
+	 * 
+	 * It IS safe for q and res to be the same object.
+	 * 
+	 * @param q
+	 *            the quaternion to multiply this quaternion by.
+	 * @param res
+	 *            the quaternion to store the result in (may be null). If
+	 *            non-null, the input values of 'res' will be ignored and
+	 *            replaced.
+	 * @return If specified res is null, then a new Quaternion; otherwise
+	 *         returns the populated 'res'.
+	 */
+	public Quaternion mult(Quaternion q, Quaternion res) {
+		if (res == null)
+			res = new Quaternion();
+		float qw = q.w, qx = q.x, qy = q.y, qz = q.z;
+		res.x = x * qw + y * qz - z * qy + w * qx;
+		res.y = -x * qz + y * qw + z * qx + w * qy;
+		res.z = x * qy - y * qx + z * qw + w * qz;
+		res.w = -x * qx - y * qy - z * qz + w * qw;
+		
+		float[] angles = new float[3];
+		 angles = res.toAngles(angles);
+		 res.angleX = angles[0];
+		 res.angleY = angles[1];
+		 res.angleZ = angles[2];
+		return res;
+	}
+
+	/**
+	 * <code>apply</code> multiplies this quaternion by a parameter matrix
+	 * internally.
+	 * 
+	 * @param matrix
+	 *            the matrix to apply to this quaternion.
+	 */
+	public void apply(Matrix3f matrix) {
+		float oldX = x, oldY = y, oldZ = z, oldW = w;
+		fromRotationMatrix(matrix);
+		float tempX = x, tempY = y, tempZ = z, tempW = w;
+
+		x = oldX * tempW + oldY * tempZ - oldZ * tempY + oldW * tempX;
+		y = -oldX * tempZ + oldY * tempW + oldZ * tempX + oldW * tempY;
+		z = oldX * tempY - oldY * tempX + oldZ * tempW + oldW * tempZ;
+		w = -oldX * tempX - oldY * tempY - oldZ * tempZ + oldW * tempW;
+	}
+
+	/**
+	 * 
+	 * <code>fromAxes</code> creates a <code>Quaternion</code> that represents
+	 * the coordinate system defined by three axes. These axes are assumed to be
+	 * orthogonal and no error checking is applied. Thus, the user must insure
+	 * that the three axes being provided indeed represents a proper right
+	 * handed coordinate system.
+	 * 
+	 * @param axis
+	 *            the array containing the three vectors representing the
+	 *            coordinate system.
+	 */
+	public Quaternion fromAxes(Vector3f[] axis) {
+		if (axis.length != 3)
+			throw new IllegalArgumentException(
+					"Axis array must have three elements");
+		return fromAxes(axis[0], axis[1], axis[2]);
+	}
+
+	/**
+	 * 
+	 * <code>fromAxes</code> creates a <code>Quaternion</code> that represents
+	 * the coordinate system defined by three axes. These axes are assumed to be
+	 * orthogonal and no error checking is applied. Thus, the user must insure
+	 * that the three axes being provided indeed represents a proper right
+	 * handed coordinate system.
+	 * 
+	 * @param xAxis
+	 *            vector representing the x-axis of the coordinate system.
+	 * @param yAxis
+	 *            vector representing the y-axis of the coordinate system.
+	 * @param zAxis
+	 *            vector representing the z-axis of the coordinate system.
+	 */
+	public Quaternion fromAxes(Vector3f xAxis, Vector3f yAxis, Vector3f zAxis) {
+		return fromRotationMatrix(xAxis.x, yAxis.x, zAxis.x, xAxis.y, yAxis.y,
+				zAxis.y, xAxis.z, yAxis.z, zAxis.z);
+	}
+
+	/**
+	 * 
+	 * <code>toAxes</code> takes in an array of three vectors. Each vector
+	 * corresponds to an axis of the coordinate system defined by the quaternion
+	 * rotation.
+	 * 
+	 * @param axis
+	 *            the array of vectors to be filled.
+	 * @throws Exception
+	 */
+	public void toAxes(Vector3f axis[]) throws Exception {
+		Matrix3f tempMat = toRotationMatrix();
+		axis[0] = tempMat.getColumn(0, axis[0]);
+		axis[1] = tempMat.getColumn(1, axis[1]);
+		axis[2] = tempMat.getColumn(2, axis[2]);
+	}
+
+	/**
+	 * <code>mult</code> multiplies this quaternion by a parameter vector. The
+	 * result is returned as a new vector. 'this' is not modified.
+	 * 
+	 * @param v
+	 *            the vector to multiply this quaternion by.
+	 * @return the new vector.
+	 */
+	public Vector3f mult(Vector3f v) {
+		return mult(v, null);
+	}
+
+	/**
+	 * <code>mult</code> multiplies this quaternion by a parameter vector. The
+	 * result is stored in the supplied vector This method is very poorly named,
+	 * since the specified vector is modified and, contrary to the other *Local
+	 * methods in this and other jME classes, <b>'this' remains unchanged</b>.
+	 * 
+	 * @param v
+	 *            the vector which this Quaternion multiplies.
+	 * @return v
+	 */
+	public Vector3f multLocal(Vector3f v) {
+		float tempX, tempY;
+		tempX = w * w * v.x + 2 * y * w * v.z - 2 * z * w * v.y + x * x * v.x
+				+ 2 * y * x * v.y + 2 * z * x * v.z - z * z * v.x - y * y * v.x;
+		tempY = 2 * x * y * v.x + y * y * v.y + 2 * z * y * v.z + 2 * w * z
+				* v.x - z * z * v.y + w * w * v.y - 2 * x * w * v.z - x * x
+				* v.y;
+		v.z = 2 * x * z * v.x + 2 * y * z * v.y + z * z * v.z - 2 * w * y * v.x
+				- y * y * v.z + 2 * w * x * v.y - x * x * v.z + w * w * v.z;
+		v.x = tempX;
+		v.y = tempY;
+		return v;
+	}
+
+	/**
+	 * Multiplies this Quaternion by the supplied quaternion. The result is
+	 * stored in this Quaternion, which is also returned for chaining. Similar
+	 * to this *= q.
+	 * 
+	 * @param q
+	 *            The Quaternion to multiply this one by.
+	 * @return This Quaternion, after multiplication.
+	 */
+	public Quaternion multLocal(Quaternion q) {
+		float x1 = x * q.w + y * q.z - z * q.y + w * q.x;
+		float y1 = -x * q.z + y * q.w + z * q.x + w * q.y;
+		float z1 = x * q.y - y * q.x + z * q.w + w * q.z;
+		w = -x * q.x - y * q.y - z * q.z + w * q.w;
+		x = x1;
+		y = y1;
+		z = z1;
+		return this;
+	}
+
+	/**
+	 * Multiplies this Quaternion by the supplied quaternion. The result is
+	 * stored in this Quaternion, which is also returned for chaining. Similar
+	 * to this *= q.
+	 * 
+	 * @param qx
+	 *            - quat x value
+	 * @param qy
+	 *            - quat y value
+	 * @param qz
+	 *            - quat z value
+	 * @param qw
+	 *            - quat w value
+	 * 
+	 * @return This Quaternion, after multiplication.
+	 */
+	public Quaternion multLocal(float qx, float qy, float qz, float qw) {
+		float x1 = x * qw + y * qz - z * qy + w * qx;
+		float y1 = -x * qz + y * qw + z * qx + w * qy;
+		float z1 = x * qy - y * qx + z * qw + w * qz;
+		w = -x * qx - y * qy - z * qz + w * qw;
+		x = x1;
+		y = y1;
+		z = z1;
+		return this;
+	}
+
+	/**
+	 * <code>mult</code> multiplies this quaternion by a parameter vector. The
+	 * result is returned as a new vector. 'this' is not modified.
+	 * 
+	 * @param v
+	 *            the vector to multiply this quaternion by.
+	 * @param store
+	 *            the vector to store the result in. It IS safe for v and store
+	 *            to be the same object.
+	 * @return the result vector.
+	 */
+	public Vector3f mult(Vector3f v, Vector3f store) {
+		if (store == null)
+			store = new Vector3f();
+		if (v.x == 0 && v.y == 0 && v.z == 0) {
+			store.set(0, 0, 0);
+		} else {
+			float vx = v.x, vy = v.y, vz = v.z;
+			store.x = w * w * vx + 2 * y * w * vz - 2 * z * w * vy + x * x * vx
+					+ 2 * y * x * vy + 2 * z * x * vz - z * z * vx - y * y * vx;
+			store.y = 2 * x * y * vx + y * y * vy + 2 * z * y * vz + 2 * w * z
+					* vx - z * z * vy + w * w * vy - 2 * x * w * vz - x * x
+					* vy;
+			store.z = 2 * x * z * vx + 2 * y * z * vy + z * z * vz - 2 * w * y
+					* vx - y * y * vz + 2 * w * x * vy - x * x * vz + w * w
+					* vz;
+		}
+		return store;
+	}
+
+	/**
+	 * <code>mult</code> multiplies this quaternion by a parameter scalar. The
+	 * result is returned as a new quaternion.
+	 * 
+	 * @param scalar
+	 *            the quaternion to multiply this quaternion by.
+	 * @return the new quaternion.
+	 */
+	public Quaternion mult(float scalar) {
+		return new Quaternion(scalar * x, scalar * y, scalar * z, scalar * w);
+	}
+
+	/**
+	 * <code>mult</code> multiplies this quaternion by a parameter scalar. The
+	 * result is stored locally.
+	 * 
+	 * @param scalar
+	 *            the quaternion to multiply this quaternion by.
+	 * @return this.
+	 */
+	public Quaternion multLocal(float scalar) {
+		w *= scalar;
+		x *= scalar;
+		y *= scalar;
+		z *= scalar;
+		return this;
+	}
+
+	/**
+	 * <code>dot</code> calculates and returns the dot product of this
+	 * quaternion with that of the parameter quaternion.
+	 * 
+	 * @param q
+	 *            the quaternion to calculate the dot product of.
+	 * @return the dot product of this and the parameter quaternion.
+	 */
+	public float dot(Quaternion q) {
+		return w * q.w + x * q.x + y * q.y + z * q.z;
+	}
+
+	/**
+	 * <code>norm</code> returns the norm of this quaternion. This is the dot
+	 * product of this quaternion with itself.
+	 * 
+	 * @return the norm of the quaternion.
+	 */
+	public float norm() {
+		return w * w + x * x + y * y + z * z;
+	}
+
+	/**
+	 * <code>normalize</code> normalizes the current <code>Quaternion</code>
+	 */
+	public void normalize() {
+		float n = FastMath.invSqrt(norm());
+		x *= n;
+		y *= n;
+		z *= n;
+		w *= n;
+	}
+
+	/**
+	 * <code>inverse</code> returns the inverse of this quaternion as a new
+	 * quaternion. If this quaternion does not have an inverse (if its normal is
+	 * 0 or less), then null is returned.
+	 * 
+	 * @return the inverse of this quaternion or null if the inverse does not
+	 *         exist.
+	 */
+	public Quaternion inverse() {
+		float norm = norm();
+		if (norm > 0.0) {
+			float invNorm = 1.0f / norm;
+			return new Quaternion(-x * invNorm, -y * invNorm, -z * invNorm, w
+					* invNorm);
+		}
+		// return an invalid result to flag the error
+		return null;
+	}
+
+	/**
+	 * <code>inverse</code> calculates the inverse of this quaternion and
+	 * returns this quaternion after it is calculated. If this quaternion does
+	 * not have an inverse (if it's norma is 0 or less), nothing happens
+	 * 
+	 * @return the inverse of this quaternion
+	 */
+	public Quaternion inverseLocal() {
+		float norm = norm();
+		if (norm > 0.0) {
+			float invNorm = 1.0f / norm;
+			x *= -invNorm;
+			y *= -invNorm;
+			z *= -invNorm;
+			w *= invNorm;
+		}
+		return this;
+	}
+
+	/**
+	 * <code>negate</code> inverts the values of the quaternion.
+	 * 
+	 */
+	public void negate() {
+		x *= -1;
+		y *= -1;
+		z *= -1;
+		w *= -1;
+	}
+
+	/**
+	 * <code>equals</code> determines if two quaternions are logically equal,
+	 * that is, if the values of (x, y, z, w) are the same for both quaternions.
+	 * 
+	 * @param o
+	 *            the object to compare for equality
+	 * @return true if they are equal, false otherwise.
+	 */
+	@Override
+	public boolean equals(Object o) {
+		if (!(o instanceof Quaternion)) {
+			return false;
+		}
+
+		if (this == o) {
+			return true;
+		}
+
+		Quaternion comp = (Quaternion) o;
+		if (Float.compare(x, comp.x) != 0)
+			return false;
+		if (Float.compare(y, comp.y) != 0)
+			return false;
+		if (Float.compare(z, comp.z) != 0)
+			return false;
+        return Float.compare(w, comp.w) == 0;
+    }
+
+	/**
+	 * 
+	 * <code>hashCode</code> returns the hash code value as an integer and is
+	 * supported for the benefit of hashing based collection classes such as
+	 * Hashtable, HashMap, HashSet etc.
+	 * 
+	 * @return the hashcode for this instance of Quaternion.
+	 * @see java.lang.Object#hashCode()
+	 */
+	@Override
+	public int hashCode() {
+		int hash = 37;
+		hash = 37 * hash + Float.floatToIntBits(x);
+		hash = 37 * hash + Float.floatToIntBits(y);
+		hash = 37 * hash + Float.floatToIntBits(z);
+		hash = 37 * hash + Float.floatToIntBits(w);
+		return hash;
+
+	}
+
+	/**
+	 * <code>readExternal</code> builds a quaternion from an
+	 * <code>ObjectInput</code> object. <br>
+	 * NOTE: Used with serialization. Not to be called manually.
+	 * 
+	 * @param in
+	 *            the ObjectInput value to read from.
+	 * @throws IOException
+	 *             if the ObjectInput value has problems reading a float.
+	 * @see java.io.Externalizable
+	 */
+	public void readExternal(ObjectInput in) throws IOException {
+		x = in.readFloat();
+		y = in.readFloat();
+		z = in.readFloat();
+		w = in.readFloat();
+	}
+
+	/**
+	 * <code>writeExternal</code> writes this quaternion out to a
+	 * <code>ObjectOutput</code> object. NOTE: Used with serialization. Not to
+	 * be called manually.
+	 * 
+	 * @param out
+	 *            the object to write to.
+	 * @throws IOException
+	 *             if writing to the ObjectOutput fails.
+	 * @see java.io.Externalizable
+	 */
+	public void writeExternal(ObjectOutput out) throws IOException {
+		out.writeFloat(x);
+		out.writeFloat(y);
+		out.writeFloat(z);
+		out.writeFloat(w);
+	}
+
+	private static final Vector3f tmpYaxis = new Vector3f();
+	private static final Vector3f tmpZaxis = new Vector3f();
+	private static final Vector3f tmpXaxis = new Vector3f();
+
+	/**
+	 * <code>lookAt</code> is a convienence method for auto-setting the
+	 * quaternion based on a direction and an up vector. It computes the
+	 * rotation to transform the z-axis to point into 'direction' and the y-axis
+	 * to 'up'.
+	 * 
+	 * @param direction
+	 *            where to look at in terms of local coordinates
+	 * @param up
+	 *            a vector indicating the local up direction. (typically {0, 1,
+	 *            0} in jME.)
+	 */
+	public void lookAt(Vector3f direction, Vector3f up) {
+		tmpZaxis.set(direction).normalizeLocal();
+		tmpXaxis.set(up).crossLocal(direction).normalizeLocal();
+		tmpYaxis.set(direction).crossLocal(tmpXaxis).normalizeLocal();
+		fromAxes(tmpXaxis, tmpYaxis, tmpZaxis);
+	}
+
+	/**
+	 * @return A new quaternion that describes a rotation that would point you
+	 *         in the exact opposite direction of this Quaternion.
+	 */
+	public Quaternion opposite() {
+		return opposite(null);
+	}
+
+	/**
+	 * @param store
+	 *            A Quaternion to store our result in. If null, a new one is
+	 *            created.
+	 * @return The store quaternion (or a new Quaterion, if store is null) that
+	 *         describes a rotation that would point you in the exact opposite
+	 *         direction of this Quaternion.
+	 */
+	public Quaternion opposite(Quaternion store) {
+		if (store == null)
+			store = new Quaternion();
+
+		Vector3f axis = new Vector3f();
+		float angle = toAngleAxis(axis);
+
+		store.fromAngleAxis(FastMath.PI + angle, axis);
+		return store;
+	}
+
+	/**
+	 * @return This Quaternion, altered to describe a rotation that would point
+	 *         you in the exact opposite direction of where it is pointing
+	 *         currently.
+	 */
+	public Quaternion oppositeLocal() {
+		return opposite(this);
+	}
+
+	@Override
+	public Quaternion clone() {
+		try {
+			return (Quaternion) super.clone();
+		} catch (CloneNotSupportedException e) {
+			Logger.error( e);
+			throw new AssertionError(); // can not happen
+		}
+	}
+
+	public float getX() {
+		return x;
+	}
+
+	public void setX(float x) {
+		this.x = x;
+	}
+
+	public float getY() {
+		return y;
+	}
+
+	public void setY(float y) {
+		this.y = y;
+	}
+
+	public float getZ() {
+		return z;
+	}
+
+	public void setZ(float z) {
+		this.z = z;
+	}
+
+	public float getW() {
+		return w;
+	}
+
+	public void setW(float w) {
+		this.w = w;
+	}
+}
diff --git a/src/engine/math/Vector2f.java b/src/engine/math/Vector2f.java
new file mode 100644
index 00000000..06281efc
--- /dev/null
+++ b/src/engine/math/Vector2f.java
@@ -0,0 +1,662 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+package engine.math;
+
+/**
+ * <code>Vector2f</code> defines a Vector for a two float value vector.
+ * 
+ */
+public class Vector2f {
+
+	/**
+	 * the x value of the vector.
+	 */
+	public float x;
+	/**
+	 * the y value of the vector.
+	 */
+	public float y;
+
+	/**
+	 * Creates a Vector2f with the given initial x and y values.
+	 * 
+	 * @param x
+	 *            The x value of this Vector2f.
+	 * @param y
+	 *            The y value of this Vector2f.
+	 */
+	public Vector2f(float x, float y) {
+		this.x = x;
+		this.y = y;
+	}
+
+	/**
+	 * Creates a Vector2f with x and y set to 0. Equivalent to Vector2f(0,0).
+	 */
+	public Vector2f() {
+		x = y = 0;
+	}
+
+	/**
+	 * Creates a new Vector2f that contains the passed vector's information
+	 * 
+	 * @param vector2f
+	 *            The vector to copy
+	 */
+	public Vector2f(Vector2f vector2f) {
+		this.x = vector2f.x;
+		this.y = vector2f.y;
+	}
+
+	/**
+	 * set the x and y values of the vector
+	 * 
+	 * @param x
+	 *            the x value of the vector.
+	 * @param y
+	 *            the y value of the vector.
+	 * @return this vector
+	 */
+	public Vector2f set(float x, float y) {
+		this.x = x;
+		this.y = y;
+		return this;
+	}
+
+	/**
+	 * set the x and y values of the vector from another vector
+	 * 
+	 * @param vec
+	 *            the vector to copy from
+	 * @return this vector
+	 */
+	public Vector2f set(Vector2f vec) {
+		this.x = vec.x;
+		this.y = vec.y;
+		return this;
+	}
+
+	/**
+	 * <code>add</code> adds a provided vector to this vector creating a
+	 * resultant vector which is returned. If the provided vector is null, null
+	 * is returned.
+	 * 
+	 * @param vec
+	 *            the vector to add to this.
+	 * @return the resultant vector.
+	 */
+	public Vector2f add(Vector2f vec) {
+		if (null == vec) {
+			return null;
+		}
+		return new Vector2f(x + vec.x, y + vec.y);
+	}
+
+	/**
+	 * <code>addLocal</code> adds a provided vector to this vector internally,
+	 * and returns a handle to this vector for easy chaining of calls. If the
+	 * provided vector is null, null is returned.
+	 * 
+	 * @param vec
+	 *            the vector to add to this vector.
+	 * @return this
+	 */
+	public Vector2f addLocal(Vector2f vec) {
+		if (null == vec) {
+			return null;
+		}
+		x += vec.x;
+		y += vec.y;
+		return this;
+	}
+
+	/**
+	 * <code>addLocal</code> adds the provided values to this vector internally,
+	 * and returns a handle to this vector for easy chaining of calls.
+	 * 
+	 * @param addX
+	 *            value to add to x
+	 * @param addY
+	 *            value to add to y
+	 * @return this
+	 */
+	public Vector2f addLocal(float addX, float addY) {
+		x += addX;
+		y += addY;
+		return this;
+	}
+
+	/**
+	 * <code>add</code> adds this vector by <code>vec</code> and stores the
+	 * result in <code>result</code>.
+	 * 
+	 * @param vec
+	 *            The vector to add.
+	 * @param result
+	 *            The vector to store the result in.
+	 * @return The result vector, after adding.
+	 */
+	public Vector2f add(Vector2f vec, Vector2f result) {
+		if (null == vec) {
+			return null;
+		}
+		if (result == null)
+			result = new Vector2f();
+		result.x = x + vec.x;
+		result.y = y + vec.y;
+		return result;
+	}
+
+	/**
+	 * <code>dot</code> calculates the dot product of this vector with a
+	 * provided vector. If the provided vector is null, 0 is returned.
+	 * 
+	 * @param vec
+	 *            the vector to dot with this vector.
+	 * @return the resultant dot product of this vector and a given vector.
+	 */
+	public float dot(Vector2f vec) {
+		if (null == vec) {
+			return 0;
+		}
+		return x * vec.x + y * vec.y;
+	}
+
+	/**
+	 * <code>cross</code> calculates the cross product of this vector with a
+	 * parameter vector v.
+	 * 
+	 * @param v
+	 *            the vector to take the cross product of with this.
+	 * @return the cross product vector.
+	 */
+	public Vector3f cross(Vector2f v) {
+		return new Vector3f(0, 0, determinant(v));
+	}
+
+	public float determinant(Vector2f v) {
+		return (x * v.y) - (y * v.x);
+	}
+
+	/**
+	 * Sets this vector to the interpolation by changeAmnt from this to the
+	 * finalVec this=(1-changeAmnt)*this + changeAmnt * finalVec
+	 * 
+	 * @param finalVec
+	 *            The final vector to interpolate towards
+	 * @param changeAmnt
+	 *            An amount between 0.0 - 1.0 representing a percentage change
+	 *            from this towards finalVec
+	 */
+	public void interpolate(Vector2f finalVec, float changeAmnt) {
+		this.x = (1 - changeAmnt) * this.x + changeAmnt * finalVec.x;
+		this.y = (1 - changeAmnt) * this.y + changeAmnt * finalVec.y;
+	}
+
+	/**
+	 * Sets this vector to the interpolation by changeAmnt from beginVec to
+	 * finalVec this=(1-changeAmnt)*beginVec + changeAmnt * finalVec
+	 * 
+	 * @param beginVec
+	 *            The begining vector (delta=0)
+	 * @param finalVec
+	 *            The final vector to interpolate towards (delta=1)
+	 * @param changeAmnt
+	 *            An amount between 0.0 - 1.0 representing a precentage change
+	 *            from beginVec towards finalVec
+	 */
+	public void interpolate(Vector2f beginVec, Vector2f finalVec,
+			float changeAmnt) {
+		this.x = (1 - changeAmnt) * beginVec.x + changeAmnt * finalVec.x;
+		this.y = (1 - changeAmnt) * beginVec.y + changeAmnt * finalVec.y;
+	}
+
+	/**
+	 * Check a vector... if it is null or its floats are NaN or infinite, return
+	 * false. Else return true.
+	 * 
+	 * @param vector
+	 *            the vector to check
+	 * @return true or false as stated above.
+	 */
+	public static boolean isValidVector(Vector2f vector) {
+		if (vector == null)
+			return false;
+		if (Float.isNaN(vector.x) || Float.isNaN(vector.y))
+			return false;
+        return !Float.isInfinite(vector.x) && !Float.isInfinite(vector.y);
+    }
+
+    public static boolean isZeroVector(Vector2f vector) {
+
+        return (vector.x == 0) &&
+                (vector.y == 0);
+
+    }
+	/**
+	 * <code>length</code> calculates the magnitude of this vector.
+	 * 
+	 * @return the length or magnitude of the vector.
+	 */
+	public float length() {
+		return FastMath.sqrt(lengthSquared());
+	}
+
+	/**
+	 * <code>lengthSquared</code> calculates the squared value of the magnitude
+	 * of the vector.
+	 * 
+	 * @return the magnitude squared of the vector.
+	 */
+	public float lengthSquared() {
+		return x * x + y * y;
+	}
+
+	/**
+	 * <code>distanceSquared</code> calculates the distance squared between this
+	 * vector and vector v.
+	 * 
+	 * @param v
+	 *            the second vector to determine the distance squared.
+	 * @return the distance squared between the two vectors.
+	 */
+	public float distanceSquared(Vector2f v) {
+		double dx = x - v.x;
+		double dy = y - v.y;
+		return (float) (dx * dx + dy * dy);
+	}
+
+	/**
+	 * <code>distanceSquared</code> calculates the distance squared between this
+	 * vector and vector v.
+	 * 
+	 * @return the distance squared between the two vectors.
+	 */
+	public float distanceSquared(float otherX, float otherY) {
+		double dx = x - otherX;
+		double dy = y - otherY;
+		return (float) (dx * dx + dy * dy);
+	}
+
+	/**
+	 * <code>distance</code> calculates the distance between this vector and
+	 * vector v.
+	 * 
+	 * @param v
+	 *            the second vector to determine the distance.
+	 * @return the distance between the two vectors.
+	 */
+	public float distance(Vector2f v) {
+		return FastMath.sqrt(distanceSquared(v));
+	}
+
+	/**
+	 * <code>mult</code> multiplies this vector by a scalar. The resultant
+	 * vector is returned.
+	 * 
+	 * @param scalar
+	 *            the value to multiply this vector by.
+	 * @return the new vector.
+	 */
+	public Vector2f mult(float scalar) {
+		return new Vector2f(x * scalar, y * scalar);
+	}
+
+	/**
+	 * <code>multLocal</code> multiplies this vector by a scalar internally, and
+	 * returns a handle to this vector for easy chaining of calls.
+	 * 
+	 * @param scalar
+	 *            the value to multiply this vector by.
+	 * @return this
+	 */
+	public Vector2f multLocal(float scalar) {
+		x *= scalar;
+		y *= scalar;
+		return this;
+	}
+
+	/**
+	 * <code>multLocal</code> multiplies a provided vector to this vector
+	 * internally, and returns a handle to this vector for easy chaining of
+	 * calls. If the provided vector is null, null is returned.
+	 * 
+	 * @param vec
+	 *            the vector to mult to this vector.
+	 * @return this
+	 */
+	public Vector2f multLocal(Vector2f vec) {
+		if (null == vec) {
+			return null;
+		}
+		x *= vec.x;
+		y *= vec.y;
+		return this;
+	}
+
+	/**
+	 * Multiplies this Vector2f's x and y by the scalar and stores the result in
+	 * product. The result is returned for chaining. Similar to
+	 * product=this*scalar;
+	 * 
+	 * @param scalar
+	 *            The scalar to multiply by.
+	 * @param product
+	 *            The vector2f to store the result in.
+	 * @return product, after multiplication.
+	 */
+	public Vector2f mult(float scalar, Vector2f product) {
+		if (null == product) {
+			product = new Vector2f();
+		}
+
+		product.x = x * scalar;
+		product.y = y * scalar;
+		return product;
+	}
+
+	/**
+	 * <code>divide</code> divides the values of this vector by a scalar and
+	 * returns the result. The values of this vector remain untouched.
+	 * 
+	 * @param scalar
+	 *            the value to divide this vectors attributes by.
+	 * @return the result <code>Vector</code>.
+	 */
+	public Vector2f divide(float scalar) {
+		return new Vector2f(x / scalar, y / scalar);
+	}
+
+	/**
+	 * <code>divideLocal</code> divides this vector by a scalar internally, and
+	 * returns a handle to this vector for easy chaining of calls. Dividing by
+	 * zero will result in an exception.
+	 * 
+	 * @param scalar
+	 *            the value to divides this vector by.
+	 * @return this
+	 */
+	public Vector2f divideLocal(float scalar) {
+		x /= scalar;
+		y /= scalar;
+		return this;
+	}
+
+	/**
+	 * <code>negate</code> returns the negative of this vector. All values are
+	 * negated and set to a new vector.
+	 * 
+	 * @return the negated vector.
+	 */
+	public Vector2f negate() {
+		return new Vector2f(-x, -y);
+	}
+
+	/**
+	 * <code>negateLocal</code> negates the internal values of this vector.
+	 * 
+	 * @return this.
+	 */
+	public Vector2f negateLocal() {
+		x = -x;
+		y = -y;
+		return this;
+	}
+
+	/**
+	 * <code>subtract</code> subtracts the values of a given vector from those
+	 * of this vector creating a new vector object. If the provided vector is
+	 * null, an exception is thrown.
+	 * 
+	 * @param vec
+	 *            the vector to subtract from this vector.
+	 * @return the result vector.
+	 */
+	public Vector2f subtract(Vector2f vec) {
+		return subtract(vec, null);
+	}
+
+	/**
+	 * <code>subtract</code> subtracts the values of a given vector from those
+	 * of this vector storing the result in the given vector object. If the
+	 * provided vector is null, an exception is thrown.
+	 * 
+	 * @param vec
+	 *            the vector to subtract from this vector.
+	 * @param store
+	 *            the vector to store the result in. It is safe for this to be
+	 *            the same as vec. If null, a new vector is created.
+	 * @return the result vector.
+	 */
+	public Vector2f subtract(Vector2f vec, Vector2f store) {
+		if (store == null)
+			store = new Vector2f();
+		store.x = x - vec.x;
+		store.y = y - vec.y;
+		return store;
+	}
+
+	/**
+	 * <code>subtract</code> subtracts the given x,y values from those of this
+	 * vector creating a new vector object.
+	 * 
+	 * @param valX
+	 *            value to subtract from x
+	 * @param valY
+	 *            value to subtract from y
+	 * @return this
+	 */
+	public Vector2f subtract(float valX, float valY) {
+		return new Vector2f(x - valX, y - valY);
+	}
+
+	/**
+	 * <code>subtractLocal</code> subtracts a provided vector to this vector
+	 * internally, and returns a handle to this vector for easy chaining of
+	 * calls. If the provided vector is null, null is returned.
+	 * 
+	 * @param vec
+	 *            the vector to subtract
+	 * @return this
+	 */
+	public Vector2f subtractLocal(Vector2f vec) {
+		if (null == vec) {
+			return null;
+		}
+		x -= vec.x;
+		y -= vec.y;
+		return this;
+	}
+
+	/**
+	 * <code>subtractLocal</code> subtracts the provided values from this vector
+	 * internally, and returns a handle to this vector for easy chaining of
+	 * calls.
+	 * 
+	 * @param valX
+	 *            value to subtract from x
+	 * @param valY
+	 *            value to subtract from y
+	 * @return this
+	 */
+	public Vector2f subtractLocal(float valX, float valY) {
+		x -= valX;
+		y -= valY;
+		return this;
+	}
+
+	/**
+	 * <code>normalize</code> returns the unit vector of this vector.
+	 * 
+	 * @return unit vector of this vector.
+	 */
+	public Vector2f normalize() {
+		float length = length();
+		if (length != 0) {
+			return divide(length);
+		}
+
+		return divide(1);
+	}
+
+	/**
+	 * <code>normalizeLocal</code> makes this vector into a unit vector of
+	 * itself.
+	 * 
+	 * @return this.
+	 */
+	public Vector2f normalizeLocal() {
+		float length = length();
+		if (length != 0) {
+			return divideLocal(length);
+		}
+
+		return divideLocal(1);
+	}
+
+	/**
+	 * <code>smallestAngleBetween</code> returns (in radians) the minimum angle
+	 * between two vectors. It is assumed that both this vector and the given
+	 * vector are unit vectors (iow, normalized).
+	 * 
+	 * @param otherVector
+	 *            a unit vector to find the angle against
+	 * @return the angle in radians.
+	 */
+	public float smallestAngleBetween(Vector2f otherVector) {
+		float dotProduct = dot(otherVector);
+        return FastMath.acos(dotProduct);
+	}
+
+	/**
+	 * <code>angleBetween</code> returns (in radians) the angle required to
+	 * rotate a ray represented by this vector to lie colinear to a ray
+	 * described by the given vector. It is assumed that both this vector and
+	 * the given vector are unit vectors (iow, normalized).
+	 * 
+	 * @param otherVector
+	 *            the "destination" unit vector
+	 * @return the angle in radians.
+	 */
+	public float angleBetween(Vector2f otherVector) {
+        return FastMath.atan2(otherVector.y, otherVector.x)
+                - FastMath.atan2(y, x);
+	}
+
+	public float getX() {
+		return x;
+	}
+
+	public void setX(float x) {
+		this.x = x;
+	}
+
+	public float getY() {
+		return y;
+	}
+
+	public void setY(float y) {
+		this.y = y;
+	}
+
+	/**
+	 * <code>getAngle</code> returns (in radians) the angle represented by this
+	 * Vector2f as expressed by a conversion from rectangular coordinates (
+	 * <code>x</code>,&nbsp;<code>y</code>) to polar coordinates
+	 * (r,&nbsp;<i>theta</i>).
+	 * 
+	 * @return the angle in radians. [-pi, pi)
+	 */
+	public float getAngle() {
+		return -FastMath.atan2(y, x);
+	}
+
+	/**
+	 * <code>zero</code> resets this vector's data to zero internally.
+	 */
+	public void zero() {
+		x = y = 0;
+	}
+
+	@Override
+	public Vector2f clone() {
+		try {
+			return (Vector2f) super.clone();
+		} catch (CloneNotSupportedException e) {
+			throw new AssertionError(); // can not happen
+		}
+	}
+
+	/**
+	 * Saves this Vector2f into the given float[] object.
+	 * 
+	 * @param floats
+	 *            The float[] to take this Vector2f. If null, a new float[2] is
+	 *            created.
+	 * @return The array, with X, Y float values in that order
+	 */
+	public float[] toArray(float[] floats) {
+		if (floats == null) {
+			floats = new float[2];
+		}
+		floats[0] = x;
+		floats[1] = y;
+		return floats;
+	}
+
+	/**
+	 * are these two vectors the same? they are is they both have the same x and
+	 * y values.
+	 * 
+	 * @param o
+	 *            the object to compare for equality
+	 * @return true if they are equal
+	 */
+	@Override
+	public boolean equals(Object o) {
+		if (!(o instanceof Vector2f)) {
+			return false;
+		}
+
+		if (this == o) {
+			return true;
+		}
+
+		Vector2f comp = (Vector2f) o;
+		if (Float.compare(x, comp.x) != 0)
+			return false;
+        return Float.compare(y, comp.y) == 0;
+    }
+
+	public void rotateAroundOrigin(float angle, boolean cw) {
+		if (cw)
+			angle = -angle;
+		float newX = FastMath.cos(angle) * x - FastMath.sin(angle) * y;
+		float newY = FastMath.sin(angle) * x + FastMath.cos(angle) * y;
+		x = newX;
+		y = newY;
+	}
+
+	public synchronized float getLat() {
+		return x;
+	}
+
+	public synchronized float getLong() {
+		return y;
+	}
+
+	public synchronized void setLat(float lat) {
+		this.x = lat;
+	}
+
+	public synchronized void setLong(float lon) {
+		this.y = lon;
+	}
+
+}
diff --git a/src/engine/math/Vector3f.java b/src/engine/math/Vector3f.java
new file mode 100644
index 00000000..06d95d19
--- /dev/null
+++ b/src/engine/math/Vector3f.java
@@ -0,0 +1,1188 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+package engine.math;
+
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+
+
+/**
+ * <code>Vector3f</code> defines a Vector for a three float value tuple.
+ * <code>Vector3f</code> can represent any three dimensional value, such as a
+ * vertex, a normal, etc. Utility methods are also included to aid in
+ * mathematical calculations.
+ *
+ */
+
+public class Vector3f {
+	public final static Vector3f ZERO = new Vector3f(0, 0, 0);
+
+	public final static Vector3f UNIT_X = new Vector3f(1, 0, 0);
+	public final static Vector3f UNIT_Y = new Vector3f(0, 1, 0);
+	public final static Vector3f UNIT_Z = new Vector3f(0, 0, 1);
+	public final static Vector3f UNIT_XYZ = new Vector3f(1, 1, 1);
+
+	/**
+	 * the x value of the vector.
+	 */
+	public float x;
+
+	/**
+	 * the y value of the vector.
+	 */
+	public float y;
+
+	/**
+	 * the z value of the vector.
+	 */
+	public float z;
+
+	/**
+	 * Constructor instantiates a new <code>Vector3f</code> with default values
+	 * of (0,0,0).
+	 *
+	 */
+	public Vector3f() {
+		x = y = z = 0.0f;
+	}
+
+	/**
+	 * Constructor instantiates a new <code>Vector3f</code> with provides
+	 * values.
+	 *
+	 * @param x
+	 *            the x value of the vector.
+	 * @param y
+	 *            the y value of the vector.
+	 * @param z
+	 *            the z value of the vector.
+	 */
+	public Vector3f(float x, float y, float z) {
+		this.x = x;
+		this.y = y;
+		this.z = z;
+	}
+
+	public Vector3f(Vector3fImmutable original) {
+		this.x = original.x;
+		this.y = original.y;
+		this.z = original.z;
+	}
+
+	/**
+	 * Constructor instantiates a new <code>Vector3f</code> that is a copy of
+	 * the provided vector
+	 *
+	 * @param copy
+	 *            The Vector3f to copy
+	 */
+	public Vector3f(Vector3f copy) {
+		this.set(copy);
+	}
+
+	/**
+	 * <code>set</code> sets the x,y,z values of the vector based on passed
+	 * parameters.
+	 *
+	 * @param x
+	 *            the x value of the vector.
+	 * @param y
+	 *            the y value of the vector.
+	 * @param z
+	 *            the z value of the vector.
+	 * @return this vector
+	 */
+	public Vector3f set(float x, float y, float z) {
+		this.x = x;
+		this.y = y;
+		this.z = z;
+		return this;
+	}
+
+	/**
+	 * <code>set</code> sets the x,y,z values of the vector by copying the
+	 * supplied vector.
+	 *
+	 * @param vect
+	 *            the vector to copy.
+	 * @return this vector
+	 */
+	public Vector3f set(Vector3f vect) {
+		this.x = vect.x;
+		this.y = vect.y;
+		this.z = vect.z;
+		return this;
+	}
+
+	/**
+	 *
+	 * <code>add</code> adds a provided vector to this vector creating a
+	 * resultant vector which is returned. If the provided vector is null, null
+	 * is returned.
+	 *
+	 * Neither 'this' nor 'vec' are modified.
+	 *
+	 * @param vec
+	 *            the vector to add to this.
+	 * @return the resultant vector.
+	 */
+	public Vector3f add(Vector3f vec) {
+		if (null == vec) {
+			return null;
+		}
+		return new Vector3f(x + vec.x, y + vec.y, z + vec.z);
+	}
+
+	/**
+	 *
+	 * <code>add</code> adds the values of a provided vector storing the values
+	 * in the supplied vector.
+	 *
+	 * @param vec
+	 *            the vector to add to this
+	 * @param result
+	 *            the vector to store the result in
+	 * @return result returns the supplied result vector.
+	 */
+	public Vector3f add(Vector3f vec, Vector3f result) {
+		result.x = x + vec.x;
+		result.y = y + vec.y;
+		result.z = z + vec.z;
+		return result;
+	}
+
+	/**
+	 * <code>addLocal</code> adds a provided vector to this vector internally,
+	 * and returns a handle to this vector for easy chaining of calls. If the
+	 * provided vector is null, null is returned.
+	 *
+	 * @param vec
+	 *            the vector to add to this vector.
+	 * @return this
+	 */
+	public Vector3f addLocal(Vector3f vec) {
+		if (null == vec) {
+			return null;
+		}
+		x += vec.x;
+		y += vec.y;
+		z += vec.z;
+		return this;
+	}
+
+	/**
+	 *
+	 * <code>add</code> adds the provided values to this vector, creating a new
+	 * vector that is then returned.
+	 *
+	 * @param addX
+	 *            the x value to add.
+	 * @param addY
+	 *            the y value to add.
+	 * @param addZ
+	 *            the z value to add.
+	 * @return the result vector.
+	 */
+	public Vector3f add(float addX, float addY, float addZ) {
+		return new Vector3f(x + addX, y + addY, z + addZ);
+	}
+
+	/**
+	 * <code>addLocal</code> adds the provided values to this vector internally,
+	 * and returns a handle to this vector for easy chaining of calls.
+	 *
+	 * @param addX
+	 *            value to add to x
+	 * @param addY
+	 *            value to add to y
+	 * @param addZ
+	 *            value to add to z
+	 * @return this
+	 */
+	public Vector3f addLocal(float addX, float addY, float addZ) {
+		x += addX;
+		y += addY;
+		z += addZ;
+		return this;
+	}
+
+	/**
+	 *
+	 * <code>scaleAdd</code> multiplies this vector by a scalar then adds the
+	 * given Vector3f.
+	 *
+	 * @param scalar
+	 *            the value to multiply this vector by.
+	 * @param add
+	 *            the value to add
+	 */
+	public void scaleAdd(float scalar, Vector3f add) {
+		x = x * scalar + add.x;
+		y = y * scalar + add.y;
+		z = z * scalar + add.z;
+	}
+
+	/**
+	 *
+	 * <code>scaleAdd</code> multiplies the given vector by a scalar then adds
+	 * the given vector.
+	 *
+	 * @param scalar
+	 *            the value to multiply this vector by.
+	 * @param mult
+	 *            the value to multiply the scalar by
+	 * @param add
+	 *            the value to add
+	 */
+	public void scaleAdd(float scalar, Vector3f mult, Vector3f add) {
+		this.x = mult.x * scalar + add.x;
+		this.y = mult.y * scalar + add.y;
+		this.z = mult.z * scalar + add.z;
+	}
+
+	/**
+	 *
+	 * <code>dot</code> calculates the dot product of this vector with a
+	 * provided vector. If the provided vector is null, 0 is returned.
+	 *
+	 * @param vec
+	 *            the vector to dot with this vector.
+	 * @return the resultant dot product of this vector and a given vector.
+	 */
+	public float dot(Vector3f vec) {
+		if (null == vec) {
+			return 0;
+		}
+		return x * vec.x + y * vec.y + z * vec.z;
+	}
+
+	/**
+	 * Returns a new vector which is the cross product of this vector with the
+	 * specified vector.
+	 * <P>
+	 * Neither 'this' nor v are modified. The starting value of 'result'
+	 * </P>
+	 *
+	 * @param v
+	 *            the vector to take the cross product of with this.
+	 * @return the cross product vector.
+	 */
+	public Vector3f cross(Vector3f v) {
+		return cross(v, null);
+	}
+
+	/**
+	 * <code>cross</code> calculates the cross product of this vector with a
+	 * parameter vector v. The result is stored in <code>result</code>
+	 * <P>
+	 * Neither 'this' nor v are modified. The starting value of 'result' (if
+	 * any) is ignored.
+	 * </P>
+	 *
+	 * @param v
+	 *            the vector to take the cross product of with this.
+	 * @param result
+	 *            the vector to store the cross product result.
+	 * @return result, after receiving the cross product vector.
+	 */
+	public Vector3f cross(Vector3f v, Vector3f result) {
+		return cross(v.x, v.y, v.z, result);
+	}
+
+	/**
+	 * <code>cross</code> calculates the cross product of this vector with a
+	 * Vector comprised of the specified other* elements. The result is stored
+	 * in <code>result</code>, without modifying either 'this' or the 'other*'
+	 * values.
+	 *
+	 * @param otherX
+	 *            x component of the vector to take the cross product of with
+	 *            this.
+	 * @param otherY
+	 *            y component of the vector to take the cross product of with
+	 *            this.
+	 * @param otherZ
+	 *            z component of the vector to take the cross product of with
+	 *            this.
+	 * @param result
+	 *            the vector to store the cross product result.
+	 * @return result, after receiving the cross product vector.
+	 */
+	public Vector3f cross(float otherX, float otherY, float otherZ, Vector3f result) {
+		if (result == null)
+			result = new Vector3f();
+		float resX = ((y * otherZ) - (z * otherY));
+		float resY = ((z * otherX) - (x * otherZ));
+		float resZ = ((x * otherY) - (y * otherX));
+		result.set(resX, resY, resZ);
+		return result;
+	}
+
+	/**
+	 * <code>crossLocal</code> calculates the cross product of this vector with
+	 * a parameter vector v.
+	 *
+	 * @param v
+	 *            the vector to take the cross product of with this.
+	 * @return this.
+	 */
+	public Vector3f crossLocal(Vector3f v) {
+		return crossLocal(v.x, v.y, v.z);
+	}
+
+	/**
+	 * <code>crossLocal</code> calculates the cross product of this vector with
+	 * a parameter vector v.
+	 *
+	 * @param otherX
+	 *            x component of the vector to take the cross product of with
+	 *            this.
+	 * @param otherY
+	 *            y component of the vector to take the cross product of with
+	 *            this.
+	 * @param otherZ
+	 *            z component of the vector to take the cross product of with
+	 *            this.
+	 * @return this.
+	 */
+	public Vector3f crossLocal(float otherX, float otherY, float otherZ) {
+		float tempx = (y * otherZ) - (z * otherY);
+		float tempy = (z * otherX) - (x * otherZ);
+		z = (x * otherY) - (y * otherX);
+		x = tempx;
+		y = tempy;
+		return this;
+	}
+
+	/**
+	 * <code>length</code> calculates the magnitude of this vector.
+	 *
+	 * @return the length or magnitude of the vector.
+	 */
+	public float length() {
+		return FastMath.sqrt(lengthSquared());
+	}
+
+	/**
+	 * <code>lengthSquared</code> calculates the squared value of the magnitude
+	 * of the vector.
+	 *
+	 * @return the magnitude squared of the vector.
+	 */
+	public float lengthSquared() {
+		return x * x + y * y + z * z;
+	}
+
+	/**
+	 * <code>distanceSquared</code> calculates the distance squared between this
+	 * vector and vector v.
+	 *
+	 * @param v
+	 *            the second vector to determine the distance squared.
+	 * @return the distance squared between the two vectors.
+	 */
+	public float distanceSquared(Vector3f v) {
+		double dx = x - v.x;
+		double dy = y - v.y;
+		double dz = z - v.z;
+		return (float) (dx * dx + dy * dy + dz * dz);
+	}
+
+	public float distanceSquared2D(Vector3f v) {
+		double dx = x - v.x;
+		double dz = z - v.z;
+		return (float) (dx * dx + dz * dz);
+	}
+
+	/**
+	 * <code>distance</code> calculates the distance between this vector and
+	 * vector v.
+	 *
+	 * @param v
+	 *            the second vector to determine the distance.
+	 * @return the distance between the two vectors.
+	 */
+	public float distance(Vector3f v) {
+		return FastMath.sqrt(distanceSquared(v));
+	}
+
+	public float distance2D(Vector3f v) {
+		return FastMath.sqrt(distanceSquared2D(v));
+	}
+
+	/**
+	 * <code>mult</code> multiplies this vector by a scalar. The resultant
+	 * vector is returned. "this" is not modified.
+	 *
+	 * @param scalar
+	 *            the value to multiply this vector by.
+	 * @return the new vector.
+	 */
+	public Vector3f mult(float scalar) {
+		return new Vector3f(x * scalar, y * scalar, z * scalar);
+	}
+
+	/**
+	 *
+	 * <code>mult</code> multiplies this vector by a scalar. The resultant
+	 * vector is supplied as the second parameter and returned. "this" is not
+	 * modified.
+	 *
+	 * @param scalar
+	 *            the scalar to multiply this vector by.
+	 * @param product
+	 *            the product to store the result in.
+	 * @return product
+	 */
+	public Vector3f mult(float scalar, Vector3f product) {
+		if (null == product) {
+			product = new Vector3f();
+		}
+
+		product.x = x * scalar;
+		product.y = y * scalar;
+		product.z = z * scalar;
+		return product;
+	}
+
+	/**
+	 * <code>multLocal</code> multiplies this vector by a scalar internally, and
+	 * returns a handle to this vector for easy chaining of calls.
+	 *
+	 * @param scalar
+	 *            the value to multiply this vector by.
+	 * @return this
+	 */
+	public Vector3f multLocal(float scalar) {
+		x *= scalar;
+		y *= scalar;
+		z *= scalar;
+		return this;
+	}
+
+	/**
+	 * <code>multLocal</code> multiplies a provided vector to this vector
+	 * internally, and returns a handle to this vector for easy chaining of
+	 * calls. If the provided vector is null, null is returned. The provided
+	 * 'vec' is not modified.
+	 *
+	 * @param vec
+	 *            the vector to mult to this vector.
+	 * @return this
+	 */
+	public Vector3f multLocal(Vector3f vec) {
+		if (null == vec) {
+			return null;
+		}
+		x *= vec.x;
+		y *= vec.y;
+		z *= vec.z;
+		return this;
+	}
+
+	/**
+	 * Returns a new Vector instance comprised of elements which are the product
+	 * of the corresponding vector elements. (N.b. this is not a cross product).
+	 * <P>
+	 * Neither 'this' nor 'vec' are modified.
+	 * </P>
+	 *
+	 * @param vec
+	 *            the vector to mult to this vector.
+	 */
+	public Vector3f mult(Vector3f vec) {
+		if (null == vec) {
+			return null;
+		}
+		return mult(vec, null);
+	}
+
+	/**
+	 * Multiplies a provided 'vec' vector with this vector. If the specified
+	 * 'store' is null, then a new Vector instance is returned. Otherwise,
+	 * 'store' with replaced values will be returned, to facilitate chaining.
+	 * </P>
+	 * <P>
+	 *'This' is not modified; and the starting value of 'store' (if any) is
+	 * ignored (and over-written).
+	 * <P>
+	 * The resultant Vector is comprised of elements which are the product of
+	 * the corresponding vector elements. (N.b. this is not a cross product).
+	 * </P>
+	 *
+	 * @param vec
+	 *            the vector to mult to this vector.
+	 * @param store
+	 *            result vector (null to create a new vector)
+	 * @return 'store', or a new Vector3f
+	 */
+	public Vector3f mult(Vector3f vec, Vector3f store) {
+		if (null == vec) {
+			return null;
+		}
+		if (store == null)
+			store = new Vector3f();
+		return store.set(x * vec.x, y * vec.y, z * vec.z);
+	}
+
+	/**
+	 * <code>divide</code> divides the values of this vector by a scalar and
+	 * returns the result. The values of this vector remain untouched.
+	 *
+	 * @param scalar
+	 *            the value to divide this vectors attributes by.
+	 * @return the result <code>Vector</code>.
+	 */
+	public Vector3f divide(float scalar) {
+		scalar = 1f / scalar;
+		return new Vector3f(x * scalar, y * scalar, z * scalar);
+	}
+
+	/**
+	 * <code>divideLocal</code> divides this vector by a scalar internally, and
+	 * returns a handle to this vector for easy chaining of calls. Dividing by
+	 * zero will result in an exception.
+	 *
+	 * @param scalar
+	 *            the value to divides this vector by.
+	 * @return this
+	 */
+	public Vector3f divideLocal(float scalar) {
+		scalar = 1f / scalar;
+		x *= scalar;
+		y *= scalar;
+		z *= scalar;
+		return this;
+	}
+
+	/**
+	 * <code>divide</code> divides the values of this vector by a scalar and
+	 * returns the result. The values of this vector remain untouched.
+	 *
+	 * @param scalar
+	 *            the value to divide this vectors attributes by.
+	 * @return the result <code>Vector</code>.
+	 */
+	public Vector3f divide(Vector3f scalar) {
+		return new Vector3f(x / scalar.x, y / scalar.y, z / scalar.z);
+	}
+
+	/**
+	 * <code>divideLocal</code> divides this vector by a scalar internally, and
+	 * returns a handle to this vector for easy chaining of calls. Dividing by
+	 * zero will result in an exception.
+	 *
+	 * @param scalar
+	 *            the value to divides this vector by.
+	 * @return this
+	 */
+	public Vector3f divideLocal(Vector3f scalar) {
+		x /= scalar.x;
+		y /= scalar.y;
+		z /= scalar.z;
+		return this;
+	}
+
+	/**
+	 *
+	 * <code>negate</code> returns the negative of this vector. All values are
+	 * negated and set to a new vector.
+	 *
+	 * @return the negated vector.
+	 */
+	public Vector3f negate() {
+		return new Vector3f(-x, -y, -z);
+	}
+
+	/**
+	 *
+	 * <code>negateLocal</code> negates the internal values of this vector.
+	 *
+	 * @return this.
+	 */
+	public Vector3f negateLocal() {
+		x = -x;
+		y = -y;
+		z = -z;
+		return this;
+	}
+
+	/**
+	 *
+	 * <code>subtract</code> subtracts the values of a given vector from those
+	 * of this vector creating a new vector object. If the provided vector is
+	 * null, null is returned.
+	 *
+	 * @param vec
+	 *            the vector to subtract from this vector.
+	 * @return the result vector.
+	 */
+	public Vector3f subtract(Vector3f vec) {
+		return new Vector3f(x - vec.x, y - vec.y, z - vec.z);
+	}
+
+	public Vector3f subtract2D(Vector3f vec) {
+		return new Vector3f(x - vec.x, 0, z - vec.z);
+	}
+
+	/**
+	 * <code>subtractLocal</code> subtracts a provided vector to this vector
+	 * internally, and returns a handle to this vector for easy chaining of
+	 * calls. If the provided vector is null, null is returned.
+	 *
+	 * @param vec
+	 *            the vector to subtract
+	 * @return this
+	 */
+	public Vector3f subtractLocal(Vector3f vec) {
+		if (null == vec) {
+			return null;
+		}
+		x -= vec.x;
+		y -= vec.y;
+		z -= vec.z;
+		return this;
+	}
+
+	/**
+	 *
+	 * <code>subtract</code>
+	 *
+	 * @param vec
+	 *            the vector to subtract from this
+	 * @param result
+	 *            the vector to store the result in
+	 * @return result
+	 */
+	public Vector3f subtract(Vector3f vec, Vector3f result) {
+		if (result == null) {
+			result = new Vector3f();
+		}
+		result.x = x - vec.x;
+		result.y = y - vec.y;
+		result.z = z - vec.z;
+		return result;
+	}
+
+	/**
+	 *
+	 * <code>subtract</code> subtracts the provided values from this vector,
+	 * creating a new vector that is then returned.
+	 *
+	 * @param subtractX
+	 *            the x value to subtract.
+	 * @param subtractY
+	 *            the y value to subtract.
+	 * @param subtractZ
+	 *            the z value to subtract.
+	 * @return the result vector.
+	 */
+	public Vector3f subtract(float subtractX, float subtractY, float subtractZ) {
+		return new Vector3f(x - subtractX, y - subtractY, z - subtractZ);
+	}
+
+	/**
+	 * <code>subtractLocal</code> subtracts the provided values from this vector
+	 * internally, and returns a handle to this vector for easy chaining of
+	 * calls.
+	 *
+	 * @param subtractX
+	 *            the x value to subtract.
+	 * @param subtractY
+	 *            the y value to subtract.
+	 * @param subtractZ
+	 *            the z value to subtract.
+	 * @return this
+	 */
+	public Vector3f subtractLocal(float subtractX, float subtractY, float subtractZ) {
+		x -= subtractX;
+		y -= subtractY;
+		z -= subtractZ;
+		return this;
+	}
+
+	/**
+	 * <code>normalize</code> returns the unit vector of this vector.
+	 *
+	 * @return unit vector of this vector.
+	 */
+	public Vector3f normalize() {
+		float length = length();
+		if (length != 0) {
+			return divide(length);
+		}
+
+		return divide(1);
+	}
+
+	/**
+	 * <code>normalizeLocal</code> makes this vector into a unit vector of
+	 * itself.
+	 *
+	 * @return this.
+	 */
+	public Vector3f normalizeLocal() {
+		float length = length();
+		if (length != 0) {
+			return divideLocal(length);
+		}
+
+		return this;
+	}
+
+	/**
+	 * <code>zero</code> resets this vector's data to zero internally.
+	 */
+	public void zero() {
+		x = y = z = 0;
+	}
+
+	/**
+	 * <code>angleBetween</code> returns (in radians) the angle between two
+	 * vectors. It is assumed that both this vector and the given vector are
+	 * unit vectors (iow, normalized).
+	 *
+	 * @param otherVector
+	 *            a unit vector to find the angle against
+	 * @return the angle in radians.
+	 */
+	public float angleBetween(Vector3f otherVector) {
+		float dotProduct = dot(otherVector);
+        return FastMath.acos(dotProduct);
+	}
+
+	/**
+	 * Sets this vector to the interpolation by changeAmnt from this to the
+	 * finalVec this=(1-changeAmnt)*this + changeAmnt * finalVec
+	 *
+	 * @param finalVec
+	 *            The final vector to interpolate towards
+	 * @param changeAmnt
+	 *            An amount between 0.0 - 1.0 representing a percentage change
+	 *            from this towards finalVec
+	 */
+	public void interpolate(Vector3f finalVec, float changeAmnt) {
+		this.x = (1 - changeAmnt) * this.x + changeAmnt * finalVec.x;
+		this.y = (1 - changeAmnt) * this.y + changeAmnt * finalVec.y;
+		this.z = (1 - changeAmnt) * this.z + changeAmnt * finalVec.z;
+	}
+	
+	
+	public Vector3f lerp(Vector3f finalVec, float changeAmnt) {
+		float x = (1 - changeAmnt) * this.x + changeAmnt * finalVec.x;
+		float y = (1 - changeAmnt) * this.y + changeAmnt * finalVec.y;
+		float z = (1 - changeAmnt) * this.z + changeAmnt * finalVec.z;
+		return new Vector3f(x,y,z);
+	}
+
+	/**
+	 * Sets this vector to the interpolation by changeAmnt from beginVec to
+	 * finalVec this=(1-changeAmnt)*beginVec + changeAmnt * finalVec
+	 *
+	 * @param beginVec
+	 *            the beginning vector (changeAmnt=0)
+	 * @param finalVec
+	 *            The final vector to interpolate towards
+	 * @param changeAmnt
+	 *            An amount between 0.0 - 1.0 representing a percentage change
+	 *            from beginVec towards finalVec
+	 */
+	public void interpolate(Vector3f beginVec, Vector3f finalVec, float changeAmnt) {
+		this.x = (1 - changeAmnt) * beginVec.x + changeAmnt * finalVec.x;
+		this.y = (1 - changeAmnt) * beginVec.y + changeAmnt * finalVec.y;
+		this.z = (1 - changeAmnt) * beginVec.z + changeAmnt * finalVec.z;
+	}
+
+	/**
+	 * Check a vector... if it is null or its floats are NaN or infinite, return
+	 * false. Else return true.
+	 *
+	 * @param vector
+	 *            the vector to check
+	 * @return true or false as stated above.
+	 */
+	public static boolean isValidVector(Vector3f vector) {
+		if (vector == null)
+			return false;
+		if (Float.isNaN(vector.x) || Float.isNaN(vector.y) || Float.isNaN(vector.z))
+			return false;
+        return !Float.isInfinite(vector.x) && !Float.isInfinite(vector.y) && !Float.isInfinite(vector.z);
+    }
+
+	public static void generateOrthonormalBasis(Vector3f u, Vector3f v, Vector3f w) {
+		w.normalizeLocal();
+		generateComplementBasis(u, v, w);
+	}
+
+	public static void generateComplementBasis(Vector3f u, Vector3f v, Vector3f w) {
+		float fInvLength;
+
+		if (FastMath.abs(w.x) >= FastMath.abs(w.y)) {
+			// w.x or w.z is the largest magnitude component, swap them
+			fInvLength = FastMath.invSqrt(w.x * w.x + w.z * w.z);
+			u.x = -w.z * fInvLength;
+			u.y = 0.0f;
+			u.z = +w.x * fInvLength;
+			v.x = w.y * u.z;
+			v.y = w.z * u.x - w.x * u.z;
+			v.z = -w.y * u.x;
+		} else {
+			// w.y or w.z is the largest magnitude component, swap them
+			fInvLength = FastMath.invSqrt(w.y * w.y + w.z * w.z);
+			u.x = 0.0f;
+			u.y = +w.z * fInvLength;
+			u.z = -w.y * fInvLength;
+			v.x = w.y * u.z - w.z * u.y;
+			v.y = -w.x * u.z;
+			v.z = w.x * u.y;
+		}
+	}
+
+	@Override
+	public Vector3f clone() {
+		try {
+			return (Vector3f) super.clone();
+		} catch (CloneNotSupportedException e) {
+			throw new AssertionError(); // can not happen
+		}
+	}
+
+	/**
+	 * Saves this Vector3f into the given float[] object.
+	 *
+	 * @param floats
+	 *            The float[] to take this Vector3f. If null, a new float[3] is
+	 *            created.
+	 * @return The array, with X, Y, Z float values in that order
+	 */
+	public float[] toArray(float[] floats) {
+		if (floats == null) {
+			floats = new float[3];
+		}
+		floats[0] = x;
+		floats[1] = y;
+		floats[2] = z;
+		return floats;
+	}
+
+	/**
+	 * are these two vectors the same? they are is they both have the same x,y,
+	 * and z values.
+	 *
+	 * @param o
+	 *            the object to compare for equality
+	 * @return true if they are equal
+	 */
+	@Override
+	public boolean equals(Object o) {
+		if (!(o instanceof Vector3f)) {
+			return false;
+		}
+
+		if (this == o) {
+			return true;
+		}
+
+		Vector3f comp = (Vector3f) o;
+		if (Float.compare(x, comp.x) != 0)
+			return false;
+		if (Float.compare(y, comp.y) != 0)
+			return false;
+        return Float.compare(z, comp.z) == 0;
+    }
+
+	/**
+	 * <code>hashCode</code> returns a unique code for this vector object based
+	 * on it's values. If two vectors are logically equivalent, they will return
+	 * the same hash code value.
+	 *
+	 * @return the hash code value of this vector.
+	 */
+	@Override
+	public int hashCode() {
+		int hash = 37;
+		hash += 37 * hash + Float.floatToIntBits(x);
+		hash += 37 * hash + Float.floatToIntBits(y);
+		hash += 37 * hash + Float.floatToIntBits(z);
+		return hash;
+	}
+
+	/**
+	 * Used with serialization. Not to be called manually.
+	 *
+	 * @param in
+	 *            input
+	 * @throws IOException
+	 * @throws ClassNotFoundException
+	 * @see java.io.Externalizable
+	 */
+	public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
+		x = in.readFloat();
+		y = in.readFloat();
+		z = in.readFloat();
+	}
+
+	/**
+	 * Used with serialization. Not to be called manually.
+	 *
+	 * @param out
+	 *            output
+	 * @throws IOException
+	 * @see java.io.Externalizable
+	 */
+	public void writeExternal(ObjectOutput out) throws IOException {
+		out.writeFloat(x);
+		out.writeFloat(y);
+		out.writeFloat(z);
+	}
+
+	public float getX() {
+		return x;
+	}
+
+	public void setX(float x) {
+		this.x = x;
+	}
+
+	public float getY() {
+		return y;
+	}
+
+	public void setY(float y) {
+		this.y = y;
+	}
+
+	public float getZ() {
+		return z;
+	}
+
+	public void setZ(float z) {
+		this.z = z;
+	}
+
+	/**
+	 * @param index
+	 * @return x value if index == 0, y value if index == 1 or z value if index
+	 *         == 2
+	 * @throws IllegalArgumentException
+	 *             if index is not one of 0, 1, 2.
+	 */
+	public float get(int index) {
+		switch (index) {
+		case 0:
+			return x;
+		case 1:
+			return y;
+		case 2:
+			return z;
+		}
+		throw new IllegalArgumentException("index must be either 0, 1 or 2");
+	}
+
+	/**
+	 * @param index
+	 *            which field index in this vector to set.
+	 * @param value
+	 *            to set to one of x, y or z.
+	 * @throws IllegalArgumentException
+	 *             if index is not one of 0, 1, 2.
+	 */
+	public void set(int index, float value) {
+		switch (index) {
+		case 0:
+			x = value;
+			return;
+		case 1:
+			y = value;
+			return;
+		case 2:
+			z = value;
+			return;
+		}
+		throw new IllegalArgumentException("index must be either 0, 1 or 2");
+	}
+
+	/**
+	 * Gets an offset from this position based on rotation around Y(up/down)-axis.
+	 *
+	 * @param rotation
+	 *            Rotation in radians
+	 * @param xOffset
+	 *            Amount to offset along x axis (left negative, right positive)
+	 * @param yOffset
+	 *            Amount to offset along y axis (down negative, up positive)
+	 * @param zOffset
+	 *            Amount to offset along z axis (backwards negative, forwards positive)
+	 * @param invertZ
+	 *            whether to invert the z axis
+	 */
+	public Vector3f getOffset(float rotation, float xOffset, float yOffset, float zOffset, boolean invertZ) {
+		float sin = FastMath.sin(rotation);
+		float cos = FastMath.cos(rotation);
+		Vector3f faceDir = new Vector3f(sin, 0f, cos);
+		Vector3f crossDir = new Vector3f(cos, 0f, sin);
+		faceDir.multLocal(zOffset);
+		crossDir.multLocal(xOffset);
+		if (invertZ) {
+			faceDir.z = -faceDir.z;
+			crossDir.z = -crossDir.z;
+		}
+		Vector3f loc = new Vector3f(this);
+		loc.addLocal(faceDir);
+		loc.addLocal(crossDir);
+		loc.y += yOffset;
+		return loc;
+	}
+
+	/**
+	 * Returns the 2D face direction from rotation.
+	 *
+	 * @param rotation
+	 *            Rotation in radians
+	 */
+	public static Vector3f getFaceDir(float rotation) {
+		return new Vector3f(FastMath.sin(rotation), 0f, FastMath.cos(rotation));
+	}
+
+	/**
+	 * Returns the 2D cross direction (perpendicular face direction) from rotation.
+	 *
+	 * @param rotation
+	 *            Rotation in radians
+	 */
+	public static Vector3f getCrossDir(float rotation) {
+		return new Vector3f(FastMath.cos(rotation), 0f, FastMath.sin(rotation));
+	}
+
+	/**
+	 * Returns the 2D rotation (around Y-axis) in radians.
+	 *
+	 * @return
+	 */
+	public float getRotation() {
+		return 3.14f + FastMath.atan2(-x, -z);
+	}
+
+	/**
+	 * Gets the XYZ component of this Vector3f
+	 *
+	 * @return
+	 */
+	public Vector2f getLatLong() {
+		return new Vector2f(this.x, this.z);
+	}
+
+	public synchronized float getLat() {
+		return x;
+	}
+
+	public synchronized float getLong() {
+		return z;
+	}
+
+	public synchronized float getAlt() {
+		return y;
+	}
+
+	public synchronized void setLat(float lat) {
+		this.x = lat;
+	}
+
+	public synchronized void setLong(float lon) {
+		this.z = lon;
+	}
+
+	public synchronized void setAlt(float alt) {
+		this.y = alt;
+	}
+	
+	public static Vector3f rotateAroundPoint(Vector3f origin, Vector3f point, double angle) {
+
+		float angleRadians;
+		double modifiedAngle;
+
+		// Convert angle to radians
+
+		modifiedAngle = angle;
+
+		if (angle < 0)
+			modifiedAngle = 360 + modifiedAngle;
+
+		angleRadians = (float) Math.toRadians(modifiedAngle);
+
+		return rotateAroundPoint(origin, point, angleRadians);
+	}
+
+	public static Vector3f rotateAroundPoint(Vector3f origin, Vector3f point, float radians) {
+
+		Vector3f outVector;
+		Vector3f directionVector;
+		Quaternion angleRotation;
+
+		// Build direction vector relative to origin
+
+		directionVector = new Vector3f(point.subtract(origin));
+
+		// Build quaternion rotation
+
+		angleRotation = new Quaternion().fromAngleAxis(radians, new Vector3f(0,1,0));
+
+		// Apply rotation to direction vector
+
+		directionVector = angleRotation.mult(directionVector);
+
+		// Translate from origin back to new rotated point
+
+		outVector = origin.add(directionVector);
+
+		return outVector;
+
+	}
+
+	@Override
+	public String toString() {
+		String out = "";
+		out += "x=" + x + ", ";
+		out += "y=" + y + ", ";
+		out += "z=" + z;
+		return out;
+	}
+
+	public static Vector3f min(Vector3f vectorA, Vector3f vectorB) {
+
+		return new Vector3f(Math.min(vectorA.x, vectorB.x),
+				Math.min(vectorA.y, vectorB.y),
+				Math.min(vectorA.z, vectorB.z));
+	}
+
+	public static Vector3f max(Vector3f vectorA, Vector3f vectorB) {
+
+		return new Vector3f(Math.max(vectorA.x, vectorB.x),
+				Math.max(vectorA.y, vectorB.y),
+				Math.max(vectorA.z, vectorB.z));
+	}
+	
+	public static Vector3f rotateAroundPoint(Vector3f origin, Vector3f point,Quaternion angleRotation) {
+
+		Vector3f outVector;
+		Vector3f directionVector;
+		// Build direction vector relative to origin
+		directionVector = new Vector3f(point.subtract(origin));
+		directionVector = angleRotation.mult(directionVector);
+
+		// Translate from origin back to new rotated point
+
+		outVector = origin.add(directionVector);
+
+		return outVector;
+
+	}
+
+}
diff --git a/src/engine/math/Vector3fImmutable.java b/src/engine/math/Vector3fImmutable.java
new file mode 100644
index 00000000..5e9bd32b
--- /dev/null
+++ b/src/engine/math/Vector3fImmutable.java
@@ -0,0 +1,595 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+package engine.math;
+
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+import java.util.concurrent.ThreadLocalRandom;
+
+import static engine.math.FastMath.sqr;
+
+public class Vector3fImmutable {
+
+	public final float x, y, z;
+
+	public Vector3fImmutable() {
+		x = y = z = 0.0f;
+	}
+
+	public Vector3fImmutable(float x, float y, float z) {
+		this.x = x;
+		this.y = y;
+		this.z = z;
+	}
+
+	public Vector3fImmutable(Vector3f original) {
+		this.x = original.x;
+		this.y = original.y;
+		this.z = original.z;
+	}
+
+	public Vector3fImmutable(Vector3fImmutable original) {
+		this.x = original.x;
+		this.y = original.y;
+		this.z = original.z;
+	}
+
+	public boolean isInsideCircle(Vector3fImmutable circleCenter, float radius) {
+
+		return (circleCenter.distanceSquared2D(this) < sqr(radius));
+	}
+
+	public Vector3fImmutable add(Vector3f vec) {
+		if (null == vec)
+			return null;
+		return new Vector3fImmutable(x + vec.x, y + vec.y, z + vec.z);
+	}
+
+	public Vector3fImmutable add(Vector3fImmutable vec) {
+		if (null == vec)
+			return null;
+		return new Vector3fImmutable(x + vec.x, y + vec.y, z + vec.z);
+	}
+
+	public Vector3fImmutable add(float x, float y, float z) {
+		return new Vector3fImmutable(this.x + x, this.y + y, this.z + z);
+	}
+
+	public Vector3fImmutable scaleAdd(float scalar, Vector3fImmutable add) {
+		return new Vector3fImmutable(x * scalar + add.x, y * scalar + add.y , z
+				* scalar + add.z);
+	}
+
+	public static Vector3fImmutable scaleAdd(float scalar, Vector3fImmutable mult,
+			Vector3fImmutable add) {
+		return new Vector3fImmutable(mult.x * scalar + add.x, mult.y * scalar
+				+ add.y, mult.z * scalar + add.z);
+	}
+
+	public float dot(Vector3fImmutable vec) {
+		if (null == vec) {
+			return 0.0f;
+		}
+
+		return x * vec.x + y * vec.y + z * vec.z;
+	}
+	
+	public float dot2D(Vector3fImmutable vec) {
+		if (null == vec) {
+			return 0.0f;
+		}
+
+		return x * vec.x  + z * vec.z;
+	}
+
+	public Vector3fImmutable cross(Vector3fImmutable v) {
+		return cross(v.x, v.y, v.z);
+	}
+
+	public Vector3fImmutable cross(float x, float y, float z) {
+		return new Vector3fImmutable(this.y * z - this.z * y, this.z * x
+				- this.x * z, this.x * y - this.y * x);
+	}
+
+	public float length() {
+		return FastMath.sqrt(lengthSquared());
+	}
+
+	public float lengthSquared() {
+		return x * x + y * y + z * z;
+	}
+
+	public float distanceSquared(Vector3fImmutable v) {
+		double dx = x - v.x;
+		double dy = y - v.y;
+		double dz = z - v.z;
+		return (float) (dx * dx + dy * dy + dz * dz);
+	}
+
+	public float magnitude() {
+		return FastMath.sqrt(sqrMagnitude());
+	}
+
+	public float sqrMagnitude() {
+		return x * x + y * y + z * z;
+	}
+
+	public Vector3fImmutable moveTowards (Vector3fImmutable target, float maxDistanceDelta)
+	{
+		Vector3fImmutable outVector;
+
+		Vector3fImmutable direction = target.subtract2D(this);
+		float magnitude = direction.magnitude();
+
+		if (magnitude <= maxDistanceDelta || magnitude == 0f)
+		{
+			return target;
+		}
+
+		outVector = direction.divide(magnitude).mult(maxDistanceDelta);
+		outVector = this.add(outVector);
+		return outVector;
+	}
+
+	public float distanceSquared2D(Vector3fImmutable v) {
+		double dx = x - v.x;
+		double dz = z - v.z;
+		return (float) (dx * dx + dz * dz);
+	}
+
+	public float distance(Vector3fImmutable v) {
+		return FastMath.sqrt(distanceSquared(v));
+	}
+
+	public float distance2D(Vector3fImmutable v) {
+		return FastMath.sqrt(distanceSquared2D(v));
+	}
+
+	public Vector3fImmutable mult(float scalar) {
+		return new Vector3fImmutable(x * scalar, y * scalar, z * scalar);
+	}
+
+	public Vector3fImmutable mult(Vector3fImmutable vec) {
+		if (null == vec) {
+			return null;
+		}
+
+		return new Vector3fImmutable(x * vec.x, y * vec.y, z * vec.z);
+	}
+
+	public Vector3fImmutable divide(float scalar) {
+		scalar = 1f / scalar;
+		return new Vector3fImmutable(x * scalar, y * scalar, z * scalar);
+	}
+
+	public Vector3fImmutable divide(Vector3fImmutable scalar) {
+		return new Vector3fImmutable(x / scalar.x, y / scalar.y, z / scalar.z);
+	}
+
+	public Vector3fImmutable negate() {
+		return new Vector3fImmutable(-x, -y, -z);
+	}
+
+	public Vector3fImmutable subtract(Vector3fImmutable vec) {
+		return new Vector3fImmutable(x - vec.x, y - vec.y, z - vec.z);
+	}
+
+	public Vector3fImmutable subtract2D(Vector3fImmutable vec) {
+		return new Vector3fImmutable(x - vec.x, 0, z - vec.z);
+	}
+
+	public Vector3fImmutable subtract(float x, float y, float z) {
+		return new Vector3fImmutable(this.x - x, this.y - y, this.z - z);
+	}
+
+	public Vector3fImmutable normalize() {
+		float length = length();
+		if (length != 0) {
+			return divide(length);
+		}
+
+		return divide(1);
+	}
+
+	public float angleBetween(Vector3fImmutable otherVector) {
+		float dotProduct = dot(otherVector);
+		return FastMath.acos(dotProduct);
+	}
+	
+	public float angleBetween2D(Vector3fImmutable otherVector) {
+		float dotProduct = dot(otherVector);
+		return FastMath.acos(dotProduct);
+	}
+
+	public Vector3fImmutable interpolate(Vector3f finalVec, float changeAmnt) {
+		return new Vector3fImmutable((1 - changeAmnt) * this.x + changeAmnt
+				* finalVec.x, (1 - changeAmnt) * this.y + changeAmnt
+				* finalVec.y, (1 - changeAmnt) * this.z + changeAmnt
+				* finalVec.z);
+	}
+	
+	public Vector3fImmutable interpolate(Vector3fImmutable finalVec, float changeAmnt) {
+		return new Vector3fImmutable((1 - changeAmnt) * this.x + changeAmnt
+				* finalVec.x, (1 - changeAmnt) * this.y + changeAmnt
+				* finalVec.y, (1 - changeAmnt) * this.z + changeAmnt
+				* finalVec.z);
+	}
+
+
+	public static boolean isValidVector(Vector3fImmutable vector) {
+		if (vector == null)
+			return false;
+		if (Float.isNaN(vector.x) || Float.isNaN(vector.y)
+				|| Float.isNaN(vector.z))
+			return false;
+		return !Float.isInfinite(vector.x) && !Float.isInfinite(vector.y)
+				&& !Float.isInfinite(vector.z);
+	}
+
+	@Override
+	public Vector3fImmutable clone() throws CloneNotSupportedException {
+		return (Vector3fImmutable) super.clone();
+	}
+
+	public float[] toArray(float[] floats) {
+		if (floats == null) {
+			floats = new float[3];
+		}
+		floats[0] = x;
+		floats[1] = y;
+		floats[2] = z;
+		return floats;
+	}
+
+	@Override
+	public boolean equals(Object o) {
+		if (!(o instanceof Vector3fImmutable)) {
+			return false;
+		}
+
+		if (this == o) {
+			return true;
+		}
+
+		Vector3fImmutable comp = (Vector3fImmutable) o;
+		if (Float.compare(x, comp.x) != 0)
+			return false;
+		if (Float.compare(y, comp.y) != 0)
+			return false;
+		return Float.compare(z, comp.z) == 0;
+	}
+
+	@Override
+	public int hashCode() {
+		int hash = 37;
+		hash += 37 * hash + Float.floatToIntBits(x);
+		hash += 37 * hash + Float.floatToIntBits(y);
+		hash += 37 * hash + Float.floatToIntBits(z);
+		return hash;
+	}
+
+	public static Vector3fImmutable readExternal(ObjectInput in)
+			throws IOException, ClassNotFoundException {
+		return new Vector3fImmutable(in.readFloat(), in.readFloat(), in
+				.readFloat());
+	}
+
+	public void writeExternal(ObjectOutput out) throws IOException {
+		out.writeFloat(x);
+		out.writeFloat(y);
+		out.writeFloat(z);
+	}
+
+	public Vector3fImmutable getOffset(float rotation, float xOffset, float yOffset, float zOffset, boolean invertZ) {
+		float sin = FastMath.sin(rotation);
+		float cos = FastMath.cos(rotation);
+		Vector3f faceDir = new Vector3f(sin, 0f, cos);
+		Vector3f crossDir = new Vector3f(cos, 0f, sin);
+		faceDir.multLocal(zOffset);
+		crossDir.multLocal(xOffset);
+		if (invertZ) {
+			faceDir.z = -faceDir.z;
+			crossDir.z = -crossDir.z;
+		}
+		Vector3f loc = new Vector3f(this);
+		loc.addLocal(faceDir);
+		loc.addLocal(crossDir);
+		loc.y += yOffset;
+		return new Vector3fImmutable(loc);
+	}
+
+	public float getX() {
+		return x;
+	}
+
+	public float getY() {
+		return y;
+	}
+
+	public float getZ() {
+		return z;
+	}
+
+	public float get(int index) {
+		switch (index) {
+		case 0:
+			return x;
+		case 1:
+			return y;
+		case 2:
+			return z;
+		}
+		throw new IllegalArgumentException("index must be either 0, 1 or 2");
+	}
+
+	public Vector2f getLatLong() {
+		return new Vector2f(this.x, this.z);
+	}
+
+	public synchronized float getLat() {
+		return x;
+	}
+
+	public synchronized float getLong() {
+		return z;
+	}
+
+	public synchronized float getAlt() {
+		return y;
+	}
+
+	public Vector3fImmutable setX(float x) {
+		return new Vector3fImmutable(x, y, z);
+	}
+
+	public Vector3fImmutable setY(float y) {
+		return new Vector3fImmutable(x, y, z);
+	}
+
+	public Vector3fImmutable setZ(float z) {
+		return new Vector3fImmutable(x, y, z);
+	}
+
+	public float getRotation() {
+		return 3.14f + FastMath.atan2(-x, -z);
+	}
+	public boolean inRange2D(Vector3fImmutable otherVec, float range){
+		float distance = this.distanceSquared2D(otherVec);
+		return !(distance > range * range);
+	}
+
+	public static String toString(Vector3fImmutable vector) {
+
+		return vector.toString();
+	}
+
+	@Override
+	public String toString() {
+
+		String outString;
+
+		outString = "(" + this.x + '/' + this.y + '/' + this.z;
+		return outString;
+
+	}
+
+	public String toString2D() {
+
+		String outString;
+
+		outString = "( " + (int)this.x + " , " + (int)(this.z *-1) +" )";
+		return outString;
+
+	}
+
+	public static Vector3fImmutable ClosestPointOnLine(Vector3fImmutable lineStart, Vector3fImmutable lineEnd, Vector3fImmutable sourcePoint) {
+
+		Vector3fImmutable closestPoint;
+		Vector3fImmutable lineStartToTarget;
+		Vector3fImmutable lineDirection;
+		float lineLength;
+		float dotProduct;
+
+		lineStartToTarget = sourcePoint.subtract(lineStart);
+		lineDirection = lineEnd.subtract(lineStart).normalize();
+		lineLength = lineStart.distance2D(lineEnd);
+
+		dotProduct = lineDirection.dot(lineStartToTarget);
+
+		if (dotProduct <= 0)
+			return lineStart;
+
+		if (dotProduct >= lineLength)
+			return lineEnd;
+
+		// Project the point by advancing it along the line from
+		// the starting point.
+
+		closestPoint = lineDirection.mult(dotProduct);
+		closestPoint = lineStart.add(closestPoint);
+
+		return closestPoint;
+	}
+
+	public Vector3fImmutable ClosestPointOnLine(Vector3fImmutable lineStart, Vector3fImmutable lineEnd) {
+
+		Vector3fImmutable closestPoint;
+		Vector3fImmutable lineStartToTarget;
+		Vector3fImmutable lineDirection;
+		float lineLength;
+		float dotProduct;
+
+		lineStartToTarget = this.subtract(lineStart);
+		lineDirection = lineEnd.subtract(lineStart).normalize();
+		lineLength = lineStart.distance2D(lineEnd);
+
+		dotProduct = lineDirection.dot(lineStartToTarget);
+
+		if (dotProduct <= 0)
+			return lineStart;
+
+		if (dotProduct >= lineLength)
+			return lineEnd;
+
+		// Project the point by advancing it along the line from
+		// the starting point.
+
+		closestPoint = lineDirection.mult(dotProduct);
+		closestPoint = lineStart.add(closestPoint);
+
+		return closestPoint;
+	}
+
+	public static Vector3fImmutable rotateAroundPoint(Vector3fImmutable origin, Vector3fImmutable point, int angle) {
+
+		float angleRadians;
+		int modifiedAngle;
+
+		// Convert angle to radians
+
+		      modifiedAngle = angle;
+
+		    if (angle < 0)
+		      modifiedAngle = 360 + modifiedAngle;
+
+		angleRadians = (float) Math.toRadians(modifiedAngle);
+
+		return rotateAroundPoint(origin, point, angleRadians);
+	}
+
+	public static Vector3fImmutable rotateAroundPoint(Vector3fImmutable origin, Vector3fImmutable point, float radians) {
+
+		Vector3fImmutable outVector;
+		Vector3f directionVector;
+		Quaternion angleRotation;
+
+		// Build direction vector relative to origin
+
+		directionVector = new Vector3f(point.subtract(origin));
+
+		// Build quaternion rotation
+
+		angleRotation = new Quaternion().fromAngleAxis(radians, new Vector3f(0,1,0));
+		// Apply rotation to direction vector
+
+		directionVector = angleRotation.mult(directionVector);
+
+		// Translate from origin back to new rotated point
+
+		outVector = origin.add(directionVector);
+
+		return outVector;
+
+	}
+	
+	public static Vector3fImmutable rotateAroundPoint(Vector3fImmutable origin, Vector3fImmutable point,Quaternion angleRotation) {
+
+		Vector3fImmutable outVector;
+		Vector3f directionVector;
+		// Build direction vector relative to origin
+		directionVector = new Vector3f(point.subtract(origin));
+
+		// Build quaternion rotation
+
+
+		// Apply rotation to direction vector
+		
+
+		directionVector = angleRotation.mult(directionVector);
+
+		// Translate from origin back to new rotated point
+
+		outVector = origin.add(directionVector);
+
+		return outVector;
+
+	}
+	
+	public static Vector3fImmutable rotateAroundPoint(Vector3fImmutable origin, Vector3fImmutable point, float w, Vector3f axis) {
+
+		Vector3fImmutable outVector;
+		Vector3f directionVector;
+		Quaternion angleRotation;
+
+		// Build direction vector relative to origin
+
+		directionVector = new Vector3f(point.subtract(origin));
+
+		// Build quaternion rotation
+
+		angleRotation = new Quaternion().fromAngleAxis(w, axis);
+		// Apply rotation to direction vector
+
+		directionVector = angleRotation.mult(directionVector);
+
+		// Translate from origin back to new rotated point
+
+		outVector = origin.add(directionVector);
+
+		return outVector;
+
+	}
+
+	public static Vector3fImmutable getRandomPointInCircle(Vector3fImmutable origin, float radius) {
+		// Member variables
+
+		float targetAngle;
+		float targetRadius;
+		Vector3fImmutable targetPosition;
+
+		targetAngle = (float) (ThreadLocalRandom.current().nextFloat() * Math.PI * 2);
+		targetRadius = (float) (Math.sqrt(ThreadLocalRandom.current().nextFloat()) * radius);
+		targetPosition = new Vector3fImmutable((float) (origin.x + targetRadius * Math.cos(targetAngle)), origin.y, (float) (origin.z + targetRadius * Math.sin(targetAngle)));
+		return targetPosition;
+	}
+	
+	public static Vector3fImmutable getLocBetween(Vector3fImmutable start, Vector3fImmutable end) {
+		// Member variables
+
+		Vector3fImmutable faceDirection = end.subtract(start).normalize();
+		float distance = end.distance(start) * .5f;
+		return faceDirection.scaleAdd(distance, start);
+	}
+
+	public static Vector3fImmutable getRandomPointOnCircle(Vector3fImmutable origin, float radius) {
+
+		// Member variables
+
+		int randomAngle;
+		Vector3fImmutable targetPosition;
+
+		randomAngle = ThreadLocalRandom.current().nextInt(360);
+
+		targetPosition = new Vector3fImmutable((float) (origin.x + radius * Math.cos(randomAngle)), origin.y, (float) (origin.z + radius * Math.sin(randomAngle)));
+		return targetPosition;
+	}
+
+	public static final Vector3fImmutable ZERO = new Vector3fImmutable(0,0,0);
+	
+	public static Vector3fImmutable transform(Vector3fImmutable origin,Vector3fImmutable point, float angle){
+		
+		//TRANSLATE TO ORIGIN
+        float x1 = point.x - origin.x;
+        float y1 = point.z - origin.z;
+
+		//APPLY ROTATION
+		float temp_x1 = (float) (x1 * Math.cos(angle) - y1 * Math.sin(angle));
+		float temp_z1 = (float) (x1 * Math.sin(angle) + y1 * Math.cos(angle));
+		
+		temp_x1 += origin.x;
+		temp_z1 += origin.z;
+		
+		return new Vector3fImmutable(temp_x1,point.y,temp_z1);
+	}
+	public float Lerp(Vector3fImmutable dest, float lerpFactor)
+	{
+		return dest.subtract(this).mult(lerpFactor).add(this).y;
+	}
+}
diff --git a/src/engine/net/AbstractConnection.java b/src/engine/net/AbstractConnection.java
new file mode 100644
index 00000000..30d34ca8
--- /dev/null
+++ b/src/engine/net/AbstractConnection.java
@@ -0,0 +1,425 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net;
+
+import engine.job.JobManager;
+import engine.net.client.Protocol;
+import engine.net.client.msg.ClientNetMsg;
+import org.pmw.tinylog.Logger;
+
+import java.io.IOException;
+import java.net.SocketException;
+import java.nio.ByteBuffer;
+import java.nio.channels.ClosedChannelException;
+import java.nio.channels.NotYetConnectedException;
+import java.nio.channels.SocketChannel;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.locks.ReentrantLock;
+
+public abstract class AbstractConnection implements
+NetMsgHandler {
+
+	private static final String error01 = "A byte buffer is being free()ed that is not of the size stored in ByteBufferPool. Size: %1%";
+	private static final String error02 = "(IP=%1%): Socket has reached the maximum outbound buffer length of %2%. Moving on while we wait for data to be sent.";
+
+	protected final SocketChannel sockChan;
+	protected final AbstractConnectionManager connMan;
+	protected final NetMsgFactory factory;
+
+	protected long lastMsgTime = System.currentTimeMillis();
+	protected long lastKeepAliveTime = System.currentTimeMillis();
+	protected long lastOpcode = -1;
+
+	protected ConcurrentLinkedQueue<ByteBuffer> outbox = new ConcurrentLinkedQueue<>();
+	protected ByteBuffer outBuf = null;
+
+	protected final AtomicBoolean execTask = new AtomicBoolean(false);
+
+	protected ConcurrentHashMap<Long, Byte> cacheList;
+
+	protected final ReentrantLock writeLock = new ReentrantLock();
+	protected final ReentrantLock readLock = new ReentrantLock();
+	protected long nextWriteTime = 0L;
+	protected Protocol lastProtocol = Protocol.NONE;
+	protected static final long SOCKET_FULL_WRITE_DELAY = 50L; //wait one second to write on full socket
+
+	//Opcode tracking
+
+	private static final int OPCODEHASH = 1016;	//Opcodes are unique modulo this number as of 11/21/10
+
+	public AbstractConnection(AbstractConnectionManager connMan,
+			SocketChannel sockChan,  boolean clientMode) {
+		this.connMan = connMan;
+		this.sockChan = sockChan;
+		this.factory = new NetMsgFactory(this);
+
+	}
+
+	public void disconnect() {
+		try {
+			this.sockChan.close();
+		} catch (IOException e) {
+			Logger.error(e.toString());
+		}
+	}
+
+	/**
+	 * Serializes AbstractNetMsg <i>msg</i> into a ByteBuffer, then queues that
+	 * ByteBuffer for sending on this AbstractConnection's SocketChannel.
+	 *
+	 * @param msg
+	 * - AbstractNetMsg to be sent.
+	 * @return boolean status if queue is successful or not. On false return,
+	 * the field <i>lastError</i> will be set.
+	 */
+	public final boolean sendMsg(AbstractNetMsg msg) {
+		//		Logger.info("Send: " + msg.getSimpleClassName());
+		try {
+			return this._sendMsg(msg);
+
+		} catch (Exception e) { // Catch-all
+			Logger.error(e);
+			return false;
+		}
+	}
+
+	/**
+	 * Serializes AbstractNetMsg <i>msg</i> into a ByteBuffer, then queues that
+	 * ByteBuffer for sending on this AbstractConnection's SocketChannel. This
+	 * internal function is <b>required</b> to be overridden by subclasses.
+	 *
+	 * @param msg
+	 * - AbstractNetMsg to be sent.
+	 * @return boolean status if queue is successful or not. On false return,
+	 * the field <i>lastError</i> will be set.
+	 */
+	protected abstract boolean _sendMsg(AbstractNetMsg msg);
+
+	/**
+	 * Queues ByteBuffer <i>bb</i> for sending on this AbstractConnection's
+	 * SocketChannel.
+	 *
+	 * @param bb
+	 * - ByteBuffer to be sent.
+	 * @return boolean status if queue is successful or not. On false return,
+	 * the field <i>lastError</i> will be set.
+	 */
+	public final boolean sendBB(ByteBuffer bb) {
+		
+		if (bb == null)
+			return false;
+		try {
+			return this._sendBB(bb);
+		} catch (Exception e) { // Catch-all
+			Logger.error(e);
+			e.printStackTrace();
+			return false;
+		}
+	}
+
+	/**
+	 * Queues ByteBuffer <i>bb</i> for sending on this AbstractConnection's
+	 * SocketChannel. This internal function is designed to be overrideable by
+	 * subclasses if needed.
+	 *
+	 * @param bb
+	 * - ByteBuffer to be sent.
+	 * @return boolean status if queue is successful or not. On false return,
+	 * the field <i>lastError</i> will be set.
+	 */
+	protected boolean _sendBB(ByteBuffer bb) {
+		this.outbox.add(bb);
+		this.connMan.sendStart(this.sockChan);
+		return true;
+	}
+
+	/**
+	 * Move data off the socketChannel's buffer and into the Connection's
+	 * internal NetMsgFactory.
+	 */
+	// FIXME the int return value on this means nothing! Clean it up!
+	protected int read() {
+
+		if (readLock.tryLock())
+			try {
+
+				if (this.sockChan.isOpen() == false) {
+					   Logger.info("Sock channel closed. Disconnecting " + this.getRemoteAddressAndPortAsString());
+					this.disconnect();
+					return 0;
+				}
+
+				// get socket buffer sized buffer from multipool
+				ByteBuffer bb = Network.byteBufferPool.getBuffer(16);
+
+				int totalBytesRead = 0;
+				int lastRead;
+				do {
+					try {
+						bb.clear();
+						lastRead = this.sockChan.read(bb);
+
+						// On EOF on the SocketChannel, disconnect.
+						if (lastRead <= -1) {
+							   Logger.info(" EOF on Socket Channel " + this.getRemoteAddressAndPortAsString());
+							this.disconnect();
+							break;
+						}
+
+						if (lastRead == 0)
+							continue;
+
+						synchronized (this.factory) {
+							this.factory.addData(bb);
+						}
+						this.checkInternalFactory();
+
+						totalBytesRead += lastRead;
+
+					} catch (NotYetConnectedException e) {
+						Logger.error(e.toString());
+						break;
+
+					} catch (ClosedChannelException e) {
+						Logger.error(e.toString());
+						this.disconnect();
+						break;
+
+					} catch (IOException e) {
+						if (!e.getMessage().startsWith(
+								"An existing connection was forcibly closed"))
+							Logger.error(e.toString());
+						this.disconnect();
+						break;
+
+					} catch (Exception e) {
+						Logger.error(e.toString());
+						break;
+					}
+				} while (lastRead > 0);
+
+				// put buffer back into multipool
+				Network.byteBufferPool.putBuffer(bb);
+
+				this.checkInternalFactory();
+				return totalBytesRead;
+
+			} finally {
+				readLock.unlock();
+			}
+		else {
+			Logger.debug("Another thread already has a read lock! Skipping.");
+			return 0;
+		}
+	}
+
+	/**
+	 * Move data off the Connection's buffer and into the SocketChannel's
+	 * Buffer.
+	 */
+	protected boolean writeAll() {
+
+		//intentional delay if socket is full. Give the socket a chance to clear out.
+		if (System.currentTimeMillis() < this.nextWriteTime)
+			return false;
+
+		if (writeLock.tryLock())
+			try {
+				String s = "";
+				int written = 0;
+
+				boolean allSentOK = true, bufferDoubled = false;
+
+				while (this.outbox.peek() != null) {
+					ByteBuffer bb = this.outbox.peek();
+
+					int toSend = bb.position();
+
+					try {
+						bb.flip();
+						written = this.sockChan.write(bb);
+
+						// Logger.debug("Using a BB with cap of: " + bb.capacity()
+						// + ". toSend: " + toSend + ", written: " + written);
+						if (written != toSend)
+							bb.compact(); // Clean up in case not all gets sent
+
+						if (toSend == written) {
+							// Actually remove it from the queue
+							this.outbox.poll();
+
+							// Pool it
+							Network.byteBufferPool.putBuffer(bb);
+							continue;
+
+						} else
+							if (written == 0) {
+								//Socket full, let's delay the next write on socket.
+								this.nextWriteTime = System.currentTimeMillis() + AbstractConnection.SOCKET_FULL_WRITE_DELAY;
+
+								int currentBufferSize = this.sockChan.socket()
+										.getSendBufferSize();
+
+								if (!bufferDoubled && currentBufferSize <= Network.INITIAL_SOCKET_BUFFER_SIZE) {
+									this.doubleSocketSendBufferSize();
+									bufferDoubled = true;
+								} else {
+
+									//                                    s = error02;
+									//                                    s = s.replaceAll("%1%", this
+									//                                            .getRemoteAddressAndPortAsString() + ":" +
+									//                                            this.printLastOpcodes());
+									//                                    s = s.replaceAll("%2%", currentBufferSize + "");
+									//                                    this.warn(s);
+
+									allSentOK = false;
+									break;
+								}
+							}
+
+					} catch (ClosedChannelException e) {
+						// Catches AsynchronousCloseException,
+						// and ClosedByInterruptException
+						Logger.error(e);
+						break;
+
+					} catch (Exception e) {
+						// Catches NotYetConnectedException
+						// and IOException
+						Logger.error(e);
+						this.disconnect();
+						break;
+					}
+
+				}
+				return allSentOK;
+			} finally {
+				writeLock.unlock();
+			}
+		else {
+			Logger.debug("Another thread already has a write lock! Skipping.");
+			return false;
+		}
+	}
+
+	private boolean doubleSocketSendBufferSize() {
+		String s;
+		try {
+			int currentSize = this.sockChan.socket().getSendBufferSize();
+			int newSize = currentSize << 1;
+
+			this.sockChan.socket().setSendBufferSize(newSize);
+
+			//            s = "(IP=" + this.getRemoteAddressAndPortAsString() + "): ";
+			//            s += this.printLastOpcodes();
+			//            s += "Socket has reached the maximum outbound buffer length of ";
+			//            s += currentSize + ". Attempting to double the outbound buffer size.";
+			//
+			//            this.warn(s);
+			return true;
+		} catch (SocketException e) {
+			Logger.error( e.toString());
+			return false;
+		}
+	}
+
+	public boolean isConnected() {
+		return this.sockChan.isConnected();
+	}
+
+	public boolean isOpen() {
+		return this.sockChan.isOpen();
+	}
+
+	/*
+	 * Getters n Setters
+	 */
+	public SocketChannel getSocketChannel() {
+		return this.sockChan;
+	}
+
+	public final void checkInternalFactory() {
+		CheckNetMsgFactoryJob j = new CheckNetMsgFactoryJob(this);
+		JobManager.getInstance().submitJob(j);
+	}
+
+	public NetMsgFactory getFactory() {
+		return factory;
+	}
+
+	public String getRemoteAddressAndPortAsString() {
+		String out = "";
+
+		if (this.sockChan == null)
+			out += "NotConnected";
+		else if (this.sockChan.socket() == null)
+			out += "NotConnected";
+		else if (this.sockChan.socket().getRemoteSocketAddress() == null)
+			out += "NotConnected";
+		else
+			out += this.sockChan.socket().getRemoteSocketAddress().toString();
+
+		return out;
+	}
+
+	public String getLocalAddressAndPortAsString() {
+		String out = "";
+
+		if (this.sockChan == null)
+			out += "NotConnected";
+		else if (this.sockChan.socket() == null)
+			out += "NotConnected";
+		else if (this.sockChan.socket().getRemoteSocketAddress() == null)
+			out += "NotConnected";
+		else {
+			out += this.sockChan.socket().getLocalSocketAddress().toString();
+			out += ":";
+			out += this.sockChan.socket().getLocalPort();
+		}
+
+		return out;
+	}
+
+	/**
+	 * Gives the Connection a chance to act on a msg prior to sending it to the
+	 * provided NetMsgHandler
+	 *
+	 */
+	@Override
+	public abstract boolean handleClientMsg(ClientNetMsg msg);
+
+	protected long getLastMsgTime() {
+		return this.lastMsgTime;
+	}
+
+	protected void setLastMsgTime() {
+		// TODO Consider making this a static to latest system time
+		this.lastMsgTime = System.currentTimeMillis();
+	}
+
+	protected long getLastKeepAliveTime() {
+		return this.lastKeepAliveTime;
+	}
+
+	protected void setLastKeepAliveTime() {
+		this.lastKeepAliveTime = System.currentTimeMillis();
+	}
+
+	public long getLastOpcode() {
+		return lastOpcode;
+	}
+
+	public void setLastOpcode(long lastOpcode) {
+		this.lastOpcode = lastOpcode;
+	}
+
+}
diff --git a/src/engine/net/AbstractConnectionManager.java b/src/engine/net/AbstractConnectionManager.java
new file mode 100644
index 00000000..22f6bd5e
--- /dev/null
+++ b/src/engine/net/AbstractConnectionManager.java
@@ -0,0 +1,708 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net;
+
+import engine.core.ControlledRunnable;
+import engine.job.AbstractJob;
+import engine.job.JobManager;
+import engine.server.MBServerStatics;
+import org.pmw.tinylog.Logger;
+
+import java.io.IOException;
+import java.net.*;
+import java.nio.channels.*;
+import java.nio.channels.spi.SelectorProvider;
+import java.util.Iterator;
+import java.util.concurrent.ConcurrentLinkedQueue;
+
+public abstract class AbstractConnectionManager extends ControlledRunnable {
+
+	private final Selector selector;
+	private final ServerSocketChannel listenChannel;
+	private final ConcurrentLinkedQueue<ChangeRequest> chngReqs = new ConcurrentLinkedQueue<>();
+
+	/*
+	 *
+	 */
+	public AbstractConnectionManager(String nodeName, InetAddress hostAddress,
+			int port) throws IOException {
+		super(nodeName);
+
+		this.selector = SelectorProvider.provider().openSelector();
+
+		// Create a new non-blocking Server channel
+		this.listenChannel = ServerSocketChannel.open();
+		this.listenChannel.configureBlocking(false);
+
+		// Bind
+		InetSocketAddress isa = new InetSocketAddress(hostAddress, port);
+		this.listenChannel.socket().bind(isa);
+
+		Logger.info(this.getLocalNodeName() + " Configured to listen: "
+				+ isa.getAddress().toString() + ':' + port);
+
+		// register an interest in Accepting new connections.
+		SelectionKey sk = this.listenChannel.register(this.selector, SelectionKey.OP_ACCEPT);
+		sk.attach(this);
+	}
+
+	/*
+	 * ControlledRunnable implementations
+	 */
+	@Override
+	protected void _startup() {
+	}
+
+	@Override
+	protected void _shutdown() {
+		this.selector.wakeup();
+		this.disconnectAll();
+		this.selector.wakeup();
+	}
+
+	@Override
+	protected boolean _preRun() {
+		this.runStatus = true;
+		return true;
+	}
+
+	@Override
+	protected boolean _Run() {
+		while (this.runCmd) {
+			try {
+				this.runLoopHook();
+
+				this.processChangeRequests();
+				this.auditSocketChannelToConnectionMap();
+				this.selector.select(250L);
+				this.processNewEvents();
+
+			} catch (Exception e) {
+				Logger.error(e.toString());
+			}
+		}
+		return true;
+	}
+
+	@Override
+	protected boolean _postRun() {
+
+		this.runStatus = false;
+
+		this.disconnectAll();
+
+		try {
+			this.selector.close();
+		} catch (IOException e) {
+			Logger.error( e.toString());
+		}
+
+		return true;
+	}
+
+	/**
+	 * Hook for subclasses to use.
+	 *
+	 */
+	protected void runLoopHook() {
+	}
+
+	/*
+	 * Accept / New Connection FNs
+	 */
+	private AbstractConnection acceptNewConnection(final SelectionKey key)
+			throws IOException {
+
+		this.preAcceptNewConnectionHook(key);
+
+		// Cancel incoming connections if server isn't set to listen
+		if (this.listenChannel == null || this.listenChannel.isOpen() == false) {
+			key.cancel();
+			return null;
+		}
+
+		// For an accept to be pending, the key contains a reference to the
+		// ServerSocketChannel
+		ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
+
+		// Get SocketChannel
+		SocketChannel sockChan = ssc.accept();
+		sockChan.configureBlocking(false);
+
+		//Configure the Socket
+		Socket socket = sockChan.socket();
+		socket.setSendBufferSize(Network.INITIAL_SOCKET_BUFFER_SIZE);
+		socket.setReceiveBufferSize(Network.INITIAL_SOCKET_BUFFER_SIZE);
+		socket.setTcpNoDelay(MBServerStatics.TCP_NO_DELAY_DEFAULT);
+
+		//Register with the selector
+		SelectionKey sk = sockChan.register(this.selector, SelectionKey.OP_READ);
+		if (sk == null) {
+			Logger.error("FIX ME! NULL SELECTION KEY!");
+			return null;
+		}
+
+		//Initialize Connection
+		AbstractConnection ac = this.getNewIncomingConnection(sockChan);
+		sk.attach(ac);
+
+		this.postAcceptNewConnectionHook(ac);
+		return ac;
+	}
+
+	/**
+	 * Hook for subclasses to use.
+	 *
+	 * @param key
+	 */
+	protected void preAcceptNewConnectionHook(SelectionKey key) {
+	}
+
+	/**
+	 * Hook for subclasses to use.
+	 *
+	 * @param ac
+	 */
+	protected void postAcceptNewConnectionHook(AbstractConnection ac) {
+	}
+
+	protected abstract AbstractConnection getNewIncomingConnection(SocketChannel sockChan);
+
+	protected abstract AbstractConnection getNewOutgoingConnection(SocketChannel sockChan);
+
+	/*
+	 * Disconnect / Destroy Connection FNs
+	 */
+	protected boolean disconnect(final SelectionKey key) {
+
+		this.disconnect((AbstractConnection) key.attachment());
+
+		key.attach(null);
+		key.cancel();
+		return key.isValid();
+	}
+
+	protected boolean disconnect(final AbstractConnection c) {
+		if (c == null)
+			return false;
+
+		c.disconnect();
+
+		return c.getSocketChannel().isConnected();
+	}
+
+	protected void disconnectAll() {
+		synchronized (this.selector.keys()) {
+			for (SelectionKey sk : this.selector.keys()) {
+				if (sk.channel() instanceof SocketChannel)
+					disconnect(sk);
+			}
+		}
+	}
+
+	/*
+	 * Data IO
+	 */
+
+	/*
+	 * WRITE SEQUENCE
+	 */
+	/**
+	 * Submits a request to set this Connection to WRITE mode.
+	 *
+	 */
+	protected void sendStart(final SocketChannel sockChan) {
+		synchronized (this.chngReqs) {
+			// Indicate we want the interest ops set changed
+			this.chngReqs.add(new ChangeRequest(sockChan, ChangeType.CHANGEOPS, SelectionKey.OP_WRITE));
+		}
+
+		this.selector.wakeup();
+	}
+
+	/**
+	 *
+	 * @param key
+	 * @return Boolean indication emit success.
+	 */
+	protected boolean sendFinish(final SelectionKey key) {
+		SocketChannel sockChan = (SocketChannel) key.channel();
+
+		// Check to see if the SocketChannel the selector offered up
+		// is null.
+		if (sockChan == null) {
+			Logger.error(": null sockChannel.");
+			this.disconnect(key);
+			return false;
+		}
+
+		AbstractConnection c = (AbstractConnection) key.attachment();
+
+		if (c == null) {
+			Logger.error(": null Connection.");
+			this.disconnect(key);
+			return false;
+		}
+
+		//		long startTime = System.currentTimeMillis();
+		boolean allSent = c.writeAll();
+
+		//		if ((System.currentTimeMillis() - startTime) > 20)
+		//			this.logDirectWARNING(c.getRemoteAddressAndPortAsString() + " took " + (System.currentTimeMillis() - startTime) + "ms to handle!");
+
+		// If all was written, switch back to Read Mode.
+		if (allSent || !c.isConnected()) {
+
+			// Indicate we want the interest ops set changed
+			ChangeRequest chReq = new ChangeRequest(c.getSocketChannel(), ChangeType.CHANGEOPS, SelectionKey.OP_READ);
+			synchronized (this.chngReqs) {
+				this.chngReqs.add(chReq);
+			}
+
+			this.selector.wakeup();
+		}
+		return true;
+	}
+
+	/*
+	 * READ SEQUENCE
+	 */
+	/**
+	 *
+	 * @param key
+	 * @return Boolean indication of success.
+	 */
+	private boolean receive(final SelectionKey key) {
+		SocketChannel sockChan = (SocketChannel) key.channel();
+
+		// Check to see if the SocketChannel the selector offered up
+		// is null.
+		if (sockChan == null) {
+			Logger.error("null sockChannel.");
+			this.disconnect(key);
+			return false;
+		}
+
+		AbstractConnection c = (AbstractConnection) key.attachment();
+
+		if (c == null) {
+			Logger.error("null Connection.");
+			this.disconnect(key);
+			return false;
+		}
+
+		c.read();
+
+		return true;
+	}
+
+	/*
+	 * Main Loop And Loop Controls
+	 */
+	private void processChangeRequests() {
+		SelectionKey selKey = null;
+		ChangeRequest sccr = null;
+		ChangeType change = null;
+		SocketChannel sockChan = null;
+
+		synchronized (this.chngReqs) {
+			Iterator<ChangeRequest> it = this.chngReqs.iterator();
+			while (it.hasNext()) {
+				sccr = it.next();
+
+				if (sccr == null) {
+					it.remove();
+					continue;
+				}
+
+				change = sccr.getChangeType();
+				sockChan = sccr.getSocketChannel();
+
+				switch (change) {
+				case CHANGEOPS:
+					selKey = sockChan.keyFor(this.selector);
+
+					if (selKey == null || selKey.isValid() == false)
+						continue;
+
+					selKey.interestOps(sccr.getOps());
+					break;
+
+				case REGISTER:
+					try {
+						sockChan.register(this.selector, sccr.getOps());
+
+					} catch (ClosedChannelException e) {
+						// TODO Should a closed channel be logged or just
+						// cleaned up?
+						// Logger.error(this.getLocalNodeName(), e);
+					}
+					break;
+				}
+			}
+			this.chngReqs.clear();
+		}
+	}
+
+	private void processNewEvents() {
+		SelectionKey thisKey = null;
+		Iterator<SelectionKey> selectedKeys = null;
+		JobManager jm = JobManager.getInstance();
+
+		selectedKeys = this.selector.selectedKeys().iterator();
+
+		if (selectedKeys.hasNext() == false)
+			return;
+
+		while (selectedKeys.hasNext()) {
+			thisKey = selectedKeys.next();
+
+			//To shake out any issues
+			if (thisKey.attachment() == null)
+				Logger.error("Found null attachment! PANIC!");
+
+			if (thisKey.attachment() instanceof AbstractConnection)
+				if (((AbstractConnection) thisKey.attachment()).execTask.get() == true)
+					continue;
+
+			selectedKeys.remove();
+
+			try {
+				if (thisKey.isValid() == false)
+					break;  // Changed from continue
+				else if (thisKey.isAcceptable())
+					this.acceptNewConnection(thisKey);
+				else if (thisKey.isReadable())
+					jm.submitJob(new ReadOperationHander(thisKey));
+				else if (thisKey.isWritable())
+					jm.submitJob(new WriteOperationHander(thisKey));
+				else if (thisKey.isConnectable())
+					this.finishConnectingTo(thisKey);
+				else
+					Logger.error("Unhandled keystate: " + thisKey.toString());
+			} catch (CancelledKeyException cke) {
+				Logger.error(this.getLocalNodeName(), cke);
+				this.disconnect(thisKey);
+
+			} catch (IOException e) {
+				Logger.error(this.getLocalNodeName(), e);
+			}
+		}
+	}
+
+	protected void connectTo(String host, int port) {
+		try {
+			this.connectTo(InetAddress.getByName(host), port);
+		} catch (UnknownHostException e) {
+			Logger.error(this.getLocalNodeName(), e);
+		}
+	}
+
+	protected void connectTo(InetAddress host, int port) {
+		try {
+			this.startConnectingTo(host, port);
+			this.selector.wakeup();
+		} catch (IOException e) {
+			Logger.error(this.getLocalNodeName(), e);
+		}
+	}
+
+	protected final void startConnectingTo(InetAddress host, int port)
+			throws IOException {
+		// Create a non-blocking socket channel
+		SocketChannel sockChan = SocketChannel.open();
+		sockChan.configureBlocking(false);
+		sockChan.socket().setSendBufferSize(Network.INITIAL_SOCKET_BUFFER_SIZE);
+		sockChan.socket().setReceiveBufferSize(Network.INITIAL_SOCKET_BUFFER_SIZE);
+
+		// Make a new Connection object
+		this.getNewOutgoingConnection(sockChan);
+
+		// Kick off connection establishment
+		sockChan.connect(new InetSocketAddress(host, port));
+
+		synchronized (this.chngReqs) {
+			this.chngReqs.add(new ChangeRequest(sockChan, ChangeType.REGISTER, SelectionKey.OP_CONNECT));
+		}
+
+		this.selector.wakeup();
+	}
+
+	private void finishConnectingTo(SelectionKey key) throws IOException {
+		this.preFinishConnectingToHook(key);
+
+		// Get sockChan
+		SocketChannel sockChan = (SocketChannel) key.channel();
+
+		// Get AbstractConnection
+		AbstractConnection ac = (AbstractConnection) key.attachment();
+
+		if (sockChan == null) {
+			Logger.error(this.getLocalNodeName(), "null socketChannel");
+			this.disconnect(key);
+			return;
+		}
+
+		if (ac == null) {
+			Logger.error(this.getLocalNodeName(), "null AbstractConnection");
+			this.disconnect(key);
+			return;
+		}
+
+		// Finish the connection. If the connection operation failed
+		// this will raise an IOException.
+		try {
+			sockChan.finishConnect();
+		} catch (IOException e) {
+			if (e.getMessage().startsWith("Connection refused:")
+					|| e.getMessage().startsWith(
+							"An existing connection was forcibly closed")) {
+				// eat this type of IOException
+			} else
+				Logger.error(this.getLocalNodeName(), e);
+
+			// Cancel the channel's registration with our selector
+			key.cancel();
+			return;
+		}
+
+		Socket socket = sockChan.socket();
+		Logger.debug("Connected to: "
+				+ socket.getInetAddress() + ':'
+				+ socket.getPort());
+
+		sockChan.configureBlocking(false);
+		sockChan.register(this.selector, SelectionKey.OP_READ);
+
+		this.postFinishConnectingToHook(ac);
+	}
+
+	/**
+	 * Hook for subclasses to use.
+	 *
+	 * @param key
+	 */
+	protected void preFinishConnectingToHook(SelectionKey key) {
+	}
+
+	/**
+	 * Hook for subclasses to use.
+	 *
+	 * @param ac
+	 */
+	protected void postFinishConnectingToHook(AbstractConnection ac) {
+	}
+
+	public final String getLocalNodeName() {
+		return this.getThreadName();
+	}
+
+	/**
+	 * Removes the mapping that contains the key 'sockChan'
+	 *
+	 * @param sockChan
+	 */
+	private long lastAuditTime = 0;
+
+	protected int auditSocketChannelToConnectionMap() {
+		long startTime = System.currentTimeMillis();
+		int numberOfItemsToProcess = 0;
+
+		if (lastAuditTime + MBServerStatics.TIMEOUT_CHECKS_TIMER_MS > startTime)
+			return -1;
+
+		synchronized (this.selector.keys()) {
+			for (SelectionKey sk : this.selector.keys()) {
+				if (!(sk.channel() instanceof SocketChannel))
+					continue;
+
+				SocketChannel sockChan = (SocketChannel) sk.channel();
+				AbstractConnection conn = (AbstractConnection) sk.attachment();
+
+				if (sockChan == null)
+					continue;
+
+				if (!sockChan.isOpen()) {
+					numberOfItemsToProcess++;
+				     Logger.info("sockChan closed. Disconnecting..");
+					disconnect(sk);
+					continue;
+				}
+
+				if (conn == null) {
+					numberOfItemsToProcess++;
+					Logger.info("Connection is null, Disconnecting.");
+					disconnect(sk);
+					continue;
+				}
+
+				//removed keep alive timeout. Believe failmu used this for disconnecting players on force quit, but a closed socket will already disconnect.
+//                if (conn.getLastKeepAliveTime() + MBServerStatics.KEEPALIVE_TIMEOUT_MS < startTime) {
+//                    numberOfItemsToProcess++;
+//                    Logger.info("Keep alive Disconnecting " + conn.getRemoteAddressAndPortAsString());
+//                    conn.disconnect();
+//                    continue;
+//                }
+
+				if (conn.getLastMsgTime() + MBServerStatics.AFK_TIMEOUT_MS < startTime) {
+					numberOfItemsToProcess++;
+					   Logger.info("AFK TIMEOUT Disconnecting " + conn.getRemoteAddressAndPortAsString());
+					conn.disconnect();
+				}
+			}
+		}
+
+		if (numberOfItemsToProcess != 0)
+			Logger.info( "Cleaned "
+					+ numberOfItemsToProcess
+					+ " dead connections in "
+					+ (System.currentTimeMillis() - startTime)
+					+ " millis.");
+
+		lastAuditTime = System.currentTimeMillis();
+		return numberOfItemsToProcess;
+	}
+
+	/*
+	 *
+	 */
+	protected static enum ChangeType {
+
+		REGISTER, CHANGEOPS
+	}
+
+	private class ChangeRequest {
+
+		private final SocketChannel sockChan;
+		private final ChangeType changeType;
+		private final Integer ops;
+
+		public ChangeRequest(SocketChannel sockChan, ChangeType changeType,
+				int ops) {
+			this.sockChan = sockChan;
+			this.changeType = changeType;
+			this.ops = ops;
+		}
+
+		public SocketChannel getSocketChannel() {
+			synchronized (this.sockChan) {
+				return this.sockChan;
+			}
+		}
+
+		public ChangeType getChangeType() {
+			synchronized (this.changeType) {
+				return this.changeType;
+			}
+		}
+
+		public int getOps() {
+			synchronized (this.ops) {
+				return this.ops;
+			}
+		}
+	}
+
+	public int getConnectionSize() {
+		if (this.selector == null)
+			return -1;
+		if (this.selector.keys() == null)
+			return -1;
+		return this.selector.keys().size();
+	}
+
+	/**
+	 * Returns the port on which this socket is listening.
+	 *
+	 * @return the port number to which this socket is listening or -1 if the
+	 * socket is not bound yet.
+	 *
+	 */
+	public int getListeningPort() {
+		if (this.listenChannel == null)
+			return -1;
+		if (this.listenChannel.socket() == null)
+			return -1;
+		return this.listenChannel.socket().getLocalPort();
+	}
+
+	/**
+	 * Returns the address of the endpoint this socket is bound to, or null if
+	 * it is not bound yet.
+	 *
+	 * @return a SocketAddress representing the local endpoint of this socket,
+	 * or null if it is not bound yet.
+	 */
+	public SocketAddress getListeningAddress() {
+		if (this.listenChannel == null)
+			return null;
+		if (this.listenChannel.socket() == null)
+			return null;
+		return this.listenChannel.socket().getLocalSocketAddress();
+	}
+
+	private class ReadOperationHander extends AbstractJob {
+
+		private final SelectionKey sk;
+		private final AbstractConnection ac;
+		private final boolean runStatus;
+
+		public ReadOperationHander(final SelectionKey sk) {
+			this.sk = sk;
+
+			if (sk.attachment() instanceof AbstractConnection) {
+				this.ac = (AbstractConnection) sk.attachment();
+				this.runStatus = this.ac.execTask.compareAndSet(false, true);
+			} else {
+				this.ac = null;
+				this.runStatus = false;
+				Logger.error("Passed selection key did not have a corresponding Connection!(Read)");
+			}
+		}
+
+		@Override
+		protected void doJob() {
+			if (runStatus) {
+				this.ac.connMan.receive(sk);
+				this.ac.execTask.compareAndSet(true, false);
+			}
+		}
+	}
+
+	private class WriteOperationHander extends AbstractJob {
+
+		private final SelectionKey sk;
+		private final AbstractConnection ac;
+		private final boolean runStatus;
+
+		public WriteOperationHander(final SelectionKey sk) {
+			this.sk = sk;
+
+			if (sk.attachment() instanceof AbstractConnection) {
+				this.ac = (AbstractConnection) sk.attachment();
+				this.runStatus = this.ac.execTask.compareAndSet(false, true);
+			} else {
+				this.runStatus = false;
+				this.ac = null;
+				Logger.error("Passed selection key did not have a corresponding Connection!(Write)");
+			}
+
+		}
+
+		@Override
+		protected void doJob() {
+			if (runStatus) {
+				this.ac.connMan.sendFinish(sk);
+				this.ac.execTask.compareAndSet(true, false);
+			}
+		}
+	}
+
+}
diff --git a/src/engine/net/AbstractNetMsg.java b/src/engine/net/AbstractNetMsg.java
new file mode 100644
index 00000000..1e8e9c1f
--- /dev/null
+++ b/src/engine/net/AbstractNetMsg.java
@@ -0,0 +1,245 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net;
+
+import engine.exception.SerializationException;
+import engine.net.client.Protocol;
+import engine.server.MBServerStatics;
+import engine.util.StringUtils;
+import org.pmw.tinylog.Logger;
+
+import java.nio.BufferOverflowException;
+import java.nio.ByteBuffer;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * This class represents the NetMsgs set to/from the SBClient and in between
+ * MBServer Server Suite components. Note that since the NetMsgs sent to/from
+ * the SBClient do NOT include a MsgLen or DataLen parameter, special
+ * serialization/deserialization must be implemented.
+ *
+ */
+public abstract class AbstractNetMsg {
+
+    protected final Protocol protocolMsg;
+    private AbstractConnection origin;
+
+    private static ConcurrentHashMap<Protocol, NetMsgStat> stats = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_HIGH);
+
+    /**
+     * This is the general purpose constructor.
+     *
+     * @param protocolMsg
+     */
+    protected AbstractNetMsg(Protocol protocolMsg) {
+        super();
+        this.protocolMsg = protocolMsg;
+    }
+
+    protected AbstractNetMsg(Protocol protocolMsg, AbstractConnection origin) {
+        super();
+        this.protocolMsg = protocolMsg;
+        this.origin = origin;
+    }
+
+    protected AbstractNetMsg(Protocol protocolMsg, AbstractNetMsg msg) {
+        super();
+        this.protocolMsg = protocolMsg;
+        this.origin = msg.origin;
+    }
+
+    /**
+     * This constructor is used by NetMsgFactory. It attempts to deserialize the
+     * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+     * past the limit) then this constructor Throws that Exception to the
+     * caller.
+     *
+     * @param reader
+     */
+    protected AbstractNetMsg(Protocol protocolMsg, AbstractConnection origin,
+                             ByteBufferReader reader)
+             {
+        this.protocolMsg = protocolMsg;
+        this.origin = origin;
+
+        // Call the subclass specific deserializer
+        try {
+            this._deserialize(reader);
+        } catch (NullPointerException e) {
+            Logger.error(e);
+        }
+    }
+
+    /**
+     * Deserializes the subclass specific items from the supplied
+     * ByteBufferReader
+     *
+     * @param reader
+     */
+    protected abstract void _deserialize(ByteBufferReader reader);
+
+    /**
+     * Serializes the subclass specific items to the supplied ByteBufferWriter
+     *
+     * @param writer
+     * @throws Exception
+     */
+    protected abstract void _serialize(ByteBufferWriter writer)
+            throws SerializationException;
+
+    /**
+     * Attempts to serialize this NetMsg into a ByteBuffer. ByteBuffer is
+     * obtained from a pool, so to retain max efficiency, the caller needs to
+     * return this BB to the pool. Header size and layout is entirely defined by
+     * the subclass of AbstractNetMsg
+     *
+     * @return a ByteBuffer
+     */
+    public final ByteBuffer serialize() {
+
+        NetMsgStat stat;
+
+        if (!AbstractNetMsg.stats.containsKey(this.protocolMsg)) {
+            stat = new NetMsgStat(this.protocolMsg, this.getPowerOfTwoBufferSize());
+            AbstractNetMsg.stats.put(this.protocolMsg, stat);
+        } else
+            stat = AbstractNetMsg.stats.get(this.protocolMsg);
+        int lowerPow = stat.getMax();
+        int upperPow = lowerPow + 4;
+
+        ByteBuffer bb = null;
+        
+        int startPos = 0;
+        ByteBufferWriter writer = null;
+
+        for (int i = lowerPow; i < upperPow; ++i) {
+
+            // get an appropriate sized BB from pool
+
+            bb = Network.byteBufferPool.getBuffer(i);
+
+            // Mark start position
+
+            startPos = bb.position();
+
+            // Make a writer
+
+            writer = new ByteBufferWriter(bb); // FIXME inefficient to
+
+            // Set aside header here.
+
+            AbstractNetMsg.allocHeader(writer, this.getHeaderSize());
+
+            // Now serialize the object's specifics
+
+            try {
+                this._serialize(writer);
+
+                //Serialize successful, update NetMsgStat
+
+                stat.updateStat(i);
+
+            } catch (BufferOverflowException boe) {
+                Logger.error("BufferSize PowerOfTwo: " + i
+                        + " is too small for " + protocolMsg != null? protocolMsg.name() : this.getClass().getName() +  ", trying again with " + (i + 1));
+
+                //Return buffer.
+
+                Network.byteBufferPool.putBuffer(bb);
+                continue;
+
+            } catch (Exception e) {
+
+                //Return buffer.
+            	Logger.error(e);
+            	e.printStackTrace();
+
+                Network.byteBufferPool.putBuffer(bb);
+                return null;
+            }
+
+			// This shouldn't throw any errors since this part of the BB has
+            // already been allocated
+
+            this.writeHeaderAt(startPos, writer);
+            return writer.getBb();
+        }
+
+		// If we get here, its not a successful serialization and lastError
+        // should be set
+
+        return null;
+    }
+
+    private static void allocHeader(ByteBufferWriter writer, int bytes) {
+        byte zero = 0; // prevents the int->byte cast
+        for (int h = 0; h < bytes; ++h) {
+            writer.put(zero);
+        }
+    }
+
+    /**
+     * Function allows for setting other than default initial Buffer size for
+     * the Serializer. Override and return the size of the buffer in power of
+     * two bytes.
+     *
+     * Example, if you would like a buffer of 65535, then return 16 from this
+     * value since 2^16 = 65535
+     *
+     * @return the power to raise two to.
+     */
+    protected int getPowerOfTwoBufferSize() {
+        return (10); // 2^10 == 1024
+    }
+
+    /**
+     * Forces subclass to define how large (in bytes) the message's header is.
+     *
+     * @return the length (in bytes) of this message type's header.
+     */
+    protected abstract int getHeaderSize();
+
+    /**
+     * Forces subclasses to implement how to write its own header into a
+     * byteBuffer
+     *
+     * @param startPos
+     * - starting position for the header write
+     * @param writer
+     * - ByteBufferWriter to write the header to.
+     */
+    protected abstract void writeHeaderAt(int startPos, ByteBufferWriter writer);
+
+    /**
+     * @return The protocolMsg of this Msg.
+     */
+    public Protocol getProtocolMsg() {
+        return protocolMsg;
+    }
+
+    /**
+     * @return The protocolMsg As a string.
+     */
+    public String getOpcodeAsString() {
+        return StringUtils.toHexString(protocolMsg.opcode);
+    }
+
+    /**
+     * @return the origin
+     */
+    public AbstractConnection getOrigin() {
+        return origin;
+    }
+
+    public void setOrigin(AbstractConnection conn) {
+        this.origin = conn;
+    }
+
+}
diff --git a/src/engine/net/ByteBufferReader.java b/src/engine/net/ByteBufferReader.java
new file mode 100644
index 00000000..f5bb71b1
--- /dev/null
+++ b/src/engine/net/ByteBufferReader.java
@@ -0,0 +1,370 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net;
+
+import engine.math.Vector3f;
+import engine.math.Vector3fImmutable;
+import engine.util.ByteBufferUtils;
+import engine.util.ByteUtils;
+
+import java.nio.Buffer;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.UUID;
+
+public class ByteBufferReader {
+
+	private final ByteBuffer bb;
+	private final boolean endianFlip;
+
+	/**
+	 * Helper class for getting structured information out of a ByteBuffer
+	 * easily. ByteBuffer passed in is copied to an internal ByteBuffer.
+	 * <B>bbin</B> must have the position attribute at the <i>end</i> of the
+	 * data to be copied over, since <b>bbin.flip()</b> is called in this
+	 * constructor.
+	 *
+	 * @param bbin
+	 * @param endianFlip
+	 */
+	public ByteBufferReader(ByteBuffer bbin, boolean endianFlip) {
+		super();
+
+		// Copy supplied BB.
+		this.bb = ByteBuffer.allocate(bbin.position()); //FIXME Do we want to get this from pool?
+		bbin.flip();
+		this.bb.put(bbin);
+
+		// prepare bb for reading
+		this.bb.flip();
+
+		this.endianFlip = endianFlip;
+	}
+
+	/*
+	 * Getters
+	 */
+
+	/**
+	 * @return
+	 * @see java.nio.ByteBuffer#get()
+	 */
+	public byte get() {
+		return bb.get();
+	}
+
+	public void get(byte[] ba) {
+		this.bb.get(ba);
+	}
+
+	/**
+	 * @return
+	 * @see java.nio.ByteBuffer#getChar()
+	 */
+	public char getChar() {
+		char x = bb.getChar();
+		if (this.endianFlip) {
+			x = Character.reverseBytes(x);
+		}
+		return x;
+	}
+
+	/**
+	 * @return
+	 * @see java.nio.ByteBuffer#getDouble()
+	 */
+	public double getDouble() {
+		double x = 0;
+		if (this.endianFlip) {
+			x = bb.order(ByteOrder.LITTLE_ENDIAN).getDouble();
+			bb.order(ByteOrder.BIG_ENDIAN);
+		} else {
+			x = bb.getDouble();
+		}
+		return x;
+	}
+
+	/**
+	 * @return
+	 * @see java.nio.ByteBuffer#getFloat()
+	 */
+	public float getFloat() {
+		float x = 0;
+		if (this.endianFlip) {
+			x = bb.order(ByteOrder.LITTLE_ENDIAN).getFloat();
+			bb.order(ByteOrder.BIG_ENDIAN);
+		} else {
+			x = bb.getFloat();
+		}
+		return x;
+	}
+
+	/**
+	 * @return
+	 * @see java.nio.ByteBuffer#getInt()
+	 */
+	public int getInt() {
+		int x = bb.getInt();
+		if (this.endianFlip) {
+			x = Integer.reverseBytes(x);
+		}
+		return x;
+	}
+
+	/**
+	 * @return
+	 * @see java.nio.ByteBuffer#getLong()
+	 */
+	public long getLong() {
+		long x = bb.getLong();
+		if (this.endianFlip) {
+			x = Long.reverseBytes(x);
+		}
+		return x;
+	}
+
+	/**
+	 * @return
+	 * @see java.nio.ByteBuffer#getShort()
+	 */
+	public short getShort() {
+		short x = bb.getShort();
+		if (this.endianFlip) {
+			x = Short.reverseBytes(x);
+		}
+		return x;
+	}
+
+	public final String getString() {
+		if (this.endianFlip) {
+			return ByteBufferUtils.getString(this.bb, true, false);
+		} else {
+			return ByteBufferUtils.getString(this.bb, false, false);
+		}
+	}
+
+	public final String getSmallString() {
+		if (this.endianFlip) {
+			return ByteBufferUtils.getString(this.bb, true, true);
+		} else {
+			return ByteBufferUtils.getString(this.bb, false, true);
+		}
+	}
+
+	public final String getHexString() {
+		if (this.endianFlip) {
+			return ByteBufferUtils.getHexString(this.bb, true);
+		} else {
+			return ByteBufferUtils.getHexString(this.bb);
+		}
+	}
+
+	public final String getUnicodeString() {
+		if (this.endianFlip) {
+			return ByteBufferUtils.getUnicodeString(this.bb, true);
+		} else {
+			return ByteBufferUtils.getUnicodeString(this.bb);
+		}
+	}
+
+	public String get1ByteAsHexString() {
+		return getBytesAsHexStringCommon(new byte[1]);
+	}
+
+	public String get2BytesAsHexString() {
+		return getBytesAsHexStringCommon(new byte[2]);
+	}
+
+	public String get4BytesAsHexString() {
+		return getBytesAsHexStringCommon(new byte[4]);
+	}
+
+	public String get8BytesAsHexString() {
+		return getBytesAsHexStringCommon(new byte[8]);
+	}
+
+	private String getBytesAsHexStringCommon(byte[] ba) {
+		this.bb.get(ba);
+		if (this.endianFlip) {
+			return ByteUtils.byteArrayToStringHex(ByteUtils
+					.switchByteArrayEndianness(ba));
+		} else {
+			return ByteUtils.byteArrayToStringHex(ba);
+		}
+	}
+
+	public Vector3f getVector3f() {
+		Vector3f out = new Vector3f();
+		if (this.endianFlip) {
+			out.x = Float
+					.intBitsToFloat(Integer.reverseBytes(this.bb.getInt()));
+			out.y = Float
+					.intBitsToFloat(Integer.reverseBytes(this.bb.getInt()));
+			out.z = Float
+					.intBitsToFloat(Integer.reverseBytes(this.bb.getInt()));
+		} else {
+			out.x = this.bb.getFloat();
+			out.y = this.bb.getFloat();
+			out.z = this.bb.getFloat();
+		}
+		return out;
+	}
+
+	public Vector3fImmutable getVector3fImmutable() {
+		Vector3fImmutable out;
+		if (this.endianFlip) {
+			out = new Vector3fImmutable(Float.intBitsToFloat(Integer
+					.reverseBytes(this.bb.getInt())), Float
+					.intBitsToFloat(Integer.reverseBytes(this.bb.getInt())),
+					Float
+							.intBitsToFloat(Integer.reverseBytes(this.bb
+									.getInt())));
+		} else {
+			out = new Vector3fImmutable(this.bb.getFloat(), this.bb.getFloat(),
+					this.bb.getFloat());
+		}
+		return out;
+	}
+
+	public final UUID getUUID() {
+		final byte[] buffer = new byte[16];
+		this.bb.get(buffer);
+		
+	    long msb = 0;
+	    long lsb = 0;
+            
+	    for (int i = 0; i < 8; i++) {
+                msb = (msb << 8) | (buffer[i] & 0xff);
+                }
+            
+	    for (int i = 8; i < 16; i++) {
+                lsb = (lsb << 8) | (buffer[i] & 0xff);
+                }
+
+        return new UUID(msb, lsb);
+	}
+
+	
+	/*
+	 * Monitors
+	 */
+
+	public byte monitorByte(byte expectedValue, String label) {
+		return this.monitorByte(expectedValue, label, false);
+	}
+
+	public byte monitorByte(byte expectedValue, String label, boolean peek) {
+        //		if (x != expectedValue) {
+//			Logger.info("MonitorTrip: " + label + ". Expected: "
+//					+ expectedValue + " Got: " + x);
+//		}
+		return this.get();
+	}
+
+	public short monitorShort(short expectedValue, String label) {
+		return this.monitorShort(expectedValue, label, false);
+	}
+
+	public short monitorShort(short expectedValue, String label, boolean peek) {
+        //		if (x != expectedValue) {
+//			Logger.info("MonitorTrip: " + label + ". Expected: "
+//					+ expectedValue + " Got: " + x);
+//		}
+		return this.getShort();
+	}
+
+	public int monitorInt(int expectedValue, String label) {
+		return this.monitorInt(expectedValue, label, false);
+	}
+
+	public int monitorInt(int expectedValue, String label, boolean peek) {
+        //		if (x != expectedValue) {
+//			Logger.info("MonitorTrip: " + label + ". Expected: "
+//					+ expectedValue + " Got: " + x);
+//		}
+		return this.getInt();
+	}
+
+	public long monitorLong(long expectedValue, String label) {
+		return this.monitorLong(expectedValue, label, false);
+	}
+
+	public long monitorLong(long expectedValue, String label, boolean peek) {
+        //		if (x != expectedValue) {
+//			Logger.info("MonitorTrip: " + label + ". Expected: "
+//					+ expectedValue + " Got: " + x);
+//		}
+		return this.getLong();
+	}
+
+	/*
+	 * ByteBuffer delegates
+	 */
+
+	/**
+	 * @return
+	 * @see java.nio.Buffer#hasRemaining()
+	 */
+	public final boolean hasRemaining() {
+		return bb.hasRemaining();
+	}
+
+	/**
+	 * @return
+	 * @see java.nio.Buffer#limit()
+	 */
+	public final int limit() {
+		return bb.limit();
+	}
+
+	/**
+	 * @return
+	 * @see java.nio.Buffer#position()
+	 */
+	public final int position() {
+		return bb.position();
+	}
+	public final Buffer position(int newPosition){
+		return bb.position(newPosition);
+	}
+	/**
+	 * @return
+	 * @see java.nio.Buffer#remaining()
+	 */
+	public final int remaining() {
+		return bb.remaining();
+	}
+
+	/*
+	 * Status getters
+	 */
+
+	protected ByteBuffer getBb() {
+		return bb;
+	}
+
+	protected boolean isEndianFlip() {
+		return endianFlip;
+	}
+
+	public String getByteArray() {
+		String ret = "";
+		if (this.bb == null)
+			return ret;
+		byte[] bbyte = bb.array();
+		if (bbyte == null)
+			return ret;
+		for (int i=0;i<bbyte.length;i++) {
+                    ret += Integer.toString((bbyte[i] & 0xff) + 0x100, 16).substring(1).toUpperCase();
+                }
+		return ret;
+	}
+
+}
diff --git a/src/engine/net/ByteBufferWriter.java b/src/engine/net/ByteBufferWriter.java
new file mode 100644
index 00000000..9ae013ac
--- /dev/null
+++ b/src/engine/net/ByteBufferWriter.java
@@ -0,0 +1,206 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net;
+
+import engine.math.Vector3f;
+import engine.math.Vector3fImmutable;
+import engine.util.ByteBufferUtils;
+import org.joda.time.DateTime;
+
+import java.nio.ByteBuffer;
+import java.time.LocalDateTime;
+import java.util.UUID;
+
+//TODO possibly pool this class? Maybe make them static?
+//TODO need to extract the SB specific stuff from here into subclass.
+
+public class ByteBufferWriter {
+
+	private final ByteBuffer bb;
+
+	public ByteBufferWriter(ByteBuffer bb) {
+		super();
+		this.bb = bb;
+	}
+
+	/*
+	 * Putters
+	 */
+
+	public synchronized void put(byte x) {
+
+		this.bb.put(x);
+	}
+
+	public synchronized void putChar(char x) {
+
+		this.bb.putChar(x);
+	}
+
+	public synchronized void putShort(short x) {
+
+		this.bb.putShort(x);
+	}
+
+	public synchronized void putInt(int x) {
+		this.bb.putInt(x);
+	}
+
+	public synchronized void putLong(long x) {
+		this.bb.putLong(x);
+	}
+
+	public synchronized void putDateTime(DateTime dateTime){
+		this.put((byte)dateTime.getDayOfMonth());
+		this.put((byte) ((byte)dateTime.getMonthOfYear() -1));
+		this.putInt(dateTime.getYear()-1900);
+		this.put((byte)dateTime.getHourOfDay());
+		this.put((byte)dateTime.getMinuteOfHour());
+		this.put((byte)dateTime.getSecondOfDay());
+	}
+
+	public synchronized void putLocalDateTime(LocalDateTime dateTime){
+		this.put((byte)dateTime.getDayOfMonth());
+		this.put((byte) ((byte)dateTime.getMonth().getValue() -1));
+		this.putInt(dateTime.getYear() -1900);
+		this.put((byte)dateTime.getHour());
+		this.put((byte)dateTime.getMinute());
+		this.put((byte)dateTime.getSecond());
+	}
+
+	public synchronized void putFloat(float x) {
+			this.bb.putFloat(x);
+	}
+
+	public synchronized void putDouble(double x) {
+			this.bb.putDouble(x);
+	}
+
+	public synchronized void putString(String x) {
+			ByteBufferUtils.putString(this.bb, x, false, false);
+	}
+
+	public synchronized void putSmallString(String x) {
+			ByteBufferUtils.putString(this.bb, x, false, true);
+	}
+
+	public synchronized void putHexString(String x) {
+			ByteBufferUtils.putHexString(this.bb, x, false);
+	}
+
+	public synchronized void putHexStringWithoutSize(String data) {
+		int length = data.length() / 2;
+
+		for (int i = 0; i < length; i++) {
+			bb.put((byte) Integer.parseInt(data.substring(2 * i, 2 * i + 2), 16));
+		}
+	}
+
+	public synchronized void putUnicodeString(String x) {
+			ByteBufferUtils.putUnicodeString(this.bb, x, false);
+	}
+
+	public synchronized void putWriter(ByteBufferWriter writer) {
+		synchronized (writer) {
+            ByteBuffer bbin = writer.bb;
+			bbin.flip();
+			this.bb.put(bbin);
+		}
+	}
+
+	public synchronized void putBB(ByteBuffer bb) {
+		synchronized (bb) {
+			bb.flip();
+			this.bb.put(bb);
+		}
+	}
+
+	public synchronized void putIntAt(int value, int position) {
+		// mark end position
+		int endPosition = this.position();
+
+		// go back to the desired position
+		this.bb.position(position);
+
+		// Write in the value:
+		this.putInt(value);
+
+		// now go back to end:
+		this.bb.position(endPosition);
+	}
+
+	public synchronized void putVector3f(Vector3f x) {
+			this.bb.putFloat(x.x);
+			this.bb.putFloat(x.y);
+			this.bb.putFloat(x.z);
+	}
+
+	public synchronized void putVector3f(Vector3fImmutable x) {
+			this.bb.putFloat(x.x);
+			this.bb.putFloat(x.y);
+			this.bb.putFloat(x.z);
+	}
+
+	public synchronized void putUUID(final UUID uuid) {
+		final long msb = uuid.getMostSignificantBits();
+		final long lsb = uuid.getLeastSignificantBits();
+		final byte[] buffer = new byte[16];
+
+		for (int i = 0; i < 8; i++) {
+			buffer[i] = (byte) (msb >>> 8 * (7 - i));
+		}
+
+		for (int i = 8; i < 16; i++) {
+			buffer[i] = (byte) (lsb >>> 8 * (7 - i));
+		}
+
+		this.bb.put(buffer);
+	}
+
+	/**
+	 * @return
+	 * @see java.nio.Buffer#hasRemaining()
+	 */
+	public final boolean hasRemaining() {
+		return bb.hasRemaining();
+	}
+
+	/**
+	 * @return
+	 * @see java.nio.Buffer#limit()
+	 */
+	public final int limit() {
+		return bb.limit();
+	}
+
+	/**
+	 * @return
+	 * @see java.nio.Buffer#position()
+	 */
+	public final int position() {
+		return bb.position();
+	}
+
+	/**
+	 * @return
+	 * @see java.nio.Buffer#remaining()
+	 */
+	public final int remaining() {
+		return bb.remaining();
+	}
+
+	/*
+	 * Getters
+	 */
+
+	public synchronized ByteBuffer getBb() {
+		return this.bb;
+	}
+}
diff --git a/src/engine/net/CheckNetMsgFactoryJob.java b/src/engine/net/CheckNetMsgFactoryJob.java
new file mode 100644
index 00000000..4f67417a
--- /dev/null
+++ b/src/engine/net/CheckNetMsgFactoryJob.java
@@ -0,0 +1,53 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+package engine.net;
+
+import engine.gameManager.ConfigManager;
+import engine.job.AbstractJob;
+import engine.net.client.msg.ClientNetMsg;
+import org.pmw.tinylog.Logger;
+
+public class CheckNetMsgFactoryJob extends AbstractJob {
+
+	private final AbstractConnection conn;
+
+	public CheckNetMsgFactoryJob(AbstractConnection conn) {
+		super();
+		this.conn = conn;
+	}
+
+	@Override
+	protected void doJob() {
+		NetMsgFactory factory = conn.getFactory();
+
+		// Make any/all msg possible
+		factory.parseBuffer();
+	
+		// get and route.
+		AbstractNetMsg msg = factory.getMsg();
+		while (msg != null) {
+			
+			// Conditionally check to see if origin is set.
+			if (msg.getOrigin() == null) {
+				Logger.warn(msg.getClass().getSimpleName() + " had a NULL for its 'origin'.");
+				msg.setOrigin(this.conn);
+			}
+
+			 if (msg instanceof engine.net.client.msg.ClientNetMsg) {
+				ConfigManager.handler.handleClientMsg((ClientNetMsg) msg);
+
+			} else {
+				Logger.error("Unrouteable message of type '" + msg.getClass().getSimpleName() + '\'');
+			}
+
+			msg = factory.getMsg();
+		}
+	}
+
+}
diff --git a/src/engine/net/ConnectionMonitorJob.java b/src/engine/net/ConnectionMonitorJob.java
new file mode 100644
index 00000000..06668bc6
--- /dev/null
+++ b/src/engine/net/ConnectionMonitorJob.java
@@ -0,0 +1,44 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net;
+
+import engine.job.AbstractJob;
+import engine.job.JobScheduler;
+import engine.server.MBServerStatics;
+
+public class ConnectionMonitorJob extends AbstractJob {
+
+    private final AbstractConnectionManager connMan;
+    private byte cnt;
+
+    public ConnectionMonitorJob(AbstractConnectionManager connMan, byte cnt) {
+        super();
+        this.connMan = connMan;
+        this.cnt = cnt;
+        this.cnt++;
+    }
+
+    @Override
+    protected void doJob() {
+
+        if (this.cnt >= 5) {
+            this.connMan.auditSocketChannelToConnectionMap();
+            this.cnt = 0;
+        } else
+            this.connMan.auditSocketChannelToConnectionMap();
+
+        // Self Sustain
+        ConnectionMonitorJob cmj = new ConnectionMonitorJob(this.connMan, cnt);
+        JobScheduler.getInstance().scheduleJob(cmj, MBServerStatics.TIMEOUT_CHECKS_TIMER_MS);
+
+        this.setCompletionStatus(JobCompletionStatus.SUCCESS);
+    }
+
+}
diff --git a/src/engine/net/Dispatch.java b/src/engine/net/Dispatch.java
new file mode 100644
index 00000000..d095f08b
--- /dev/null
+++ b/src/engine/net/Dispatch.java
@@ -0,0 +1,74 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+
+
+
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+
+package engine.net;
+
+import engine.objects.PlayerCharacter;
+
+import java.util.concurrent.ConcurrentLinkedQueue;
+
+import static engine.net.MessageDispatcher.itemPoolSize;
+
+/**
+ * Data class holds a message and a distribution list
+ */
+
+public class Dispatch {
+
+    private static final ConcurrentLinkedQueue<Dispatch> dispatchPool = new ConcurrentLinkedQueue<>();
+            
+    public  PlayerCharacter player;
+    public  AbstractNetMsg msg;
+    
+        public Dispatch(PlayerCharacter player, AbstractNetMsg msg) {
+        this.player = player;
+        this.msg = msg; 
+}
+        
+        public void reset() {
+            this.player = null;
+            this.msg = null;
+        }
+        
+        public static Dispatch borrow(PlayerCharacter player, AbstractNetMsg msg) {
+
+            Dispatch dispatch;
+            
+            dispatch = dispatchPool.poll();
+
+            if (dispatch == null) {
+                dispatch = new Dispatch(player, msg);
+            }      else {
+            	dispatch.player = player;
+            	dispatch.msg = msg;
+                itemPoolSize.decrement();
+            }
+            
+            return dispatch;
+        }
+        
+        public void release() {
+            this.reset();
+            dispatchPool.add(this);
+            itemPoolSize.increment();
+        }
+}
diff --git a/src/engine/net/DispatchMessage.java b/src/engine/net/DispatchMessage.java
new file mode 100644
index 00000000..626aac47
--- /dev/null
+++ b/src/engine/net/DispatchMessage.java
@@ -0,0 +1,312 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net;
+
+import engine.Enum.DispatchChannel;
+import engine.Enum.GameObjectType;
+import engine.InterestManagement.WorldGrid;
+import engine.gameManager.SessionManager;
+import engine.math.Vector3fImmutable;
+import engine.net.client.ClientConnection;
+import engine.objects.AbstractWorldObject;
+import engine.objects.Item;
+import engine.objects.PlayerCharacter;
+import engine.server.MBServerStatics;
+import org.pmw.tinylog.Logger;
+
+import java.util.HashSet;
+
+import static engine.net.MessageDispatcher.dispatchCount;
+import static engine.net.MessageDispatcher.maxRecipients;
+
+/*
+ * Dispatch Message is the main interface to Magicbane's threaded
+ * asynch message delivery system.
+ */
+
+public class DispatchMessage {
+
+	public static void startMessagePump() {
+
+		Thread messageDispatcher;
+		messageDispatcher = new Thread(new MessageDispatcher());
+
+		messageDispatcher.setName("MessageDispatcher");
+		messageDispatcher.start();
+	}
+
+
+	public static void sendToAllInRange(AbstractWorldObject obj,
+			AbstractNetMsg msg){
+
+		if (obj == null)
+			return;
+
+		if (obj.getObjectType() ==  GameObjectType.PlayerCharacter || obj.getObjectType() ==  GameObjectType.Mob || obj.getObjectType() == GameObjectType.NPC || obj.getObjectType() ==  GameObjectType.Corpse)
+			dispatchMsgToInterestArea(obj, msg, DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, true, false);
+		else
+			dispatchMsgToInterestArea(obj ,msg, DispatchChannel.PRIMARY, MBServerStatics.STRUCTURE_LOAD_RANGE, false, false);
+
+	}
+
+	// Dispatches a message to a playercharacter's interest area
+	// Method includes handling of exclusion rules for visibility, self, etc.
+
+	public static void dispatchMsgToInterestArea(AbstractWorldObject sourceObject, AbstractNetMsg msg, DispatchChannel dispatchChannel, int interestRange, boolean sendToSelf, boolean useIgnore) {
+
+		Dispatch messageDispatch;
+		HashSet<AbstractWorldObject> gridList;
+		PlayerCharacter gridPlayer;
+		AbstractWorldObject dispatchSource;
+		PlayerCharacter sourcePlayer = null;
+		long recipientCount = 0;
+
+		if (sourceObject == null)
+			return;
+
+		// If the source of the message is a structure, item or player
+		// setup our method variables accordingly.
+
+		switch (sourceObject.getObjectType()) {
+		case Item:
+			dispatchSource = (AbstractWorldObject) ((Item) sourceObject).getOwner();
+			break;
+		case PlayerCharacter:
+			dispatchSource = sourceObject;
+			sourcePlayer = (PlayerCharacter)sourceObject;
+			if (sourcePlayer.getClientConnection() != null && sendToSelf){
+				Dispatch dispatch = Dispatch.borrow(sourcePlayer, msg);
+				DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.PRIMARY);
+			}
+
+
+			break;
+		default:
+			dispatchSource = sourceObject;
+		}
+
+		gridList = WorldGrid.getObjectsInRangePartial(dispatchSource.getLoc(), interestRange, MBServerStatics.MASK_PLAYER);
+
+		for (AbstractWorldObject gridObject : gridList) {
+
+			gridPlayer = (PlayerCharacter)gridObject;
+
+			// Apply filter options if source of dispatch is a player
+
+			if ((dispatchSource.getObjectType() == GameObjectType.PlayerCharacter) &&
+					(sourcePlayer != null)) {
+
+				if (gridPlayer.getObjectUUID() == sourcePlayer.getObjectUUID())
+					continue;
+
+				if ((useIgnore == true) && (gridPlayer.isIgnoringPlayer(sourcePlayer) == true))
+					continue;
+
+				if(gridPlayer.canSee(sourcePlayer) == false)
+					continue;
+			}
+
+			messageDispatch = Dispatch.borrow(gridPlayer, msg);
+			MessageDispatcher.send(messageDispatch, dispatchChannel);
+			recipientCount++;
+		}
+
+		// Update metrics
+
+		if (recipientCount > maxRecipients[dispatchChannel.getChannelID()])
+			maxRecipients[dispatchChannel.getChannelID()] = recipientCount;
+
+		dispatchCount[dispatchChannel.getChannelID()].increment();
+	}
+
+	public static void dispatchMsgToInterestArea(Vector3fImmutable targetLoc,AbstractWorldObject sourceObject, AbstractNetMsg msg, DispatchChannel dispatchChannel, int interestRange, boolean sendToSelf, boolean useIgnore) {
+
+		Dispatch messageDispatch;
+		HashSet<AbstractWorldObject> gridList;
+		PlayerCharacter gridPlayer;
+		AbstractWorldObject dispatchSource;
+		PlayerCharacter sourcePlayer = null;
+		long recipientCount = 0;
+
+		if (sourceObject == null)
+			return;
+
+		// If the source of the message is a structure, item or player
+		// setup our method variables accordingly.
+
+		switch (sourceObject.getObjectType()) {
+		case Item:
+			dispatchSource = (AbstractWorldObject) ((Item) sourceObject).getOwner();
+			break;
+		case PlayerCharacter:
+			dispatchSource = sourceObject;
+			sourcePlayer = (PlayerCharacter)sourceObject;
+
+			if (sourcePlayer.getClientConnection() != null && sendToSelf){
+				Dispatch dispatch = Dispatch.borrow(sourcePlayer, msg);
+				DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.PRIMARY);
+			}
+
+
+			break;
+		default:
+			dispatchSource = sourceObject;
+		}
+
+		gridList = WorldGrid.getObjectsInRangePartial(targetLoc, interestRange, MBServerStatics.MASK_PLAYER);
+
+		for (AbstractWorldObject gridObject : gridList) {
+
+			gridPlayer = (PlayerCharacter)gridObject;
+
+			// Apply filter options if source of dispatch is a player
+
+			if ((dispatchSource.getObjectType() == GameObjectType.PlayerCharacter) &&
+					(sourcePlayer != null)) {
+
+				if (gridPlayer.getObjectUUID() == sourcePlayer.getObjectUUID())
+					continue;
+
+				if ((useIgnore == true) && (gridPlayer.isIgnoringPlayer(sourcePlayer) == true))
+					continue;
+
+				if(gridPlayer.canSee(sourcePlayer) == false)
+					continue;
+			}
+
+			messageDispatch = Dispatch.borrow(gridPlayer, msg);
+			MessageDispatcher.send(messageDispatch, dispatchChannel);
+			recipientCount++;
+		}
+
+		// Update metrics
+
+		if (recipientCount > maxRecipients[dispatchChannel.getChannelID()])
+			maxRecipients[dispatchChannel.getChannelID()] = recipientCount;
+
+		dispatchCount[dispatchChannel.getChannelID()].increment();
+	}
+
+	// Sends a message to all players in the game
+
+	public static void dispatchMsgToAll(AbstractNetMsg msg) {
+
+		Dispatch messageDispatch;
+		long recipientCount = 0;
+
+		// Send message to nobody?  No thanks!
+
+		if (SessionManager.getAllActivePlayerCharacters().isEmpty())
+			return;
+
+		// Messages to all we will default to the secondary dispatch
+		// delivery channel.  They are generally large, or inconsequential.
+
+		for (PlayerCharacter player : SessionManager.getAllActivePlayerCharacters()) {
+			messageDispatch = Dispatch.borrow(player, msg);
+			MessageDispatcher.send(messageDispatch, DispatchChannel.SECONDARY);
+			recipientCount++;
+		}
+
+		// Update metrics
+
+		if (recipientCount > maxRecipients[DispatchChannel.SECONDARY.getChannelID()])
+			maxRecipients[DispatchChannel.SECONDARY.getChannelID()] = recipientCount;
+
+		dispatchCount[DispatchChannel.SECONDARY.getChannelID()].increment();
+
+	}
+	
+	public static void dispatchMsgToAll(PlayerCharacter source, AbstractNetMsg msg, boolean ignore) {
+
+		Dispatch messageDispatch;
+		long recipientCount = 0;
+
+		// Send message to nobody?  No thanks!
+
+		if (SessionManager.getAllActivePlayerCharacters().isEmpty())
+			return;
+
+		// Messages to all we will default to the secondary dispatch
+		// delivery channel.  They are generally large, or inconsequential.
+
+		for (PlayerCharacter player : SessionManager.getAllActivePlayerCharacters()) {
+			
+			if (ignore && player.isIgnoringPlayer(source))
+				continue;
+			
+			messageDispatch = Dispatch.borrow(player, msg);
+			MessageDispatcher.send(messageDispatch, DispatchChannel.SECONDARY);
+			recipientCount++;
+		}
+
+		// Update metrics
+
+		if (recipientCount > maxRecipients[DispatchChannel.SECONDARY.getChannelID()])
+			maxRecipients[DispatchChannel.SECONDARY.getChannelID()] = recipientCount;
+
+		dispatchCount[DispatchChannel.SECONDARY.getChannelID()].increment();
+
+	}
+
+	// Sends a message to an arbitrary distribution list
+
+	public static void dispatchMsgDispatch(Dispatch messageDispatch, DispatchChannel dispatchChannel) {
+		
+		if (messageDispatch == null){
+			Logger.info("DISPATCH Null for DispatchMessage!");
+			return;
+		}
+
+		// No need to serialize an empty list
+
+		if (messageDispatch.player == null){
+			Logger.info("Player Null for Dispatch!");
+			messageDispatch.release();
+			return;
+		}
+			
+
+		MessageDispatcher.send(messageDispatch, dispatchChannel);
+
+		dispatchCount[dispatchChannel.getChannelID()].increment();
+
+	}
+
+	protected static void serializeDispatch(Dispatch messageDispatch) {
+		ClientConnection connection;
+
+		if (messageDispatch.player == null){
+			Logger.info("Player null in serializeDispatch");
+			messageDispatch.release();
+			return;
+		}
+
+		connection = messageDispatch.player.getClientConnection();
+
+		if ((connection == null) || (connection.isConnected() == false)) {
+			messageDispatch.release();
+			return;
+		}
+
+		if (messageDispatch.msg == null) {
+			Logger.error("null message sent to " + messageDispatch.player.getName());
+			messageDispatch.release();
+			return;
+		}
+
+		if (!connection.sendMsg(messageDispatch.msg))
+			Logger.error(messageDispatch.msg.getProtocolMsg() + " failed sending to " + messageDispatch.player.getName());
+
+		messageDispatch.release();
+	}
+
+
+}
diff --git a/src/engine/net/ItemProductionManager.java b/src/engine/net/ItemProductionManager.java
new file mode 100644
index 00000000..adf379d2
--- /dev/null
+++ b/src/engine/net/ItemProductionManager.java
@@ -0,0 +1,155 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+
+
+
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net;
+
+import engine.Enum.DispatchChannel;
+import engine.objects.ProducedItem;
+import org.pmw.tinylog.Logger;
+
+import java.util.HashSet;
+import java.util.concurrent.DelayQueue;
+import java.util.concurrent.atomic.LongAdder;
+
+/**
+ * Thread blocks until MagicBane dispatch messages are
+ * enqueued then processes them in FIFO order. The collection
+ * is thread safe.
+ * 
+ * Any large messages not time sensitive such as load object
+ * sent to more than a single individual should be spawned
+ * individually on a DispatchMessageThread.
+ */
+
+
+
+public enum ItemProductionManager implements Runnable {
+
+	ITEMPRODUCTIONMANAGER;
+	
+	
+    // Instance variables
+    
+    private ItemQueue itemQueue;
+
+    private long nextFailedItemAudit;
+    
+    // Class variables
+
+    @SuppressWarnings("unchecked") // Cannot have arrays of generics in java.
+    private static final DelayQueue<ItemQueue> producedQueue = new DelayQueue<>();
+
+    
+    // Performance metrics
+    
+    public static  volatile long[] messageCount = new long[DispatchChannel.values().length];
+    public static  LongAdder[] dispatchCount = new LongAdder[DispatchChannel.values().length];
+    public static  volatile long[] maxRecipients = new long[DispatchChannel.values().length];
+    public static  LongAdder dispatchPoolSize = new LongAdder();
+    
+    public static HashSet<ProducedItem> FailedItems = new HashSet<>();
+    
+    public Thread itemProductionThread = null;
+    
+    // Thread constructor
+    
+    
+    public void startMessagePump() {
+
+		itemProductionThread = new Thread(this);
+		itemProductionThread.setName("ItemProductionManager");
+		
+	}
+    
+    public void initialize(){
+    	itemProductionThread.start();
+    }
+    
+
+    public static void send(ItemQueue item) {
+    
+        // Don't queue up empty dispatches!
+        
+        if (item == null)
+        	return;
+        
+        producedQueue.add(item);
+        
+    }
+        
+    @Override
+    public void run() {
+        
+        
+        while (true) {
+            try {												
+                
+                    this.itemQueue = producedQueue.take();
+                    
+                    if (this.itemQueue == null){	
+                    	return;
+                    }
+                    
+                    if (this.itemQueue != null) {
+                    if (this.itemQueue.item == null){
+                    	this.itemQueue.release();
+                   	return;
+                   	}
+                    
+                    
+                   boolean created = this.itemQueue.item.finishProduction();
+                   
+                   if (!created)
+                	   FailedItems.add(this.itemQueue.item);
+                   
+                   this.itemQueue.release();
+                   
+                
+                }
+                
+              
+            } catch (Exception e) {
+                Logger.error(e);
+            }
+
+        }
+    }
+
+    public static String getNetstatString() {
+
+        String outString = null;
+        String newLine = System.getProperty("line.separator");
+        outString = "[LUA_NETSTA()]" + newLine;
+        outString += "poolSize: " + dispatchPoolSize.longValue() + '\n';
+        
+        for (DispatchChannel dispatchChannel : DispatchChannel.values()) {
+            
+            outString += "Channel: " + dispatchChannel.name() + '\n';
+            outString += "Dispatches: " + dispatchCount[dispatchChannel.getChannelID()].longValue()+ '\n';
+            outString += "Messages: " + messageCount[dispatchChannel.getChannelID()] + '\n';
+            outString += "maxRecipients: " + maxRecipients[dispatchChannel.getChannelID()] + '\n';
+        }
+        return outString;
+    }
+            
+        // For Debugging:
+        //Logger.error("MessageDispatcher", messageDispatch.msg.getOpcodeAsString() + " sent to " + messageDispatch.playerList.size() + " players");
+    }
diff --git a/src/engine/net/ItemQueue.java b/src/engine/net/ItemQueue.java
new file mode 100644
index 00000000..ee0b160f
--- /dev/null
+++ b/src/engine/net/ItemQueue.java
@@ -0,0 +1,99 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+
+
+
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+
+package engine.net;
+
+import engine.objects.ProducedItem;
+
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.Delayed;
+import java.util.concurrent.TimeUnit;
+
+import static engine.net.MessageDispatcher.itemPoolSize;
+
+/**
+ * Data class holds a message and a distribution list
+ */
+
+public class ItemQueue implements Delayed {
+
+    private static final ConcurrentLinkedQueue<ItemQueue> itemPool = new ConcurrentLinkedQueue<>();
+            
+    public ProducedItem item;
+    public long delayTime;
+    
+    
+        public ItemQueue(ProducedItem item, long delayTime) {
+        	this.item = item;
+        	this.delayTime = System.currentTimeMillis() + delayTime;
+        
+}
+        
+        public void reset() {
+            this.item = null;
+            this.delayTime = 0;
+        }
+        
+        public static ItemQueue borrow(ProducedItem item, long delayTime) {
+
+            ItemQueue itemQueue;
+            
+            itemQueue = itemPool.poll();
+
+            if (itemQueue == null) {
+            itemQueue = new ItemQueue(item, delayTime);
+            }      else {
+            	itemQueue.item = item;
+            	itemQueue.delayTime = System.currentTimeMillis() + delayTime;
+                itemPoolSize.decrement();
+            }
+            
+            return itemQueue;
+        }
+        
+        public void release() {
+            this.reset();
+            itemPool.add(this);
+            itemPoolSize.increment();
+        }
+
+		@Override
+		public int compareTo(Delayed another) {
+			   ItemQueue anotherTask = (ItemQueue) another;
+			   
+		        if (this.delayTime < anotherTask.delayTime) {
+		            return -1;
+		        }
+		 
+		        if (this.delayTime > anotherTask.delayTime) {
+		            return 1;
+		        }
+		 
+		        return 0;
+		}
+
+		@Override
+		public long getDelay(TimeUnit unit) {
+			 long difference = delayTime - System.currentTimeMillis();
+		        return unit.convert(difference, TimeUnit.MILLISECONDS);
+		}
+}
diff --git a/src/engine/net/MessageDispatcher.java b/src/engine/net/MessageDispatcher.java
new file mode 100644
index 00000000..123a3730
--- /dev/null
+++ b/src/engine/net/MessageDispatcher.java
@@ -0,0 +1,145 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+
+
+
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net;
+
+import engine.Enum.DispatchChannel;
+import org.pmw.tinylog.Logger;
+
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.atomic.LongAdder;
+import java.util.regex.Pattern;
+
+/**
+ * Thread blocks until MagicBane dispatch messages are
+ * enqueued then processes them in FIFO order. The collection
+ * is thread safe.
+ * 
+ * Any large messages not time sensitive such as load object
+ * sent to more than a single individual should be spawned
+ * individually on a DispatchMessageThread.
+ */
+
+public class MessageDispatcher implements Runnable {
+
+    // Instance variables
+    
+    private Dispatch messageDispatch;
+    private final Pattern filterPattern; // Unused, but just in case
+    
+    // Class variables
+
+    @SuppressWarnings("unchecked") // Cannot have arrays of generics in java.
+    private static final ConcurrentLinkedQueue<Dispatch>[] _messageQueue = new ConcurrentLinkedQueue[DispatchChannel.values().length];
+
+    private static final LinkedBlockingQueue<Boolean> _blockingQueue = new LinkedBlockingQueue<>();
+    
+    // Performance metrics
+    
+    public static  volatile long[] messageCount = new long[DispatchChannel.values().length];
+    public static  LongAdder[] dispatchCount = new LongAdder[DispatchChannel.values().length];
+    public static  volatile long[] maxRecipients = new long[DispatchChannel.values().length];
+    public static  LongAdder itemPoolSize = new LongAdder();
+    
+    // Thread constructor
+    
+    public MessageDispatcher() {
+
+        // Create new FIFO queues for this network thread
+        
+        for (DispatchChannel dispatchChannel : DispatchChannel.values()) {
+            _messageQueue[dispatchChannel.getChannelID()] = new ConcurrentLinkedQueue<>();
+            dispatchCount[dispatchChannel.getChannelID()] = new LongAdder();
+        }
+            
+        filterPattern = Pattern.compile("[^\\p{ASCII}]");
+        Logger.info( " Dispatcher thread has started!");
+    
+    }
+
+    public static void send(Dispatch messageDispatch, DispatchChannel dispatchChannel) {
+    
+        // Don't queue up empty dispatches!
+        
+        if (messageDispatch.player == null)
+            return;
+        
+        _messageQueue[dispatchChannel.getChannelID()].add(messageDispatch);
+        _blockingQueue.add(true);
+        
+        // Update performance metrics
+        
+        messageCount[dispatchChannel.getChannelID()]++;
+        
+    }
+        
+    @Override
+    public void run() {
+        
+        boolean shouldBlock;
+        
+        while (true) {
+            try {
+                
+                shouldBlock = true;
+                
+                for (DispatchChannel dispatchChannel : DispatchChannel.values()) {
+                    
+                    this.messageDispatch = _messageQueue[dispatchChannel.getChannelID()].poll();
+                    
+                    if (this.messageDispatch != null) {
+                    DispatchMessage.serializeDispatch(this.messageDispatch);
+                    shouldBlock = false;
+                }
+                
+                }
+                
+                if (shouldBlock == true)
+                    shouldBlock = _blockingQueue.take();
+
+            } catch (Exception e) {
+                Logger.error(e);
+            }
+
+        }
+    }
+
+    public static String getNetstatString() {
+
+        String outString = null;
+        String newLine = System.getProperty("line.separator");
+        outString = "[LUA_NETSTA()]" + newLine;
+        outString += "poolSize: " + itemPoolSize.longValue() + '\n';
+        
+        for (DispatchChannel dispatchChannel : DispatchChannel.values()) {
+            
+            outString += "Channel: " + dispatchChannel.name() + '\n';
+            outString += "Dispatches: " + dispatchCount[dispatchChannel.getChannelID()].longValue()+ '\n';
+            outString += "Messages: " + messageCount[dispatchChannel.getChannelID()] + '\n';
+            outString += "maxRecipients: " + maxRecipients[dispatchChannel.getChannelID()] + '\n';
+        }
+        return outString;
+    }
+            
+        // For Debugging:
+        //Logger.error("MessageDispatcher", messageDispatch.msg.getOpcodeAsString() + " sent to " + messageDispatch.playerList.size() + " players");
+    }
diff --git a/src/engine/net/NetMsgFactory.java b/src/engine/net/NetMsgFactory.java
new file mode 100644
index 00000000..7d205f3a
--- /dev/null
+++ b/src/engine/net/NetMsgFactory.java
@@ -0,0 +1,425 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net;
+
+import engine.exception.FactoryBuildException;
+import engine.gameManager.ChatManager;
+import engine.net.client.ClientConnection;
+import engine.net.client.Protocol;
+import engine.objects.PlayerCharacter;
+import engine.server.MBServerStatics;
+import org.joda.time.DateTime;
+import org.pmw.tinylog.Logger;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.HashMap;
+
+public class NetMsgFactory {
+
+	// NetMsg Opcode to Constructor List
+	private static final HashMap<Integer, Constructor> netMsgDefinitions = new HashMap<>();
+
+	// Standardize the error strings
+	private static String ALL_GOOD_JUST_NOT_ENOUGH_BYTES = "Not enough Bytes";
+	private static String DESERIALIZATION_FAILURE = "Deserialization Failure";
+	private static String UNIMPLEMENTED_OPCODE = "Unimplemented Opcode";
+	private static String UNKNOWN_OPCODE = "Unknown Opcode";
+
+	protected ByteBuffer internalBuffer;
+	private final ArrayList<AbstractNetMsg> msgOutbox;
+
+	private boolean enableFloodControl;
+	private boolean bypassFloodControl; // temp bypass
+	private boolean floodControlTripped;
+
+	private static final int FLOOD_CONTROL_TRIP_SETPOINT = 1000;
+	private int badOpcodeCount;
+	private final AbstractConnection owner;
+	private int lastMsgPosition = 0;
+
+	public NetMsgFactory(AbstractConnection origin, boolean enableFloodControl) {
+		this.internalBuffer = Network.byteBufferPool.getBuffer(18); //256k buffer
+		
+		this.bypassFloodControl = false;
+		this.msgOutbox = new ArrayList<>();
+		this.enableFloodControl = enableFloodControl;
+		this.floodControlTripped = false;
+		this.owner = origin;
+	}
+
+	public NetMsgFactory(AbstractConnection origin) {
+		this(origin, true);
+	}
+
+	public final void addData(byte[] ba) {
+		// Dont use prefab BB's here, since sizeof(ba) is unknown.
+		ByteBuffer bb = ByteBuffer.wrap(ba);
+		bb.position(bb.capacity());
+		this.addData(bb);
+	}
+
+	public final void addData(ByteBuffer newData) {
+		synchronized (this.internalBuffer) {
+
+			int newCapacity = this.internalBuffer.position() + newData.position();
+
+			if (newCapacity >= this.internalBuffer.capacity()) {
+				//Resize!!!!
+				Logger.warn(
+						"Bytebuffer is being be Resized.");
+
+				//Get a newer, bigger BB
+				ByteBuffer newBB = Network.byteBufferPool.getBufferToFit((int) (newCapacity * 1.5));
+
+				//Copy old data in
+				this.internalBuffer.flip();
+				newBB.put(this.internalBuffer);
+
+				//Get a handle on old BB
+				ByteBuffer oldBB = this.internalBuffer;
+				
+				//install new BB
+				this.internalBuffer = newBB;
+
+				//Return old BB
+				Network.byteBufferPool.putBuffer(oldBB);
+			}
+
+			synchronized (newData) {
+				// Copy over the data.
+				newData.flip();
+
+				try {
+					this.internalBuffer.put(newData);
+				} catch (Exception e) {
+					Logger.error( e.toString());
+					// TODO figure out how to handle this error.
+				}
+			}
+		}
+	}
+
+	public void parseBuffer() {
+		// Check flood control first
+		if (this.floodControlTripped)
+			// this.conn.disconnect();
+			return;
+
+		// MBServer.jobMan.submitJob(new ParseBufferJob(this));
+		this._parseBuffer();
+	}
+
+	/**
+	 * This function makes a copy of the current internal byte buffer and loads
+	 * the copy into a ByteBufferReader. It is copied so that the Factory can
+	 * continue to accumulate data on the internal buffer from the
+	 * socketChannels. The ByteBufferReader is then used in an attempt to build
+	 * an AbstractNetMsg subclass based on protocolMsg. If a message is successfully
+	 * built, the bytes used are removed from the Factory's internal byte
+	 * buffer.
+	 *
+	 * @return
+	 * @throws Exception
+	 */
+	protected void _parseBuffer() {
+		synchronized (this.internalBuffer) {
+			while (this.internalBuffer.position() > 0) {
+				// Check flood control first
+				if (this.floodControlTripped)
+					break;
+
+				ByteBufferReader reader = null;
+
+				// Check to see if the minimum amount of data is here:
+				if (this.internalBuffer.position() < 4)
+					// nothing wrong, just not enough info yet.
+					break;
+
+				// copy internal buffer into a reader
+				reader = new ByteBufferReader(this.internalBuffer, false);
+
+				// Reset the limit to the capacity
+				this.internalBuffer.limit(this.internalBuffer.capacity());
+
+				try {
+					AbstractNetMsg msg = this.tryBuild(owner, reader);
+
+					// error, null messages are being returned on unhandled
+					// opcodes
+					// for some reason
+					if (msg == null)
+						throw new FactoryBuildException(UNIMPLEMENTED_OPCODE);
+						
+					
+					
+					if (owner.getClass().equals(ClientConnection.class)){
+						ClientConnection client = (ClientConnection)owner;
+						client.setLastOpcode(msg.getProtocolMsg().opcode);
+					}
+					
+					
+					
+
+					//					Logger.debug("Adding a " + msg.getSimpleClassName()
+					//							+ " to the outbox.");
+					this.addMsgToOutBox(msg);
+
+					this.dropLeadingBytesFromBuffer(reader.position());
+					this.bypassFloodControl = false;
+
+				} catch (FactoryBuildException e) {
+					String error = e.getMessage();
+					int readerPos = reader.position();
+
+					if (error.equals(ALL_GOOD_JUST_NOT_ENOUGH_BYTES)){
+						break;
+					}
+						// no worries, just break.
+						
+					else if (error.equals(DESERIALIZATION_FAILURE)) {
+						// Lop readerPos bytes off the buffer.
+						this.dropLeadingBytesFromBuffer(readerPos);
+
+						// Lets bypass flood control for now.
+						this.bypassFloodControl = true;
+						continue;
+
+					} else if (error.equals(UNIMPLEMENTED_OPCODE)) {
+						
+						if (owner.lastProtocol != null && owner.lastProtocol.constructor == null){
+							this.dropLeadingBytesFromBuffer(readerPos);
+							this.bypassFloodControl = true;
+							continue;
+						}
+							
+						// Lop readerPos bytes off the buffer.
+						if (reader.position() >= 4)
+						reader.position(reader.position() - 4);
+						int newPosition = Protocol.FindNextValidOpcode(reader);
+						this.dropLeadingBytesFromBuffer(newPosition);
+						// Lets bypass flood control for now.
+						this.bypassFloodControl = true;
+
+						continue;
+
+					} else if (error.equals(UNKNOWN_OPCODE)) {
+						
+						if (owner.lastProtocol != null && owner.lastProtocol.constructor == null){
+							this.dropLeadingBytesFromBuffer(readerPos);
+							this.bypassFloodControl = true;
+							continue;
+						}
+						// We don't know what this is or how long, so dump the
+						// first
+						// byte and try again
+						if (reader.position() >= 4)
+						reader.position(reader.position() - 4);
+						int newPosition = Protocol.FindNextValidOpcode(reader);
+						this.dropLeadingBytesFromBuffer(newPosition);
+						// Lets bypass flood control for now.
+						this.bypassFloodControl = true;
+
+						continue;
+					}
+				} catch (Exception e) {
+					// TODO FIX THIS!!!!
+//					Logger.error( e);
+
+				}// end catch
+
+			} // end while loop
+		}
+	}// end fn
+
+	public AbstractNetMsg tryBuild(AbstractConnection origin,
+			ByteBufferReader reader) throws FactoryBuildException {
+		try {
+
+			// Get the protocolMsg
+			int opcode = reader.getInt();
+			// String ocHex = StringUtils.toHexString(protocolMsg);
+
+			if (MBServerStatics.PRINT_INCOMING_OPCODES)
+				try {
+					Logger.info( "Incoming protocolMsg: "
+							+ Protocol.getByOpcode(opcode).name() + " " + Integer.toHexString(opcode) + ", size: " + reader.getBb().limit() + "; " + getByteArray(reader));
+				} catch (Exception e) {
+					Logger.error( e);
+				}
+
+			return NetMsgFactory.getNewInstanceOf(opcode, origin, reader);
+
+		} catch (BufferUnderflowException e) {
+			// This is okay. it indicates that we recognized the protocolMsg, but
+			// there isn't enough information in
+			// the reader to complete the NetMsg deserialization
+			throw new FactoryBuildException(ALL_GOOD_JUST_NOT_ENOUGH_BYTES);
+
+		}
+	}
+
+	public static String getByteArray(ByteBufferReader reader) {
+		String ret = "";
+		if (reader == null)
+			return ret;
+
+		ByteBuffer bb = reader.getBb();
+		if (bb == null)
+			return ret;
+
+		int length = bb.limit(); // - bb.position();
+		ByteBuffer temp = bb.duplicate();
+		temp.position(bb.limit());
+		temp.flip();
+		for (int i = 0; i < length; i++) {
+			ret += Integer.toString((temp.get() & 0xff) + 0x100, 16).substring(1).toUpperCase();
+		}
+		return ret;
+	}
+
+	private void incrBadOpcodeCount() {
+		// keeping this a nested if for Troubleshooting/clarity
+		if (this.enableFloodControl == true)
+			if (this.bypassFloodControl == false) {
+				++this.badOpcodeCount;
+
+
+
+				if (this.badOpcodeCount >= FLOOD_CONTROL_TRIP_SETPOINT){
+					if (this.owner != null){
+						if (this.owner instanceof ClientConnection){
+							ClientConnection client = (ClientConnection) this.owner;
+							if (client.getPlayerCharacter() != null){
+								ChatManager.chatSystemError(client.getPlayerCharacter(),"TRIPPED Flood Control! PLEASE RELOG!");
+								Logger.info( client.getPlayerCharacter().getName() + " Tripped Flood Control!" + this.badOpcodeCount);
+							}
+
+						}
+					}
+					this.floodControlTripped = true;
+				}else{
+					if (this.owner != null){
+						if (this.owner instanceof ClientConnection){
+							ClientConnection client = (ClientConnection) this.owner;
+							if (client.getPlayerCharacter() != null){
+								ChatManager.chatSystemError(client.getPlayerCharacter(),"Client sending bad messages. bad message Count " + this.badOpcodeCount);
+								Logger.info( client.getPlayerCharacter().getName() + " has been caught sending bad opcodes. Bad Opcode Count " + this.badOpcodeCount);
+							}
+
+						}
+
+
+					}
+				}
+
+			}
+	}
+
+	protected final void dropLeadingBytesFromBuffer(int numberOfBytes) {
+		this.internalBuffer.limit(this.internalBuffer.position());
+		this.internalBuffer.position(numberOfBytes);
+		this.internalBuffer.compact(); // Compact
+	}
+
+	protected boolean addMsgToOutBox(AbstractNetMsg msg) {
+		synchronized (this.msgOutbox) {
+			return msgOutbox.add(msg);
+		}
+	}
+
+	public AbstractNetMsg getMsg() {
+		synchronized (this.msgOutbox) {
+			if (this.msgOutbox.isEmpty())
+				return null;
+			return msgOutbox.remove(0);
+		}
+	}
+
+	public boolean hasMsg() {
+		synchronized (this.msgOutbox) {
+			return !msgOutbox.isEmpty();
+		}
+	}
+
+	public boolean hasData() {
+		synchronized (this.internalBuffer) {
+			return (this.internalBuffer.position() != 0);
+		}
+	}
+
+	@SuppressWarnings("unchecked")
+	private static AbstractNetMsg getNewInstanceOf(int opcode,
+			AbstractConnection origin, ByteBufferReader reader) {
+		try {
+
+			Protocol protocolMsg = Protocol.getByOpcode(opcode);
+
+			if (protocolMsg == Protocol.NONE){
+				
+				String errorString = DateTime.now().toString() + origin.lastProtocol.name();
+				
+				int errorCode = errorString.hashCode();
+				
+				
+				if (origin instanceof ClientConnection){
+					PlayerCharacter player = ((ClientConnection)origin).getPlayerCharacter();
+					if (player != null){
+//						if (MBServerStatics.worldServerName.equals("Grief"))
+						Logger.error("Invalid protocol msg for player " + player.getFirstName() + " : " + opcode + " lastopcode: " + origin.lastProtocol.name() + " Error Code : " + errorCode);
+					}else
+						Logger.error("Invalid protocol msg  : " + opcode + " lastopcode: " + origin.lastProtocol.name() + " Error Code : " + errorCode);
+
+				}
+					
+				return null;
+			}
+			origin.lastProtocol = protocolMsg;
+
+			if (protocolMsg.constructor == null){
+				return null;
+			}
+			
+			
+
+			Constructor<AbstractNetMsg> constructor = protocolMsg.constructor;
+
+			if (constructor == null)
+				return null;
+
+			Object[] myArgs = new Object[2];
+			myArgs[0] = origin;
+			myArgs[1] = reader;
+
+			Object object = constructor.newInstance(myArgs);
+
+			if (object instanceof engine.net.AbstractNetMsg)
+				return (AbstractNetMsg) object;
+
+		} catch (IllegalAccessException | IllegalArgumentException | InstantiationException | ExceptionInInitializerError e) {
+			Logger.error( e);
+
+		} catch (InvocationTargetException e) {
+			if (e.getCause() != null
+					&& e.getCause().getClass() == BufferUnderflowException.class)
+				throw new BufferUnderflowException();
+			Logger.error(e);
+		}
+		return null;
+	}
+
+	public ByteBuffer getInternalBuffer() {
+		return internalBuffer;
+	}
+
+}
diff --git a/src/engine/net/NetMsgHandler.java b/src/engine/net/NetMsgHandler.java
new file mode 100644
index 00000000..b208ffd3
--- /dev/null
+++ b/src/engine/net/NetMsgHandler.java
@@ -0,0 +1,17 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net;
+
+import engine.net.client.msg.ClientNetMsg;
+
+public interface NetMsgHandler {
+
+    public abstract boolean handleClientMsg(ClientNetMsg msg);
+}
diff --git a/src/engine/net/NetMsgStat.java b/src/engine/net/NetMsgStat.java
new file mode 100644
index 00000000..f54e3b29
--- /dev/null
+++ b/src/engine/net/NetMsgStat.java
@@ -0,0 +1,77 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net;
+
+import engine.net.client.Protocol;
+
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+
+public class NetMsgStat {
+
+    private final Protocol protocolMsg;
+    private final AtomicLong total = new AtomicLong();
+    private final AtomicLong count = new AtomicLong();
+    private final AtomicInteger average = new AtomicInteger();
+    private final AtomicInteger max = new AtomicInteger();
+    private final AtomicInteger countUnderAverage = new AtomicInteger();
+    private final AtomicInteger countOverAverage = new AtomicInteger();
+    private final AtomicInteger countOverMax = new AtomicInteger();
+
+    public NetMsgStat(Protocol protocolMsg, int startSize) {
+        
+        if (startSize < 10)
+            startSize = 10;
+        
+        if (startSize > 30)
+            startSize = 30;
+
+        this.protocolMsg = protocolMsg;
+        this.total.set(startSize);
+        this.count.set(1L);
+        this.average.set(10);
+        this.max.set(startSize);
+        this.countUnderAverage.set(0);
+        this.countOverAverage.set(0);
+        this.countOverMax.set(0);
+    }
+
+    public void updateStat(int i) {
+        this.total.addAndGet(i);
+        this.count.incrementAndGet();
+
+        int avg = (int) (this.total.get() / this.count.get());
+        if (avg < 0)
+            avg = 0;
+        else if (avg > 30)
+            avg = 30;
+        else
+            this.average.set(avg);
+
+        if (this.max.get() < i)
+            this.max.set(i);
+        
+        if (i <= avg)
+            this.countUnderAverage.incrementAndGet();
+        else if (i < this.max.get())
+            this.countOverAverage.incrementAndGet();
+        else
+            this.countOverMax.incrementAndGet();
+    }
+
+    public Protocol getOpcode() {
+        return this.protocolMsg;
+    }
+
+    public int getMax() {
+        return this.max.get();
+    }
+
+}
diff --git a/src/engine/net/Network.java b/src/engine/net/Network.java
new file mode 100644
index 00000000..7bef5e73
--- /dev/null
+++ b/src/engine/net/Network.java
@@ -0,0 +1,60 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net;
+
+import engine.pooling.MultisizeByteBufferPool;
+
+import java.nio.ByteBuffer;
+
+public class Network {
+
+    public static final int INITIAL_SOCKET_BUFFER_SIZE = 128 * 1024;
+    public static final int INITIAL_BYTEBUFFER_POOL_SIZE = 256;
+
+    public static final MultisizeByteBufferPool byteBufferPool = new MultisizeByteBufferPool();
+
+    public static void init() {
+		//Force a few to be created.
+
+        //Small (2^10-15)
+        for (int a = 10; a < 16; ++a) {
+            for (int i = 0; i < 50; ++i) {
+                byteBufferPool.putBuffer(ByteBuffer.allocateDirect(MultisizeByteBufferPool.powersOfTwo[a]));
+            }
+        }
+
+        //standard size (2^16)
+        for (int i = 0; i < 100; ++i) {
+            byteBufferPool.putBuffer(ByteBuffer.allocateDirect(MultisizeByteBufferPool.powersOfTwo[16]));
+        }
+
+        //Large (2^17)
+        for (int i = 0; i < 50; ++i) {
+            byteBufferPool.putBuffer(ByteBuffer.allocateDirect(MultisizeByteBufferPool.powersOfTwo[17]));
+        }
+
+        // NetMsgFactory size (2^18)
+        for (int i = 0; i < 64; ++i) {
+            byteBufferPool.putBuffer(ByteBuffer
+                    .allocateDirect(MultisizeByteBufferPool.powersOfTwo[18]));
+        }
+
+        //Very Large (2^19)
+        for (int i = 0; i < 25; ++i) {
+            byteBufferPool.putBuffer(ByteBuffer.allocateDirect(MultisizeByteBufferPool.powersOfTwo[19]));
+        }
+
+        //Very Large (2^20)
+        for (int i = 0; i < 10; ++i) {
+            byteBufferPool.putBuffer(ByteBuffer.allocateDirect(MultisizeByteBufferPool.powersOfTwo[20]));
+        }
+    }
+
+}
diff --git a/src/engine/net/client/ClientAuthenticator.java b/src/engine/net/client/ClientAuthenticator.java
new file mode 100644
index 00000000..dd36f503
--- /dev/null
+++ b/src/engine/net/client/ClientAuthenticator.java
@@ -0,0 +1,381 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client;
+
+import engine.net.AbstractConnection;
+import engine.server.MBServerStatics;
+import engine.util.ByteUtils;
+import org.pmw.tinylog.Logger;
+
+import javax.crypto.Cipher;
+import javax.crypto.KeyAgreement;
+import javax.crypto.ShortBufferException;
+import javax.crypto.interfaces.DHPublicKey;
+import javax.crypto.spec.DHParameterSpec;
+import javax.crypto.spec.DHPublicKeySpec;
+import javax.crypto.spec.SecretKeySpec;
+import java.io.IOException;
+import java.math.BigInteger;
+import java.nio.BufferOverflowException;
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.security.*;
+
+public class ClientAuthenticator {
+
+    private final AbstractConnection origin;
+
+    private ByteBuffer buffer = ByteBuffer.allocate(100);
+    private byte[] secretKeyBytes = new byte[16];
+    private SecretKeySpec BFKey;
+
+    private Cipher cipher;
+    private byte[] iVecEnc = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+    private byte[] iVecDec = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+    private int iVecEncOffset = 0;
+    private int iVecDecOffset = 0;
+    private int totalRead = 0;
+    private KeyFactory keyFactory;
+    private DHParameterSpec dhParamSpec;
+    private KeyPairGenerator keyPairGen;
+    private KeyAgreement keyAgree;
+    private boolean initialized = false;
+    private boolean keyInit = false;
+    private byte[] secretKey;
+    private byte[] serverPublicKey;
+
+    public ClientAuthenticator(AbstractConnection origin) {
+        super();
+        this.origin = origin;
+        try {
+            // init the resuable Crypto Stuff.
+            this.keyFactory = KeyFactory.getInstance("DH");
+            this.dhParamSpec = new DHParameterSpec(ClientAuthenticator.P, ClientAuthenticator.G);
+
+            this.keyPairGen = KeyPairGenerator.getInstance("DH");
+            this.keyPairGen.initialize(this.dhParamSpec);
+            this.keyAgree = KeyAgreement.getInstance("DH");
+
+        } catch (NoSuchAlgorithmException | InvalidAlgorithmParameterException e) {
+            Logger.error("NoSuchAlgorithmException  " + e.getMessage());
+            this.keyInit = false;
+            return;
+        }
+
+        this.keyInit = true;
+    }
+
+    private void calcKeys(AbstractConnection origin, byte[] clientPublicKeyBytes) {
+
+        try {
+			// get the forwarded client public key, in byte[] form.
+
+            // Convert client public key to a BigInteger
+            BigInteger clientPublicKeyBI = new BigInteger(1, clientPublicKeyBytes);
+
+            // convert the client's Public Key to a DHPublicKey object
+            DHPublicKeySpec dhKeySpec = new DHPublicKeySpec(clientPublicKeyBI, ClientAuthenticator.P, ClientAuthenticator.G);
+            DHPublicKey clientPublicKey = (DHPublicKey) this.keyFactory.generatePublic(dhKeySpec);
+
+            // Now calculate the server's PublicKey
+            byte[] serverPublicKeyBytes = new byte[96];
+            boolean invalid = true;
+
+            int tryCnt = 1;
+            while (invalid) {
+                KeyPair keyPair = keyPairGen.generateKeyPair();
+                this.keyAgree = KeyAgreement.getInstance("DH");
+                this.keyAgree.init(keyPair.getPrivate());
+                DHPublicKey serverPublicKey = (DHPublicKey) keyPair.getPublic();
+
+                String hex = serverPublicKey.getY().toString(16);
+
+                if (hex.length() == 192) {
+                    invalid = false;
+                    serverPublicKeyBytes = ByteUtils.stringHexToByteArray(hex);
+                }
+                if (tryCnt >= 5)
+                    // Give java 4 tries to get a Public key of valid length.
+                    throw new Exception("Not able to generate a valid length public key");
+                ++tryCnt;
+            }
+
+            // Calculate shared DH Secret Key
+            keyAgree.doPhase(clientPublicKey, true);
+            this.secretKey = keyAgree.generateSecret();
+            this.serverPublicKey = serverPublicKeyBytes;
+
+            // Now we have the server's publicKey and the common secretKey
+        } catch (Exception e) {
+            origin.disconnect();
+            Logger.error(e);
+        }
+
+    }
+
+    public synchronized int initialize(AbstractConnection origin) {
+        long startTime = System.currentTimeMillis();
+        int read = -1;
+
+        // Read the data from connections socket channel
+        try {
+            read = origin.getSocketChannel().read(this.buffer);
+        } catch (IOException e) {
+        	if (e.getLocalizedMessage() != null && !e.getLocalizedMessage().equals(MBServerStatics.EXISTING_CONNECTION_CLOSED) && !e.getLocalizedMessage().equals(MBServerStatics.RESET_BY_PEER))
+        		  Logger.error(e);
+                  origin.disconnect();
+        	return 0;
+        }
+
+        if (read == -1) {
+        	 Logger.info("EOF on Socket Channel, Disconnecting " + origin.getLocalAddressAndPortAsString());
+            origin.disconnect();
+            return read;
+        }
+
+        this.totalRead += read;
+
+        if (this.totalRead > 100)
+            Logger.error( "Possible Spam warning: "
+                    + origin.getSocketChannel().socket().toString());
+
+        // Not all arrived yet, so wait for more
+        if (this.totalRead < 100)
+            return read;
+
+        this.buffer.flip();
+        this.buffer.getInt(); // get the length first & throw away value.
+
+        byte[] PeerPubKeyEnc = new byte[96];
+        this.buffer.get(PeerPubKeyEnc);
+
+        this.calcKeys(origin, PeerPubKeyEnc);
+
+        try {
+            byte[] sharedSecret = this.secretKey;
+            byte[] PubKeyEnc = this.serverPublicKey;
+
+            // Cut DH SecretKey down to 16 bytes
+            System.arraycopy(sharedSecret, 0, secretKeyBytes, 0, 16);
+
+            // Calculate Blowfish Secret Key and make ciphers streams.
+            this.BFKey = new SecretKeySpec(this.secretKeyBytes, "Blowfish");
+
+			// Initialize cipher and Ivecs.
+            // Ivecs must be run through the cipher once
+            // to prep the cipher for cfb mode.
+            this.cipher = Cipher.getInstance("Blowfish/ECB/NoPadding");
+            this.cipher.init(Cipher.ENCRYPT_MODE, this.BFKey);
+            this.cipher.update(this.iVecEnc, 0, 8, this.iVecEnc, 0);
+            this.cipher.update(this.iVecDec, 0, 8, this.iVecDec, 0);
+
+            // Send public key to peer
+            byte[] pubKeyLen = {0x00, 0x00, 0x00, 0x60}; // hex for 96
+            ByteBuffer bb = ByteBuffer.wrap(pubKeyLen);
+            bb.position(bb.limit());
+            origin.sendBB(bb);
+            bb = ByteBuffer.wrap(PubKeyEnc);
+            bb.position(bb.limit());
+            origin.sendBB(bb);
+        } catch (Exception e) {
+            Logger.error(e);
+            origin.disconnect();
+            return read;
+        }
+        /*
+         * //Send Secret Key to Login Server if (PortalServer.ServerType ==
+         * "Login") { byte[] keyinfo = new byte[20]; keyinfo[0] = 0x11;
+         * keyinfo[1] = 0x11; keyinfo[2] = 0x11; keyinfo[3] = 0x11; for (int i =
+         * 0; i < 16; i++) keyinfo[i + 4] = ShortSharedSecret[i];
+         * PortalPair.HandleOutput(keyinfo, 20); }
+         */
+        this.initialized = true;
+
+//        long endTime = System.currentTimeMillis();
+//
+//        long time = endTime - startTime;
+//       // Logger.debug("", "Authenticator took " + time + " ms to initialize.");
+
+        return read;
+    }
+
+    public synchronized void encrypt(final ByteBuffer dataIn, final ByteBuffer dataOut) {
+        try {
+            // Assume that if position != 0 that we need to flip.
+            if (dataIn.position() != 0)
+                dataIn.flip();
+
+            int count = dataIn.limit();
+
+            //Line up the iVecEncOffset.. fall through is intentional
+            if (iVecEncOffset != 0)
+                if ((this.iVecEncOffset + dataIn.limit()) < 8) {
+					//This handles cases where the net msg + offset won't reach 8 bytes total
+                    //prevents BufferUnderflowException in small net messages. -
+                    int newEncOffset = this.iVecEncOffset + dataIn.limit();
+                    for (int i = this.iVecEncOffset; i < newEncOffset; i++) {
+                        this.iVecEnc[i] = (byte) (dataIn.get() ^ this.iVecEnc[i]);
+                        dataOut.put(this.iVecEnc[i]);
+                    }
+                    this.iVecEncOffset = newEncOffset;
+                    return;
+                } else
+                    switch (iVecEncOffset) {
+                        case 1:
+                            this.iVecEnc[1] = (byte) (dataIn.get() ^ this.iVecEnc[1]);
+                            dataOut.put(this.iVecEnc[1]);
+                        case 2:
+                            this.iVecEnc[2] = (byte) (dataIn.get() ^ this.iVecEnc[2]);
+                            dataOut.put(this.iVecEnc[2]);
+                        case 3:
+                            this.iVecEnc[3] = (byte) (dataIn.get() ^ this.iVecEnc[3]);
+                            dataOut.put(this.iVecEnc[3]);
+                        case 4:
+                            this.iVecEnc[4] = (byte) (dataIn.get() ^ this.iVecEnc[4]);
+                            dataOut.put(this.iVecEnc[4]);
+                        case 5:
+                            this.iVecEnc[5] = (byte) (dataIn.get() ^ this.iVecEnc[5]);
+                            dataOut.put(this.iVecEnc[5]);
+                        case 6:
+                            this.iVecEnc[6] = (byte) (dataIn.get() ^ this.iVecEnc[6]);
+                            dataOut.put(this.iVecEnc[6]);
+                        case 7:
+                            this.iVecEnc[7] = (byte) (dataIn.get() ^ this.iVecEnc[7]);
+                            dataOut.put(this.iVecEnc[7]);
+                            count -= (8 - iVecEncOffset);
+                            this.iVecEncOffset = 0;
+                            this.cipher.update(this.iVecEnc, 0, 8, this.iVecEnc, 0);
+                    }
+
+            //Main loop - unrolled x8
+            int loopCount = (count) >> 3;
+            for (int i = 0; i < loopCount; i++) {
+
+                this.iVecEnc[0] = (byte) (dataIn.get() ^ this.iVecEnc[0]);
+                this.iVecEnc[1] = (byte) (dataIn.get() ^ this.iVecEnc[1]);
+                this.iVecEnc[2] = (byte) (dataIn.get() ^ this.iVecEnc[2]);
+                this.iVecEnc[3] = (byte) (dataIn.get() ^ this.iVecEnc[3]);
+                this.iVecEnc[4] = (byte) (dataIn.get() ^ this.iVecEnc[4]);
+                this.iVecEnc[5] = (byte) (dataIn.get() ^ this.iVecEnc[5]);
+                this.iVecEnc[6] = (byte) (dataIn.get() ^ this.iVecEnc[6]);
+                this.iVecEnc[7] = (byte) (dataIn.get() ^ this.iVecEnc[7]);
+
+                dataOut.put(this.iVecEnc[0]);
+                dataOut.put(this.iVecEnc[1]);
+                dataOut.put(this.iVecEnc[2]);
+                dataOut.put(this.iVecEnc[3]);
+                dataOut.put(this.iVecEnc[4]);
+                dataOut.put(this.iVecEnc[5]);
+                dataOut.put(this.iVecEnc[6]);
+                dataOut.put(this.iVecEnc[7]);
+                this.cipher.update(this.iVecEnc, 0, 8, this.iVecEnc, 0);
+            }
+
+            //Resync the iVecEncOffset to handle the remainder..
+            this.iVecEncOffset = count % 8;
+            for (int i = 0; i < iVecEncOffset; i++) {
+                this.iVecEnc[i] = (byte) (dataIn.get() ^ this.iVecEnc[i]);
+                dataOut.put(this.iVecEnc[i]);
+            }
+        } catch (BufferUnderflowException e) {
+            if (dataIn != null && dataOut != null)
+                Logger.warn("Encrypt Error: (in)" + dataIn.toString() + " :: (out)" + dataOut.toString());
+            Logger.error("ClientAuth.encrypt() -> Underflow" + e);
+        } catch (BufferOverflowException e) {
+            if (dataIn != null && dataOut != null)
+                Logger.warn("Encrypt Error: (in)" + dataIn.toString() + " :: (out)" + dataOut.toString());
+            Logger.error("ClientAuth.encrypt() -> Overflow" + e);
+        } catch (Exception e) {
+            Logger.error("ClientAuth.encrypt()" + e);
+        }
+    }
+
+    public synchronized void decrypt(ByteBuffer dataIn, ByteBuffer dataOut) {
+        try { // get lock
+            synchronized (dataIn) { // TODO is this lock needed?
+
+                // Assume that if position != 0 that we need to flip.
+                if (dataIn.position() != 0)
+                    dataIn.flip();
+
+                byte encryptedByte;
+                byte decryptedByte;
+
+                for (int i = 0; i < dataIn.limit(); ++i) {
+
+                    // Get byte out of ByteBuffer
+                    encryptedByte = dataIn.get();
+
+                    // XOR it against the iVEC
+                    decryptedByte = (byte) (encryptedByte ^ this.iVecDec[this.iVecDecOffset]);
+
+                    // put the decrypted byte into the outgoing ByteBuffer
+                    dataOut.put(decryptedByte);
+
+                    // store the encrypted byte back into the iVEC
+                    this.iVecDec[this.iVecDecOffset] = encryptedByte;
+
+                    // Increment ivecOffset
+                    this.iVecDecOffset++;
+
+                    // Check to see if iVecOffset is at MAX. If so, reset
+                    if (this.iVecDecOffset > 7) {
+                        try {
+                            this.cipher.update(this.iVecDec, 0, 8,
+                                    this.iVecDec, 0);
+                        } catch (ShortBufferException e) {
+                            // suck up this error
+                            Logger.error(e);
+                        }
+                        this.iVecDecOffset = 0;
+                    }
+                }
+               
+               
+            }
+        } catch (Exception e) {
+            Logger.error("ClientAuth.decrypt()" + e);
+        }
+    }
+
+    private void initiateKey(byte[] clientPublicKeyBytes) {
+
+    }
+
+    /**
+     * @return the secretKeyBytes
+     */
+    public byte[] getSecretKeyBytes() {
+        return secretKeyBytes;
+    }
+
+    /**
+     * @return the initialized
+     */
+    public boolean initialized() {
+        return initialized;
+    }
+
+    private static final byte P_Bytes[] = {(byte) 0xFB, (byte) 0x46, (byte) 0x56, (byte) 0xB4, (byte) 0xBE, (byte) 0x81, (byte) 0xA4,
+        (byte) 0x2C, (byte) 0x37, (byte) 0xC4, (byte) 0xA2, (byte) 0x61, (byte) 0x4A, (byte) 0xAC, (byte) 0x65, (byte) 0x90,
+        (byte) 0x31, (byte) 0xB6, (byte) 0x83, (byte) 0x26, (byte) 0x63, (byte) 0x94, (byte) 0x08, (byte) 0x95, (byte) 0x56,
+        (byte) 0x8D, (byte) 0x5E, (byte) 0xBF, (byte) 0x94, (byte) 0x10, (byte) 0x5A, (byte) 0x37, (byte) 0xB6, (byte) 0x82,
+        (byte) 0x1A, (byte) 0x75, (byte) 0x2B, (byte) 0xF1, (byte) 0x94, (byte) 0xB7, (byte) 0x7E, (byte) 0x56, (byte) 0xC6,
+        (byte) 0xD1, (byte) 0xF5, (byte) 0x18, (byte) 0xE1, (byte) 0xA5, (byte) 0x13, (byte) 0x9E, (byte) 0xC1, (byte) 0x85,
+        (byte) 0x98, (byte) 0xB7, (byte) 0x32, (byte) 0xDB, (byte) 0x38, (byte) 0x09, (byte) 0x1A, (byte) 0xF8, (byte) 0x5C,
+        (byte) 0xDA, (byte) 0x4F, (byte) 0x9F, (byte) 0x67, (byte) 0x93, (byte) 0x72, (byte) 0x8F, (byte) 0x75, (byte) 0x4F,
+        (byte) 0x0B, (byte) 0xBD, (byte) 0x69, (byte) 0x61, (byte) 0x97, (byte) 0x1F, (byte) 0xEE, (byte) 0xFB, (byte) 0x5B,
+        (byte) 0xB0, (byte) 0x85, (byte) 0xC4, (byte) 0x27, (byte) 0x7E, (byte) 0x41, (byte) 0x42, (byte) 0xC2, (byte) 0xF1,
+        (byte) 0xDA, (byte) 0x64, (byte) 0x8F, (byte) 0x4E, (byte) 0x28, (byte) 0xFD, (byte) 0x2A, (byte) 0x63};
+
+    private static final BigInteger P = new BigInteger(1, P_Bytes);
+    private static final BigInteger G = BigInteger.valueOf(5);
+
+}
diff --git a/src/engine/net/client/ClientConnection.java b/src/engine/net/client/ClientConnection.java
new file mode 100644
index 00000000..0ca889be
--- /dev/null
+++ b/src/engine/net/client/ClientConnection.java
@@ -0,0 +1,354 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+
+package engine.net.client;
+
+import engine.Enum;
+import engine.gameManager.ConfigManager;
+import engine.gameManager.SessionManager;
+import engine.job.JobScheduler;
+import engine.jobs.DisconnectJob;
+import engine.net.AbstractConnection;
+import engine.net.AbstractNetMsg;
+import engine.net.Network;
+import engine.net.client.msg.ClientNetMsg;
+import engine.net.client.msg.login.LoginErrorMsg;
+import engine.objects.Account;
+import engine.objects.PlayerCharacter;
+import engine.server.MBServerStatics;
+import engine.session.SessionID;
+import org.pmw.tinylog.Logger;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.ClosedChannelException;
+import java.nio.channels.NotYetConnectedException;
+import java.nio.channels.SocketChannel;
+import java.util.concurrent.locks.ReentrantLock;
+
+public class ClientConnection extends AbstractConnection {
+
+	// Enumeration of a message's origin for logging purposes
+	private enum MessageSource {
+
+		SOURCE_CLIENT,
+		SOURCE_SERVER
+	}
+
+	private byte cryptoInitTries = 0;
+	protected SessionID sessionID = null;
+	private final ClientAuthenticator crypto;
+	private final String clientIpAddress;
+	public String machineID;
+	public long guildtreespam = 0;
+	public long ordernpcspam = 0;
+	public ReentrantLock trainLock = new ReentrantLock();
+	public ReentrantLock sellLock = new ReentrantLock();
+	public ReentrantLock buyLock = new ReentrantLock();
+
+	public boolean desyncDebug = false;
+	
+	public byte[] lastByteBuffer;
+
+	public ClientConnection(ClientConnectionManager connMan,
+			SocketChannel sockChan) {
+		super(connMan, sockChan, true);
+		this.crypto = new ClientAuthenticator(this);
+
+		this.clientIpAddress = sockChan.socket().getRemoteSocketAddress()
+				.toString().replace("/", "").split(":")[0];
+	}
+
+	@Override
+	protected boolean _sendMsg(AbstractNetMsg msg) {
+		try {
+			msg.setOrigin(this);
+			ByteBuffer bb = msg.serialize();
+
+			// Application protocol logging toggled via
+			// DevCmd: netdebug on|off
+
+			if (MBServerStatics.DEBUG_PROTOCOL)
+				applicationProtocolLogger(msg, MessageSource.SOURCE_SERVER);
+
+			boolean retval = this.sendBB(bb);
+			Network.byteBufferPool.putBuffer(bb);//return here.
+
+			return retval;
+
+		} catch (Exception e) { // Catch-all
+			Logger.error(e);
+			return false;
+		}
+	}
+
+	/**
+	 * Sending a NetMsg to the client involves NOT including a dataLen parameter
+	 * and also involves encrypting the data.
+	 *
+	 */
+	@Override
+	protected boolean _sendBB(ByteBuffer bb) {
+		boolean useCrypto = this.crypto.initialized();
+		boolean retVal;
+
+		// Logger.debug("useCrypto: " + useCrypto + ". bb.cap(): " +
+		// bb.capacity());
+		if (useCrypto == false)
+			retVal = super._sendBB(bb);
+		else {
+			if (bb == null)
+				Logger.error("Incoming bb is null");
+			ByteBuffer encrypted = Network.byteBufferPool.getBufferToFit(bb.capacity());
+			if (encrypted == null)
+				Logger.error("Encrypted bb is null");
+			this.crypto.encrypt(bb, encrypted);
+			retVal = super._sendBB(encrypted);
+		}
+
+		return retVal;
+	}
+
+	/**
+	 * Receiving data from a client involves the initial Crypto Key Exchange,
+	 * waiting for a complete NetMsg to arrive using an accumulation factory and
+	 * decrypting the data.
+	 */
+	//FIXME the int return value on this means nothing!  Clean it up!
+	@Override
+	protected int read() {
+
+		if (readLock.tryLock())
+			try {
+
+				// First and foremost, check to see if we the Crypto is initted yet
+				if (!this.crypto.initialized())
+					this.crypto.initialize(this);
+
+				if (!this.crypto.initialized()) {
+					++this.cryptoInitTries;
+					if (this.cryptoInitTries >= MBServerStatics.MAX_CRYPTO_INIT_TRIES) {
+						Logger.info("Failed to initialize after "
+								+ MBServerStatics.MAX_CRYPTO_INIT_TRIES
+								+ " tries. Disconnecting.");
+						this.disconnect();
+					}
+					return 0;
+				}
+
+				// check to see if SessionID == null;
+				if (this.sessionID == null)
+					this.sessionID = new SessionID(this.crypto.getSecretKeyBytes());
+
+				// Get ByteBuffers out of pool.
+				ByteBuffer bb = Network.byteBufferPool.getBuffer(16);
+				ByteBuffer decrypted = Network.byteBufferPool.getBuffer(16);
+				// ByteBuffer bb = ByteBuffer.allocate(1024 * 4);
+
+				int totalBytesRead = 0;
+				int lastRead = 0;
+				do {
+					try {
+						bb.clear();
+						decrypted.clear();
+							lastRead = this.sockChan.read(bb);
+						// On EOF on the SocketChannel, disconnect.
+						if (lastRead <= -1) {
+							this.disconnect();
+							break;
+						}
+
+						if (lastRead == 0)
+							continue;
+
+						// ByteBuffer decrypted = ByteBuffer.allocate(lastRead);
+						this.crypto.decrypt(bb, decrypted);
+						this.factory.addData(decrypted);
+						
+						
+						this.checkInternalFactory();
+
+						totalBytesRead += lastRead;
+					
+
+					} catch (NotYetConnectedException e) {
+						Logger.error(e.getLocalizedMessage());
+						totalBytesRead = -1; // Set error retVal
+						break;
+
+					} catch (ClosedChannelException e) {
+						// TODO Should a closed channel be logged or just cleaned up?
+						// this.logEXCEPTION(e);
+						this.disconnect();
+						totalBytesRead = -1; // Set error retVal
+						break;
+
+					} catch (IOException e) {
+						if ( e.getLocalizedMessage() != null && (!e.getLocalizedMessage().equals(MBServerStatics.EXISTING_CONNECTION_CLOSED) && !e.getLocalizedMessage().equals(MBServerStatics.RESET_BY_PEER))){
+							Logger.info("Error Reading message opcode " + this.lastOpcode);
+							Logger.error(e);
+						}
+						this.disconnect();
+						totalBytesRead = -1; // Set error retVal
+						break;
+
+					} catch (Exception e){
+						Logger.info("Error Reading message opcode " + this.lastOpcode);
+						Logger.error(e);
+						totalBytesRead = -1; // Set error retVal
+						this.disconnect();
+						break;
+					}
+				}
+
+				while (lastRead > 0);
+
+				Network.byteBufferPool.putBuffer(bb);
+				Network.byteBufferPool.putBuffer(decrypted);
+
+				return totalBytesRead;
+
+			} finally {
+				readLock.unlock();
+			}
+		else {
+			Logger.debug("Another thread already has a read lock! Skipping.");
+			return 0;
+		}
+	}
+
+	@Override
+	public void disconnect() {
+		super.disconnect();
+		try {
+
+			if (ConfigManager.serverType.equals(Enum.ServerType.WORLDSERVER))
+				ConfigManager.worldServer.removeClient(this);
+			else
+				ConfigManager.loginServer.removeClient(this);
+
+			// TODO There has to be a more direct way to do this...
+			SessionManager.remSession(
+					SessionManager.getSession(sessionID));
+		} catch (NullPointerException e) {
+			Logger
+			.error(
+					"Tried to remove improperly initialized session. Skipping." +
+					e);
+		}
+	}
+	
+	public void forceDisconnect() {
+		super.disconnect();
+	}
+
+	/*
+	 * Getters n setters
+	 */
+
+	public SessionID getSessionID() {
+		return sessionID;
+	}
+
+	public byte[] getSecretKeyBytes() {
+		return this.crypto.getSecretKeyBytes();
+	}
+
+	/*
+	 * Convenience getters for SessionManager
+	 */
+	public Account getAccount() {
+		return SessionManager.getAccount(this);
+	}
+
+	public PlayerCharacter getPlayerCharacter() {
+		return SessionManager.getPlayerCharacter(this);
+	}
+
+	@Override
+	public boolean handleClientMsg(ClientNetMsg msg) {
+
+		Protocol protocolMsg = msg.getProtocolMsg();
+
+		switch (protocolMsg) {
+			case KEEPALIVESERVERCLIENT:
+			this.setLastKeepAliveTime();
+			break;
+			//           case ClientOpcodes.OpenVault:
+				//           case ClientOpcodes.Random:
+			//           case ClientOpcodes.DoorTryOpen:
+			//           case ClientOpcodes.SetSelectedObect:
+			//           case ClientOpcodes.MoveObjectToContainer:
+			//            case ClientOpcodes.ToggleSitStand:
+			//            case ClientOpcodes.SocialChannel:
+			//            case ClientOpcodes.OpenFriendsCondemnList:
+			case SELLOBJECT:
+			this.setLastMsgTime();
+			break;
+			case MOVETOPOINT:
+			case ARCCOMBATMODEATTACKING:
+			this.setLastMsgTime();
+			break;
+		default:
+			this.setLastMsgTime();
+			break;
+		}
+
+		// Application protocol logging toggled via
+		// DevCmd: netdebug on|off
+
+		if (MBServerStatics.DEBUG_PROTOCOL)
+			applicationProtocolLogger(msg, MessageSource.SOURCE_CLIENT);
+
+		return ConfigManager.handler.handleClientMsg(msg); // *** Refactor : Null check then call
+	}
+	// Method logs detailed information about application
+	// protocol traffic.  Toggled at runtime via the
+	// DevCmd netdebug on|off
+
+	private void applicationProtocolLogger(AbstractNetMsg msg, MessageSource origin) {
+
+		String outString = "";
+		PlayerCharacter tempPlayer = null;
+
+		// Log the protocolMsg
+		if (origin == MessageSource.SOURCE_CLIENT)
+			outString = " Incoming protocolMsg: ";
+		else
+			outString = " Outgoing protocolMsg: ";
+
+		Logger.info(outString
+				+ Integer.toHexString(msg.getProtocolMsg().opcode)
+				+ '/' + msg.getProtocolMsg());
+
+		// Dump message contents using reflection
+		tempPlayer = this.getPlayerCharacter();
+		outString = "";
+		outString += (tempPlayer == null) ? "PlayerUnknown" : tempPlayer.getFirstName() + ' '
+				+ msg.toString();
+		Logger.info(outString);
+	}
+
+	public void kickToLogin(int errCode, String message) {
+
+		LoginErrorMsg lom = new LoginErrorMsg(errCode, message);
+
+		if (!sendMsg(lom))
+			Logger.error("Failed to send  message"); // TODO Do we just accept this failure to send Msg?
+
+		DisconnectJob dj = new DisconnectJob(this);
+		JobScheduler.getInstance().scheduleJob(dj, 250);
+
+	}
+
+	public final String getClientIpAddress() {
+		return this.clientIpAddress;
+	}
+}
diff --git a/src/engine/net/client/ClientConnectionManager.java b/src/engine/net/client/ClientConnectionManager.java
new file mode 100644
index 00000000..0ccf5fb8
--- /dev/null
+++ b/src/engine/net/client/ClientConnectionManager.java
@@ -0,0 +1,35 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client;
+
+import engine.net.AbstractConnection;
+import engine.net.AbstractConnectionManager;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.nio.channels.SocketChannel;
+
+public class ClientConnectionManager extends AbstractConnectionManager {
+
+    public ClientConnectionManager(String threadName, InetAddress hostAddress, int port)
+            throws IOException {
+        super(threadName, hostAddress, port);
+    }
+
+    @Override
+    protected AbstractConnection getNewIncomingConnection(SocketChannel sockChan) {
+        return new ClientConnection(this, sockChan);
+    }
+
+    @Override
+    protected AbstractConnection getNewOutgoingConnection(SocketChannel sockChan) {
+        return new ClientConnection(this, sockChan);
+    }
+}
diff --git a/src/engine/net/client/ClientMessagePump.java b/src/engine/net/client/ClientMessagePump.java
new file mode 100644
index 00000000..07ede49c
--- /dev/null
+++ b/src/engine/net/client/ClientMessagePump.java
@@ -0,0 +1,2340 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+
+package engine.net.client;
+
+import engine.Enum.*;
+import engine.InterestManagement.WorldGrid;
+import engine.ai.MobileFSM.STATE;
+import engine.exception.MsgSendException;
+import engine.gameManager.*;
+import engine.job.JobContainer;
+import engine.job.JobScheduler;
+import engine.jobs.RefreshGroupJob;
+import engine.jobs.StuckJob;
+import engine.math.Vector3fImmutable;
+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 java.util.ArrayList;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ThreadLocalRandom;
+
+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;
+	}
+
+	/*
+	 *  Incoming client protocol message are processed here
+	 */
+
+	@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) {
+				case SETSELECTEDOBECT:
+				ClientMessagePump.targetObject((TargetObjectMsg) msg, origin);
+				break;
+				case CITYDATA:
+				ClientMessagePump.MapData(s, origin);
+				break;
+
+				/*
+				 * 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 UPDATESTATE:
+				UpdateStateMsg rwss = (UpdateStateMsg) msg;
+				runWalkSitStand(rwss, origin);
+				break;
+			case ACTIVATECHARTER:
+				UseCharterMsg ucm = (UseCharterMsg) msg;
+				ucm.setUnknown02(1);
+				ucm.configure();
+				Dispatch dispatch = Dispatch.borrow(origin.getPlayerCharacter(), ucm);
+				DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
+				break;
+			case CHECKUNIQUEGUILD:
+				break;
+			case CREATEPETITION:
+				break;
+			case CANCELGUILDCREATION:
+				break;
+			case LEAVEREQUEST:
+				origin.disconnect();
+				break;
+			case POWER:
+				PowersManager.usePower((PerformActionMsg) msg, origin, false);
+				break;
+			case REQUESTMELEEATTACK:
+				CombatManager.setAttackTarget((AttackCmdMsg) msg, origin);
+				break;
+			case READYTOENTER:
+				break;
+			case OPENVAULT:
+				break;
+			case WHOREQUEST:
+				WhoRequest((WhoRequestMsg) msg, origin);
+				break;
+			case CLIENTADMINCOMMAND:
+				ChatManager.HandleClientAdminCmd((ClientAdminCommandMsg) msg, origin);
+				break;
+			case SOCIALCHANNEL:
+				social((SocialMsg) msg, origin);
+				break;
+			case COMBATMODE:
+				CombatManager.toggleCombat((ToggleCombatMsg) msg, origin);
+				break;
+			case ARCCOMBATMODEATTACKING:
+				CombatManager.toggleCombat((SetCombatModeMsg) msg, origin);
+				break;
+			case MODIFYGUILDSTATE:
+				ToggleLfgRecruitingMsg tlrm = (ToggleLfgRecruitingMsg) msg;
+				toggleLfgRecruiting(tlrm, origin);
+				break;
+			case TOGGLESITSTAND:
+				ToggleSitStandMsg tssm = (ToggleSitStandMsg) msg;
+				toggleSitStand(tssm, origin);
+				break;
+			case GUILDTREESTATUS:
+				GuildTreeStatusMsg((GuildTreeStatusMsg) msg, origin);
+				break;
+			case CUSTOMERPETITION:
+				Logger.info("CCR Petition received: " + msg.toString());
+				// TODO need to send something back to client
+				// TODO what to do with petition?
+				break;
+			case IGNORE:
+				((IgnoreMsg) msg).handleRequest(origin);
+				break;
+			case UNEQUIP:
+				TransferItemFromEquipToInventory((TransferItemFromEquipToInventoryMsg) msg, origin);
+				break;
+			case EQUIP:
+				TransferItemFromInventoryToEquip((TransferItemFromInventoryToEquipMsg) msg, origin);
+				break;
+			case DELETEOBJECT:
+				DeleteItem((DeleteItemMsg) msg, 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 REQUESTCONTENTS:
+				lootWindowRequest((LootWindowRequestMsg) msg, origin);
+				break;
+			case MOVEOBJECTTOCONTAINER:
+				loot((LootMsg) msg, origin);
+				break;
+			case SHOWCOMBATINFO:
+				show((ShowMsg) msg, origin);
+				break;
+			case TRANSFERITEMTOBANK:
+				transferItemFromInventoryToBank((TransferItemFromInventoryToBankMsg) msg, origin);
+				break;
+			case TRANSFERITEMFROMBANK:
+				transferItemFromBankToInventory((TransferItemFromBankToInventoryMsg) msg, origin);
+				break;
+			case TRANSFERITEMFROMVAULTTOINVENTORY:
+				transferItemFromVaultToInventory((TransferItemFromVaultToInventoryMsg) msg, origin);
+				break;
+			case ITEMTOVAULT:
+				transferItemFromInventoryToVault((TransferItemFromInventoryToVaultMsg) msg, origin);
+				break;
+			case TRANSFERGOLDFROMVAULTTOINVENTORY:
+				transferGoldFromVaultToInventory((TransferGoldFromVaultToInventoryMsg) msg, origin);
+				break;
+			case GOLDTOVAULT:
+				transferGoldFromInventoryToVault((TransferGoldFromInventoryToVaultMsg) 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 VENDORDIALOG:
+				VendorDialogMsg.replyDialog((VendorDialogMsg) msg, origin);
+				break;
+			case ARCMINEWINDOWAVAILABLETIME:
+				MineWindowAvailableTime((ArcMineWindowAvailableTimeMsg) msg, origin);
+				break;
+			case ARCMINEWINDOWCHANGE:
+				MineWindowChange((ArcMineWindowChangeMsg) msg, origin);
+				break;
+			case ARCOWNEDMINESLIST:
+				ListOwnedMines((ArcOwnedMinesListMsg) msg, origin);
+				break;
+			case ARCMINECHANGEPRODUCTION:
+				changeMineProduction((ArcMineChangeProductionMsg) msg, origin);
+				break;
+			case SHOPLIST:
+				openBuyFromNPCWindow((BuyFromNPCWindowMsg) msg, origin);
+				break;
+			case BUYFROMNPC:
+				buyFromNPC((BuyFromNPCMsg) msg, origin);
+				break;
+			case SHOPINFO:
+				openSellToNPCWindow((SellToNPCWindowMsg) msg, origin);
+				break;
+			case SELLOBJECT:
+				sellToNPC((SellToNPCMsg) msg, origin);
+				break;
+			case REPAIROBJECT:
+				Repair((RepairMsg) msg, origin);
+				break;
+			case TRAINERLIST:
+				WorldServer.trainerInfo((TrainerInfoMsg) msg, origin);
+				break;
+			case ARCUNTRAINLIST:
+				WorldServer.refinerScreen((RefinerScreenMsg) msg, origin);
+				break;
+			case TRAINSKILL:
+				TrainMsg.train((TrainMsg) 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:
+				stuck(origin);
+				break;
+			case RANDOM:
+				ClientMessagePump.randomRoll((RandomMsg) msg, origin);
+				break;
+			case ARCPETATTACK:
+				petAttack((PetAttackMsg) msg, origin);
+				break;
+			case ARCPETCMD:
+				petCmd((PetCmdMsg) msg, origin);
+				break;
+			case MANAGENPC:
+				ManageNPCCmd((ManageNPCMsg) msg, origin);
+				break;
+			case ARCPROMPTRECALL:
+				HandlePromptRecall((PromptRecallMsg) 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;
+	}
+
+	// *** Refactor need to figure this out.
+	//     Commented out for some reson or another.
+
+	//TODO what is this used for?
+	private void ManageNPCCmd(ManageNPCMsg msg, ClientConnection origin) {
+
+	}
+
+	private static void MapData(Session s, ClientConnection origin) {
+
+		Dispatch dispatch;
+try{
+	
+
+		if (s == null || origin == null)
+			return;
+
+		PlayerCharacter pc = s.getPlayerCharacter();
+
+		if (pc == null)
+			return;
+boolean updateMine = false;
+boolean updateCity = false;
+
+//do not update Cities and mines everytime you open map. only update them to client when something's changed.
+		long lastRefresh = pc.getTimeStamp("mineupdate");
+		if (lastRefresh <= Mine.getLastChange()){
+			pc.setTimeStamp("mineupdate", System.currentTimeMillis());
+			updateMine = true;
+		}
+				long lastCityRefresh = pc.getTimeStamp("cityUpdate");
+				if (lastCityRefresh <= City.lastCityUpdate){
+					pc.setTimeStamp("cityUpdate", System.currentTimeMillis());
+					updateCity = true;
+				}
+					
+				
+		
+		WorldObjectMsg wom = new WorldObjectMsg(s, false);
+		wom.updateMines(true);
+		wom.updateCities(updateCity);
+		dispatch = Dispatch.borrow(pc, wom);
+		DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
+		//	}
+
+		lastRefresh = pc.getTimeStamp("hotzoneupdate");
+		if (lastRefresh <= WorldServer.getLastHZChange()) {
+			Zone hotzone = ZoneManager.getHotZone();
+			if (hotzone != null) {
+				HotzoneChangeMsg hcm = new HotzoneChangeMsg(hotzone.getObjectType().ordinal(), hotzone.getObjectUUID());
+				dispatch = Dispatch.borrow(pc, hcm);
+				DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
+				pc.setTimeStamp("hotzoneupdate", System.currentTimeMillis() - 100);
+			}
+		}
+
+		WorldRealmMsg wrm = new WorldRealmMsg();
+		dispatch = Dispatch.borrow(pc, wrm);
+		DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
+}catch(Exception e){
+	e.printStackTrace();
+}
+	}
+
+	private static void WhoRequest(WhoRequestMsg msg, ClientConnection origin) {
+
+		// Handle /who request
+		PlayerCharacter pc = origin.getPlayerCharacter();
+
+		if (pc == null)
+			return;
+
+		if (pc.getTimeStamp("WHO") > System.currentTimeMillis()) {
+			ErrorPopupMsg.sendErrorMsg(pc, "Who too fast! Please wait 3 seconds.");
+			return;
+		}
+
+		WhoResponseMsg.HandleResponse(msg.getSet(), msg.getFilterType(), msg.getFilter(), origin);
+		pc.getTimestamps().put("WHO", System.currentTimeMillis() + 3000);
+	}
+
+	private static void runWalkSitStand(UpdateStateMsg msg, ClientConnection origin) throws MsgSendException {
+		PlayerCharacter pc = SessionManager.getPlayerCharacter(origin);
+		if (pc == null)
+			return;
+		
+		pc.update();
+		if (msg.getSpeed() == 2)
+			pc.setWalkMode(false);
+		else
+			pc.setWalkMode(true);
+		DispatchMessage.dispatchMsgToInterestArea(pc, msg, DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, true, false);
+	}
+
+	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 targetObject(TargetObjectMsg msg, ClientConnection origin) {
+		PlayerCharacter pc = SessionManager.getPlayerCharacter(origin);
+		if (pc == null)
+			return;
+
+		// TODO improve this later. hacky way to make sure player ingame is
+		// active.
+
+		if (!pc.isActive())
+			pc.setActive(true);
+
+		pc.setLastTarget(GameObjectType.values()[msg.getTargetType()], msg.getTargetID());
+	}
+
+	private static void social(SocialMsg msg, ClientConnection origin) throws MsgSendException {
+		PlayerCharacter pc = SessionManager.getPlayerCharacter(origin);
+		if (pc == null)
+			return;
+		DispatchMessage.dispatchMsgToInterestArea(pc, msg, DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, true, true);
+	}
+
+	private static void TransferItemFromEquipToInventory(TransferItemFromEquipToInventoryMsg msg, ClientConnection origin) {
+		PlayerCharacter pc = origin.getPlayerCharacter();
+		if (pc == null)
+			return;
+
+		CharacterItemManager itemManager = pc.getCharItemManager();
+		if (itemManager == null)
+			return;
+
+		int slot = msg.getSlotNumber();
+
+		Item i = itemManager.getItemFromEquipped(slot);
+		if (i == null)
+			return;
+
+		if (!itemManager.doesCharOwnThisItem(i.getObjectUUID()))
+			return;
+
+		//dupe check
+		if (!i.validForEquip(origin, pc, itemManager))
+			return;
+
+		if (i.containerType == ItemContainerType.EQUIPPED)
+			itemManager.moveItemToInventory(i);
+
+		int ItemType = i.getObjectType().ordinal();
+		int ItemID = i.getObjectUUID();
+		for (String name : i.getEffects().keySet()) {
+			Effect eff = i.getEffects().get(name);
+			if (eff == null)
+				return;
+			ApplyEffectMsg pum = new ApplyEffectMsg();
+			pum.setEffectID(eff.getEffectToken());
+			pum.setSourceType(pc.getObjectType().ordinal());
+			pum.setSourceID(pc.getObjectUUID());
+			pum.setTargetType(pc.getObjectType().ordinal());
+			pum.setTargetID(pc.getObjectUUID());
+			pum.setNumTrains(eff.getTrains());
+			pum.setUnknown05(1);
+			pum.setUnknown02(2);
+			pum.setUnknown06((byte) 1);
+			pum.setEffectSourceType(ItemType);
+			pum.setEffectSourceID(ItemID);
+			pum.setDuration(-1);
+
+
+			DispatchMessage.dispatchMsgToInterestArea(pc, pum, DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, true, false);;
+
+		}
+		// Update player formulas
+		pc.applyBonuses();
+		DispatchMessage.dispatchMsgToInterestArea(pc, msg, DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, false, false);
+
+	}
+
+	//call this if the transfer fails server side to kick the item back to inventory from equip
+	private void forceTransferFromInventoryToEquip(TransferItemFromEquipToInventoryMsg msg, ClientConnection origin, String reason) {
+		//TODO add this later
+		//PATCHED CODEZZ
+	}
+
+	private static void TransferItemFromInventoryToEquip(TransferItemFromInventoryToEquipMsg msg, ClientConnection origin) {
+		PlayerCharacter pc = origin.getPlayerCharacter();
+		if (pc == null)
+			return;
+
+		CharacterItemManager itemManager = pc.getCharItemManager();
+		if (itemManager == null) {
+			forceTransferFromEquipToInventory(msg, origin, "Can't find your item manager");
+			return;
+		}
+
+		int uuid = msg.getUUID();
+		int slot = msg.getSlotNumber();
+		//System.out.println("loading to slot: " + slot);
+
+		Item i = itemManager.getItemByUUID(uuid);
+
+		if (i == null) {
+			forceTransferFromEquipToInventory(msg, origin, "Item not found in your item manager");
+			return;
+		}
+
+		if (!itemManager.doesCharOwnThisItem(i.getObjectUUID())) {
+			forceTransferFromEquipToInventory(msg, origin, "You do not own this item");
+			return;
+		}
+
+		//dupe check
+		if (!i.validForInventory(origin, pc, itemManager))
+			return;
+
+		if (i.containerType == ItemContainerType.INVENTORY) {
+			if (!itemManager.equipItem(i, (byte) slot)) {
+				forceTransferFromEquipToInventory(msg, origin, "Failed to transfer item.");
+				return;
+			}
+		}
+		else {
+			forceTransferFromEquipToInventory(msg, origin, "This item is not in your inventory");
+			return;
+		}
+
+		// Update player formulas
+		pc.applyBonuses();
+		DispatchMessage.dispatchMsgToInterestArea(pc, msg, DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, false, false);
+
+
+		for (String name : i.getEffects().keySet()) {
+			Effect eff = i.getEffects().get(name);
+			if (eff == null)
+				return;
+
+			ApplyEffectMsg pum = new ApplyEffectMsg();
+			pum.setEffectID(eff.getEffectToken());
+			pum.setSourceType(pc.getObjectType().ordinal());
+			pum.setSourceID(pc.getObjectUUID());
+			pum.setTargetType(pc.getObjectType().ordinal());
+			pum.setTargetID(pc.getObjectUUID());
+			pum.setNumTrains(eff.getTrains());
+			pum.setUnknown05(1);
+			pum.setUnknown06((byte) 1);
+			pum.setEffectSourceType(i.getObjectType().ordinal());
+			pum.setEffectSourceID(i.getObjectUUID());
+			pum.setDuration(-1);
+
+			DispatchMessage.dispatchMsgToInterestArea(pc, pum, DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, false, false);;
+		}
+	}
+
+	//call this if the transfer fails server side to kick the item back to inventory from equip
+	private static void forceTransferFromEquipToInventory(TransferItemFromInventoryToEquipMsg msg, ClientConnection origin, String reason) {
+
+		PlayerCharacter pc = origin.getPlayerCharacter();
+
+		if (pc == null)
+			return;
+
+		TransferItemFromEquipToInventoryMsg back = new TransferItemFromEquipToInventoryMsg(pc, msg.getSlotNumber());
+		Dispatch dispatch = Dispatch.borrow(pc, back);
+		DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
+
+		ChatManager.chatInfoError(pc, "Can't equip item: " + reason);
+	}
+
+	public static Boolean NPCVaultBankRangeCheck(PlayerCharacter pc, ClientConnection origin, String bankorvault) {
+
+		if (pc == null)
+			return false;
+
+		NPC npc = pc.getLastNPCDialog();
+
+		if (npc == null)
+			return false;
+
+		// System.out.println(npc.getContract().getName());
+		// last npc must be either a banker or vault keeper
+
+		if (bankorvault.equals("vault")) {
+			if (npc.getContract().getContractID() != 861)
+				return false;
+		}
+		else
+			// assuming banker
+
+			if (!npc.getContract().getName().equals("Bursar"))
+				return false;
+
+		if (pc.getLoc().distanceSquared2D(npc.getLoc()) > MBServerStatics.NPC_TALK_RANGE * MBServerStatics.NPC_TALK_RANGE) {
+			ErrorPopupMsg.sendErrorPopup(pc, 14);
+			return false;
+		} else
+			return true;
+
+	}
+
+	private static void transferItemFromInventoryToBank(TransferItemFromInventoryToBankMsg msg, ClientConnection origin) {
+
+		PlayerCharacter player = origin.getPlayerCharacter();
+		Dispatch dispatch;
+
+		if (player == null)
+			return;
+
+		if (!NPCVaultBankRangeCheck(player, origin, "bank"))
+			return;
+
+		CharacterItemManager itemManager = player.getCharItemManager();
+
+		if (itemManager == null)
+			return;
+
+		if (itemManager.getBankWeight() > 500) {
+			ErrorPopupMsg.sendErrorPopup(player, 21);
+			return;
+		}
+
+		int uuid = msg.getUUID();
+
+		Item item = itemManager.getItemByUUID(uuid);
+
+		if (item == null)
+			return;
+
+		//dupe check  WTF CHECK BUT NO LOGGING?
+
+		if (!item.validForInventory(origin, player, itemManager))
+			return;
+
+		if (item.containerType == ItemContainerType.INVENTORY && itemManager.isBankOpen())
+			if (item.getItemBase().getType().equals(engine.Enum.ItemType.GOLD)) {
+				if (!itemManager.moveGoldToBank(item, msg.getNumItems()))
+					return;
+				UpdateGoldMsg goldMes = new UpdateGoldMsg(player);
+				goldMes.configure();
+
+				dispatch = Dispatch.borrow(player, goldMes);
+				DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
+
+			}
+			else {
+
+				if (!itemManager.hasRoomBank(item.getItemBase().getWeight()))
+					return;
+
+				if (!itemManager.moveItemToBank(item))
+					return;
+
+				dispatch = Dispatch.borrow(player, msg);
+				DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
+
+			}
+	}
+
+	private static void transferItemFromBankToInventory(TransferItemFromBankToInventoryMsg msg, ClientConnection origin) {
+
+		PlayerCharacter player = origin.getPlayerCharacter();
+		Dispatch dispatch;
+
+		if (player == null)
+			return;
+
+		if (!NPCVaultBankRangeCheck(player, origin, "bank"))
+			return;
+
+		CharacterItemManager itemManager = player.getCharItemManager();
+
+		if (itemManager == null)
+			return;
+
+		int uuid = msg.getUUID();
+
+		Item item = itemManager.getItemByUUID(uuid);
+
+		if (item == null)
+			return;
+
+		//dupe check
+		// WTF Checking but not logging?
+
+		if (!item.validForBank(origin, player, itemManager))
+			return;
+
+		if (item.containerType == ItemContainerType.BANK  && itemManager.isBankOpen() == false)
+			return;
+
+		if (item.getItemBase().getType().equals(engine.Enum.ItemType.GOLD)) {
+
+			if (!itemManager.moveGoldToInventory(item, msg.getNumItems()))
+				return;
+
+			UpdateGoldMsg goldMes = new UpdateGoldMsg(player);
+			goldMes.configure();
+
+			dispatch = Dispatch.borrow(player, goldMes);
+			DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
+
+			return;
+		}
+
+		// Not gold, process update here
+
+		if (!itemManager.hasRoomInventory(item.getItemBase().getWeight()))
+			return;
+
+		if (itemManager.moveItemToInventory(item) == false)
+			return;
+
+		dispatch = Dispatch.borrow(player, msg);
+		DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
+
+	}
+
+	private static void transferItemFromVaultToInventory(TransferItemFromVaultToInventoryMsg msg, ClientConnection origin) {
+
+		PlayerCharacter player = origin.getPlayerCharacter();
+		Dispatch dispatch;
+
+		if (player == null)
+			return;
+
+		if (player.getAccount() == null)
+			return;
+		player.getAccount().transferItemFromVaultToInventory(msg, origin);
+
+
+	}
+
+	//call this if the transfer fails server side to kick the item back to inventory from vault
+	public static void forceTransferFromInventoryToVault(TransferItemFromVaultToInventoryMsg msg, ClientConnection origin, String reason) {
+
+		PlayerCharacter player = origin.getPlayerCharacter();
+		Dispatch dispatch;
+
+		if (player == null)
+			return;
+
+		TransferItemFromInventoryToVaultMsg back = new TransferItemFromInventoryToVaultMsg(msg);
+		dispatch = Dispatch.borrow(player, back);
+		DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
+
+		ChatManager.chatInfoError(player, "Can't transfer to inventory: " + reason);
+	}
+
+	private static void transferItemFromInventoryToVault(TransferItemFromInventoryToVaultMsg msg, ClientConnection origin) {
+
+		PlayerCharacter player = origin.getPlayerCharacter();
+		Dispatch dispatch;
+
+		if (player == null)
+			return;
+
+		if (player.getAccount() == null)
+			return;
+		player.getAccount().transferItemFromInventoryToVault(msg, origin);
+
+	}
+
+	//call this if the transfer fails server side to kick the item back to vault from inventory
+	public static void forceTransferFromVaultToInventory(TransferItemFromInventoryToVaultMsg msg, ClientConnection origin, String reason) {
+
+		PlayerCharacter player = origin.getPlayerCharacter();
+		Dispatch dispatch;
+
+		if (player == null)
+			return;
+
+		TransferItemFromVaultToInventoryMsg back = new TransferItemFromVaultToInventoryMsg(msg);
+		dispatch = Dispatch.borrow(player, back);
+		DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
+
+		ChatManager.chatInfoError(player, "Can't transfer to vault: " + reason);
+	}
+
+	private static void transferGoldFromVaultToInventory(TransferGoldFromVaultToInventoryMsg msg, ClientConnection origin) {
+
+		PlayerCharacter player = origin.getPlayerCharacter();
+
+
+		if (player == null)
+			return;
+
+		Account account = player.getAccount();
+
+		if (account == null)
+			return;
+
+		account.transferGoldFromVaultToInventory(msg, origin);
+	}
+
+	private static void transferGoldFromInventoryToVault(TransferGoldFromInventoryToVaultMsg msg, ClientConnection origin) {
+
+		PlayerCharacter player = origin.getPlayerCharacter();
+		Dispatch dispatch;
+
+		if (player == null)
+			return;
+
+		Account account = player.getAccount();
+
+		if (account == null)
+			return;
+
+		account.transferGoldFromInventoryToVault(msg, origin);
+
+	}
+
+	private static void DeleteItem(DeleteItemMsg msg, ClientConnection origin) {
+
+		CharacterItemManager itemManager = origin.getPlayerCharacter().getCharItemManager();
+		int uuid = msg.getUUID();
+
+
+		PlayerCharacter sourcePlayer = origin.getPlayerCharacter();
+
+		if (sourcePlayer == null)
+			return;
+
+		if (!sourcePlayer.isAlive())
+			return;
+
+		Item i = Item.getFromCache(msg.getUUID());
+
+		if (i == null)
+			return;
+
+		if (!itemManager.doesCharOwnThisItem(i.getObjectUUID()))
+			return;
+
+		if (!itemManager.inventoryContains(i))
+			return;
+
+		if (i.isCanDestroy())
+			if (itemManager.delete(i) == true) {
+				Dispatch dispatch = Dispatch.borrow(sourcePlayer, msg);
+				DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
+			}
+
+	}
+
+	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.setUnknown01(-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 lootWindowRequest(LootWindowRequestMsg msg, ClientConnection origin) throws MsgSendException {
+
+		PlayerCharacter pc = SessionManager.getPlayerCharacter(origin);
+
+		if (pc == null)
+			return;
+
+		if (!pc.isAlive())
+			return;
+
+		if (msg.getSourceType() != pc.getObjectType().ordinal() || msg.getSourceID() != pc.getObjectUUID()) {
+			Logger.error("Player " + pc.getObjectUUID() + " looting from character of id "
+					+ msg.getSourceType() + ' ' + msg.getSourceID());
+			return;
+		}
+
+		if (pc.getAltitude() > 0)
+			return;
+		if (!pc.isAlive()) {
+			return;
+		}
+
+
+		LootWindowResponseMsg lwrm = null;
+		GameObjectType targetType = GameObjectType.values()[msg.getTargetType()];
+		AbstractCharacter characterTarget = null;
+		Corpse corpseTarget = null;
+
+		switch (targetType) {
+		case PlayerCharacter:
+
+			characterTarget = PlayerCharacter.getFromCache(msg.getTargetID());
+			if (characterTarget == null)
+				return;
+			if (characterTarget.isAlive())
+				return;
+			if (pc.getLoc().distanceSquared2D(characterTarget.getLoc()) > sqr(MBServerStatics.LOOT_RANGE)){
+				ErrorPopupMsg.sendErrorMsg(pc, "You are too far away to loot this corpse.");
+
+				Logger.info(pc.getFirstName() + " tried looting at " + pc.getLoc().distance2D(characterTarget.getLoc()) + " distance." );
+				return;
+			}
+			lwrm = new LootWindowResponseMsg(characterTarget.getObjectType().ordinal(), characterTarget.getObjectUUID(), characterTarget.getInventory(true));
+			break;
+		case NPC:
+			characterTarget = NPC.getFromCache(msg.getTargetID());
+			if (characterTarget == null)
+				return;
+			break;
+		case Mob:
+			characterTarget = Mob.getFromCache(msg.getTargetID());
+			if ((characterTarget == null) || characterTarget.isAlive()) {
+				return;
+			}
+
+			if (pc.getLoc().distanceSquared2D(characterTarget.getLoc()) > sqr(MBServerStatics.LOOT_RANGE)){
+				ErrorPopupMsg.sendErrorMsg(pc, "You are too far away to loot this corpse.");
+
+				Logger.info(pc.getFirstName() + " tried looting at " + pc.getLoc().distance2D(characterTarget.getLoc()) + " distance." );
+
+				if (!((Mob)characterTarget).isLootSync()){
+
+					((Mob)characterTarget).setLootSync(true);
+					WorldGrid.updateObject(characterTarget, pc);
+				}
+
+
+				return;
+			}
+
+			lwrm = new LootWindowResponseMsg(characterTarget.getObjectType().ordinal(), characterTarget.getObjectUUID(), characterTarget.getInventory());
+			break;
+		case Corpse:
+			corpseTarget = Corpse.getCorpse(msg.getTargetID());
+
+			if ((corpseTarget == null)) {
+				return;
+			}
+
+			if (pc.getLoc().distanceSquared(corpseTarget.getLoc()) > sqr(MBServerStatics.LOOT_RANGE)){
+				ErrorPopupMsg.sendErrorMsg(pc, "You are too far away to loot this corpse.");
+
+				Logger.info(pc.getFirstName() + " tried looting at " + pc.getLoc().distance2D(characterTarget.getLoc()) + " distance." );
+				return;
+			}
+			lwrm = new LootWindowResponseMsg(corpseTarget.getObjectType().ordinal(), msg.getTargetID(), corpseTarget.getInventory());
+			break;
+		}
+
+		if (lwrm == null)
+			return;
+
+		DispatchMessage.dispatchMsgToInterestArea(pc, msg, DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, false, false);
+		Dispatch dispatch = Dispatch.borrow(pc, lwrm);
+		DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.PRIMARY);
+
+	}
+
+	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.getCharItemManager() != null) {
+
+							itemRet = tar.getCharItemManager().lootItemFromMe(item, player, origin);
+
+							//Take equipment off mob
+							if (tar.getObjectType() == GameObjectType.Mob && itemRet != null){
+								Mob mobTarget = (Mob)tar;
+								if (mobTarget.getFidalityID() != 0){
+									if (item != null && item.getObjectType() == GameObjectType.MobLoot){
+										int fidelityEquipID = ((MobLoot)item).getFidelityEquipID();
+
+										if (fidelityEquipID != 0){
+											for (MobEquipment equip: mobTarget.getEquip().values()){
+												if (equip.getObjectUUID() == fidelityEquipID){
+													TransferItemFromEquipToInventoryMsg back = new TransferItemFromEquipToInventoryMsg(mobTarget, equip.getSlot());
+
+													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 {
+
+					}
+
+				}
+				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.getItemBase().getType().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.getItemBase().getType().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.getItemBase().getType().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.getItemBase().getType().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();
+			}
+		}
+
+
+	}
+
+	//returns true if looted item is goldItem and is split. Otherwise returns false
+
+	// 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);
+
+	}
+
+	private static void MineWindowAvailableTime(ArcMineWindowAvailableTimeMsg msg, ClientConnection origin) {
+		Building tol = BuildingManager.getBuildingFromCache(msg.getBuildingUUID());
+		Dispatch dispatch;
+
+		if (tol == null)
+			return;
+		
+		if (tol.getBlueprintUUID() == 0)
+			return;
+
+		if (tol.getBlueprint().getBuildingGroup() != BuildingGroup.TOL)
+			return;
+
+		PlayerCharacter pc = SessionManager.getPlayerCharacter(origin);
+
+		if (pc == null)
+			return;
+
+		if (!Guild.sameGuild(tol.getGuild(), pc.getGuild()))
+			return; //must be same guild
+
+		if (GuildStatusController.isInnerCouncil(pc.getGuildStatus()) == false) // is this only GL?
+			return;
+
+		ArcMineWindowAvailableTimeMsg amwat = new ArcMineWindowAvailableTimeMsg(tol, 10);
+		amwat.configure();
+		dispatch = Dispatch.borrow(pc, amwat);
+		DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
+
+	}
+
+	private static void MineWindowChange(ArcMineWindowChangeMsg msg, ClientConnection origin) {
+
+		Building tol = (Building) BuildingManager.getBuildingFromCache(msg.getBuildingID());
+		//hodge podge sanity check to make sure they dont set it before early window and is not set at late window.
+		if (msg.getTime() < MBServerStatics.MINE_EARLY_WINDOW && msg.getTime() != MBServerStatics.MINE_LATE_WINDOW)
+			return;    //invalid mine time, must be in range
+		if (tol == null)
+			return;
+
+		if (tol.getBlueprintUUID() == 0)
+			return;
+
+		if (tol.getBlueprint().getBuildingGroup() != BuildingGroup.TOL)
+			return;
+
+		PlayerCharacter pc = SessionManager.getPlayerCharacter(origin);
+		if (pc == null)
+			return;
+
+		Guild tolG = tol.getGuild();
+		if (tolG == null)
+			return;
+		if (!Guild.sameGuild(tolG, pc.getGuild()))
+			return; //must be same guild
+		if (GuildStatusController.isInnerCouncil(pc.getGuildStatus()) == false) // is this only GL?
+			return;
+
+		if (!DbManager.GuildQueries.UPDATE_MINETIME(tolG.getObjectUUID(), msg.getTime())) {
+			Logger.error("MineWindowChange", "Failed to update mine time for guild " + tolG.getObjectUUID());
+			ChatManager.chatGuildError(pc, "Failed to update the mine time");
+			return;
+		}
+		tolG.setMineTime(msg.getTime());
+		ChatManager.chatGuildInfo(pc, "Mine time updated.");
+	}
+
+	private static void ListOwnedMines(ArcOwnedMinesListMsg msg, ClientConnection origin) {
+
+		PlayerCharacter pc = SessionManager.getPlayerCharacter(origin);
+
+		if (pc == null)
+			return;
+		//TODO verify this against the warehouse?
+		
+		if (GuildStatusController.isInnerCouncil(pc.getGuildStatus()) == false)// is this only GL?
+			return;
+
+		msg.setMineList(Mine.getMinesForGuild(pc.getGuild().getObjectUUID()));
+		Dispatch dispatch = Dispatch.borrow(pc, msg);
+		DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
+	}
+
+	private static void changeMineProduction(ArcMineChangeProductionMsg msg, ClientConnection origin) {
+
+		PlayerCharacter sourcePlayer = SessionManager.getPlayerCharacter(origin);
+
+		if (sourcePlayer == null)
+			return;
+
+		//TODO verify this against the warehouse?
+
+		if (GuildStatusController.isInnerCouncil(sourcePlayer.getGuildStatus()) == false) // is this only GL?
+			return;
+
+		Mine mine = Mine.getMine(msg.getMineID());
+
+		if (mine == null)
+			return;
+
+		//make sure mine belongs to guild
+		if (mine.getOwningGuild() == null || mine.getOwningGuild().getObjectUUID() != sourcePlayer.getGuild().getObjectUUID())
+			return;
+
+		//make sure valid resource
+		Resource r = Resource.resourceByHash.get(msg.getResourceHash());
+
+		if (r == null)
+			return;
+
+		//update resource
+		mine.changeProductionType(r);
+		Mine.setLastChange(System.currentTimeMillis());
+		Dispatch dispatch = Dispatch.borrow(sourcePlayer, msg);
+		DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
+	}
+
+	private static void randomRoll(RandomMsg msg, ClientConnection origin) throws MsgSendException {
+
+		PlayerCharacter source = origin.getPlayerCharacter();
+
+		if (source == null || !source.isAlive())
+			return;
+
+		//2 second cooldown on random rolls
+		long lastRandom = source.getTimeStamp("RandomRoll");
+
+		if (System.currentTimeMillis() - lastRandom < 2000)
+			return;
+		source.setTimeStamp("RandomRoll", System.currentTimeMillis());
+
+		//handle random roll
+		int max = msg.getMax();
+
+		if (max > 0)
+			msg.setRoll(ThreadLocalRandom.current().nextInt(max) + 1);
+		else if (max < 0) {
+			max = 1 - max;
+			msg.setRoll((ThreadLocalRandom.current().nextInt(max) - max) + 1);
+		}
+
+		msg.setSourceType(source.getObjectType().ordinal());
+		msg.setSourceID(source.getObjectUUID());
+
+		//send to all in range
+		DispatchMessage.dispatchMsgToInterestArea(source, msg, DispatchChannel.SECONDARY, MBServerStatics.CHARACTER_LOAD_RANGE, true, true);
+	}
+
+	private static void stuck(ClientConnection origin) {
+
+		PlayerCharacter sourcePlayer = origin.getPlayerCharacter();
+
+		if (sourcePlayer == null)
+			return;
+
+		if (sourcePlayer.getTimers().containsKey("Stuck"))
+			return;
+
+		StuckJob sj = new StuckJob(sourcePlayer);
+		JobContainer jc = JobScheduler.getInstance().scheduleJob(sj, 10000); // Convert
+		ConcurrentHashMap<String, JobContainer> timers = sourcePlayer.getTimers();
+
+		if (timers != null) {
+			if (timers.containsKey("Stuck")) {
+				timers.get("Stuck").cancelJob();
+				timers.remove("Stuck");
+			}
+			timers.put("Stuck", jc);
+		}
+	}
+
+	private static void GuildTreeStatusMsg(GuildTreeStatusMsg msg, ClientConnection origin) throws SQLException {
+
+		PlayerCharacter player = SessionManager.getPlayerCharacter(origin);
+		Dispatch dispatch;
+
+		if (player == null)
+			return;
+
+		if (origin.guildtreespam > System.currentTimeMillis()) {
+			return;
+		}
+		origin.guildtreespam = System.currentTimeMillis() + 5000;
+
+		Building b = BuildingManager.getBuildingFromCache(msg.getTargetID());
+		if (b == null)
+			return;
+
+		GuildTreeStatusMsg gtsm = new GuildTreeStatusMsg(b, player);
+		gtsm.configure();
+
+		dispatch = Dispatch.borrow(player, gtsm);
+		DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
+	}
+
+
+	private static void openSellToNPCWindow(SellToNPCWindowMsg msg, ClientConnection origin) {
+
+		PlayerCharacter sourcePlayer = SessionManager.getPlayerCharacter(origin);
+		Dispatch dispatch;
+
+		if (sourcePlayer == null)
+			return;
+
+		NPC npc = NPC.getFromCache(msg.getNPCID());
+
+		if (npc == null)
+			return;
+
+		// test within talking range
+
+		if (sourcePlayer.getLoc().distanceSquared2D(npc.getLoc()) > MBServerStatics.NPC_TALK_RANGE * MBServerStatics.NPC_TALK_RANGE) {
+			ErrorPopupMsg.sendErrorPopup(sourcePlayer, 14);
+			return;
+		}
+
+		Contract con = npc.getContract();
+
+		if (con == null)
+			return;
+		float bargain = sourcePlayer.getBargain();
+		
+		float profit = npc.getBuyPercent(sourcePlayer) + bargain;
+		
+		if (profit > 1)
+			profit = 1;
+		
+		msg.setupOutput();
+
+		msg.setUnknown05(profit);
+		msg.setUnknown06(500000); //TODO set goldItem on npc later
+		msg.setItemType(con.getBuyItemType());
+		msg.setSkillTokens(con.getBuySkillToken());
+		msg.setUnknownArray(con.getBuyUnknownToken());
+
+		dispatch = Dispatch.borrow(sourcePlayer, msg);
+		DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
+
+	}
+
+	private static void sellToNPC(SellToNPCMsg msg, ClientConnection origin) {
+
+		PlayerCharacter player = SessionManager.getPlayerCharacter(origin);
+		Dispatch dispatch;
+
+		if (player == null)
+			return;
+
+		CharacterItemManager itemMan = player.getCharItemManager();
+
+		if (itemMan == null)
+			return;
+
+		NPC npc = NPC.getFromCache(msg.getNPCID());
+
+		if (npc == null)
+			return;
+
+		Item gold = itemMan.getGoldInventory();
+
+		if (gold == null)
+			return;
+
+		if (origin.sellLock.tryLock()) {
+			try {
+				Item sell;
+				int cost = 0;
+
+
+				if (npc.getCharItemManager().getInventoryCount() > 150) {
+					if (npc.getParentZone() != null && npc.getParentZone().getPlayerCityUUID() == 0) {
+						ArrayList<Item> inv = npc.getInventory();
+						for (int i = 0; i < 20; i++) {
+							try {
+								Item toRemove = inv.get(i);
+								if (toRemove != null)
+									npc.getCharItemManager().delete(toRemove);
+							} catch (Exception e) {
+								break;
+							}
+
+						}
+					}
+
+				}
+
+				// Early exit sanity check
+
+				if (msg.getItemType() == GameObjectType.Item.ordinal() == false)
+					return;
+
+				sell = Item.getFromCache(msg.getItemID());
+
+				if (sell == null)
+					return;
+
+				//get item to sell
+
+				ItemBase ib = sell.getItemBase();
+
+				if (ib == null)
+					return;
+
+				if (npc.getParentZone() != null && npc.getParentZone().getPlayerCityUUID() != 0)
+					if (!npc.getCharItemManager().hasRoomInventory(ib.getWeight())){
+
+						ErrorPopupMsg.sendErrorPopup(player, 21);
+						return;
+					}
+
+				if (!sell.validForInventory(origin, player, itemMan))
+					return;
+
+				//get goldItem cost to sell
+				
+				
+				cost = sell.getBaseValue();
+				
+				if (sell.isID())
+					cost = sell.getMagicValue();
+				
+				float bargain = player.getBargain();
+				
+				float profit = npc.getBuyPercent(player) + bargain;
+				
+				if (profit > 1)
+					profit = 1;
+					
+				
+				
+				cost *= profit;
+
+				if (gold.getNumOfItems() + cost > 10000000) {
+					return;
+				}
+
+				if (gold.getNumOfItems() + cost < 0)
+					return;
+
+				//TODO make sure npc can buy item type
+				//test room available for item on npc
+
+				//                                 if (!npc.isStatic() && npc.getCharItemManager().getInventoryCount() > 150) {
+				//                                   //  chatMan.chatSystemInfo(pc, "This vendor's inventory is full");
+				//                                     return;
+				//                                 }
+
+				//make sure item is in player inventory
+
+				Building building = (!npc.isStatic()) ? npc.getBuilding() : null;
+
+				if (building != null && building.getProtectionState().equals(ProtectionState.NPC))
+					building = null;
+				if (npc.getParentZone().getPlayerCityUUID() == 0)
+					building = null;
+
+				//make sure npc can afford item
+
+				if (building != null && !building.hasFunds(cost)){
+					ErrorPopupMsg.sendErrorPopup(player, 17);
+					return;
+				}
+				if (building != null && (building.getStrongboxValue() - cost) < 0){
+					ErrorPopupMsg.sendErrorPopup(player, 17);
+					return;
+				}
+
+				//TODO transfer item and goldItem transfer should be handled together incase failure
+				//transfer the item
+
+				if (!itemMan.sellToNPC(sell, npc))
+					return;
+
+				if (!itemMan.sellToNPC(building, cost, sell))
+					return;
+
+				//handle goldItem transfer
+
+				if (sell == null)
+					return;
+
+				// ***REFACTOR: SellToNpc sends this message, is this a duplicate?
+
+				//update player's goldItem count
+				UpdateGoldMsg ugm = new UpdateGoldMsg(player);
+				ugm.configure();
+
+				dispatch = Dispatch.borrow(player, ugm);
+				DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
+
+				//send the sell message back to update player
+				msg.setItemType(sell.getObjectType().ordinal());
+				msg.setItemID(sell.getObjectUUID());
+				msg.setUnknown01(cost); //not sure if this is correct
+
+				dispatch = Dispatch.borrow(player, msg);
+				DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
+
+			} finally {
+				origin.sellLock.unlock();
+			}
+		}
+		else {
+			ErrorPopupMsg.sendErrorPopup(player, 12);
+		}
+	}
+
+	private static void openBuyFromNPCWindow(BuyFromNPCWindowMsg msg, ClientConnection origin) {
+
+		PlayerCharacter sourcePlayer = SessionManager.getPlayerCharacter(origin);
+		Dispatch dispatch;
+
+		if (sourcePlayer == null)
+			return;
+
+		NPC npc = NPC.getFromCache(msg.getNpcID());
+
+		if (npc == null)
+			return;
+
+		// test within talking range
+
+		if (sourcePlayer.getLoc().distanceSquared2D(npc.getLoc()) > MBServerStatics.NPC_TALK_RANGE * MBServerStatics.NPC_TALK_RANGE) {
+			ErrorPopupMsg.sendErrorPopup(sourcePlayer, 14);
+			return;
+		}
+
+		dispatch = Dispatch.borrow(sourcePlayer, msg);
+		DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
+	}
+
+	private static void buyFromNPC(BuyFromNPCMsg msg, ClientConnection origin) {
+
+		PlayerCharacter sourcePlayer = SessionManager.getPlayerCharacter(origin);
+
+		if (sourcePlayer == null)
+			return;
+
+		if (origin.buyLock.tryLock()) {
+
+			try {
+				CharacterItemManager itemMan = sourcePlayer.getCharItemManager();
+
+				if (itemMan == null)
+					return;
+
+				NPC npc = NPC.getFromCache(msg.getNPCID());
+
+				if (npc == null)
+					return;
+
+				Item gold = itemMan.getGoldInventory();
+
+				if (gold == null)
+					return;
+
+				Item buy = null;
+
+				if (msg.getItemType() == GameObjectType.MobEquipment.ordinal()) {
+					ArrayList<MobEquipment> sellInventory = npc.getContract().getSellInventory();
+					if (sellInventory == null)
+						return;
+					for (MobEquipment me : sellInventory) {
+						if (me.getObjectUUID() == msg.getItemID()) {
+							ItemBase ib = me.getItemBase();
+							if (ib == null)
+								return;
+
+							//test room available for item
+							if (!itemMan.hasRoomInventory(ib.getWeight()))
+								return;
+
+							int cost = me.getMagicValue();
+							
+							float bargain = sourcePlayer.getBargain();
+							
+							float profit = npc.getSellPercent(sourcePlayer) - bargain;
+							
+						if (profit < 1)
+							profit = 1;
+							
+							
+							cost *= profit;
+							
+							
+							
+						
+
+							
+							if (gold.getNumOfItems() - cost < 0) {
+								//dont' have enough goldItem exit!
+								// chatMan.chatSystemInfo(pc, "" + "You dont have enough gold.");
+								return;
+							}
+
+							Building b = (!npc.isStatic()) ? npc.getBuilding() : null;
+							
+							if (b != null && b.getProtectionState().equals(ProtectionState.NPC))
+								b = null;
+							int buildingDeposit = cost - me.getMagicValue();
+							if (b != null && (b.getStrongboxValue() + buildingDeposit) > b.getMaxGold()) {
+								ErrorPopupMsg.sendErrorPopup(sourcePlayer, 206);
+								return;
+							}
+
+							if (!itemMan.buyFromNPC(b, cost, buildingDeposit)) {
+								// chatMan.chatSystemInfo(pc, "" + "You Failed to buy the item.");
+								return;
+							}
+
+							buy = Item.createItemForPlayer(sourcePlayer, ib);
+
+							if (buy != null) {
+								me.transferEnchants(buy);
+								itemMan.addItemToInventory(buy);
+								//itemMan.updateInventory();
+							}
+						}
+					}
+				}
+				else if (msg.getItemType() == GameObjectType.Item.ordinal()) {
+
+					CharacterItemManager npcCim = npc.getCharItemManager();
+
+					if (npcCim == null)
+						return;
+
+					buy = Item.getFromCache(msg.getItemID());
+
+					if (buy == null)
+						return;
+
+					ItemBase ib = buy.getItemBase();
+
+					if (ib == null)
+						return;
+
+					if (!npcCim.inventoryContains(buy))
+						return;
+
+					//test room available for item
+					if (!itemMan.hasRoomInventory(ib.getWeight()))
+						return;
+
+					//TODO test cost and subtract goldItem
+
+					//TODO CHnage this if we ever put NPc city npcs in buildings.
+					int cost = buy.getBaseValue();
+					
+					if (buy.isID() || buy.isCustomValue())
+						cost = buy.getMagicValue();
+					
+					float bargain = sourcePlayer.getBargain();
+					
+					float profit = npc.getSellPercent(sourcePlayer) - bargain;
+					
+				if (profit < 1)
+					profit = 1;
+					
+					if (!buy.isCustomValue())
+						cost *= profit;
+					else
+						cost = buy.getValue();
+					
+					
+						
+					if (gold.getNumOfItems() - cost < 0) {
+						ErrorPopupMsg.sendErrorPopup(sourcePlayer, 128);  // Insufficient Gold
+						return;
+					}
+
+					Building b = (!npc.isStatic()) ? npc.getBuilding() : null;
+					
+					if (b != null)
+					if (b.getProtectionState().equals(ProtectionState.NPC))
+						b = null;
+					
+					int buildingDeposit = cost;
+
+					if (b != null && (b.getStrongboxValue() + buildingDeposit) > b.getMaxGold()) {
+						ErrorPopupMsg.sendErrorPopup(sourcePlayer, 206);
+						return;
+					}
+
+					if (!itemMan.buyFromNPC(b, cost, buildingDeposit)) {
+						ErrorPopupMsg.sendErrorPopup(sourcePlayer, 110);
+						return;
+					}
+
+					if (buy != null)
+						itemMan.buyFromNPC(buy, npc);
+
+				}else if (msg.getItemType() == GameObjectType.MobLoot.ordinal()) {
+
+					CharacterItemManager npcCim = npc.getCharItemManager();
+
+					if (npcCim == null)
+						return;
+
+					buy = MobLoot.getFromCache(msg.getItemID());
+
+					if (buy == null)
+						return;
+
+					ItemBase ib = buy.getItemBase();
+
+					if (ib == null)
+						return;
+
+					if (!npcCim.inventoryContains(buy))
+						return;
+
+					//test room available for item
+					if (!itemMan.hasRoomInventory(ib.getWeight()))
+						return;
+
+					//TODO test cost and subtract goldItem
+
+					//TODO CHnage this if we ever put NPc city npcs in buildings.
+
+					int cost = buy.getMagicValue();
+						cost *= npc.getSellPercent(sourcePlayer);
+					
+
+					if (gold.getNumOfItems() - cost < 0) {
+						ErrorPopupMsg.sendErrorPopup(sourcePlayer, 128);  // Insufficient Gold
+						return;
+					}
+
+					Building b = (!npc.isStatic()) ? npc.getBuilding() : null;
+					
+					if (b != null && b.getProtectionState().equals(ProtectionState.NPC))
+						b = null;
+					int buildingDeposit = cost;
+
+					if (b != null && (b.getStrongboxValue() + buildingDeposit) > b.getMaxGold()) {
+						ErrorPopupMsg.sendErrorPopup(sourcePlayer, 206);
+						return;
+					}
+
+					if (!itemMan.buyFromNPC(b, cost, buildingDeposit))
+						return;
+
+					if (buy != null)
+						itemMan.buyFromNPC(buy, npc);
+
+				}
+				else
+					return;
+
+				if (buy != null) {
+
+					msg.setItem(buy);
+					//send the buy message back to update player
+					//					msg.setItemType(buy.getObjectType().ordinal());
+					//					msg.setItemID(buy.getObjectUUID());
+					Dispatch dispatch = Dispatch.borrow(sourcePlayer, msg);
+					DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
+					itemMan.updateInventory();
+				}
+
+			} finally {
+				origin.buyLock.unlock();
+			}
+		}
+		else {
+			ErrorPopupMsg.sendErrorPopup(origin.getPlayerCharacter(), 12); // All production slots taken
+		}
+
+	}
+
+	//Handle RepairObject Window and RepairObject Requests
+
+	private static void Repair(RepairMsg msg, ClientConnection origin) {
+
+		PlayerCharacter player = SessionManager.getPlayerCharacter(origin);
+		Dispatch dispatch;
+
+		if (player == null)
+			return;
+
+		NPC npc = NPC.getFromCache(msg.getNPCID());
+
+		if (npc == null)
+			return;
+
+		if (msg.getMsgType() == 1) { //Open RepairObject Window
+
+			if (player.getLoc().distanceSquared2D(npc.getLoc()) > MBServerStatics.NPC_TALK_RANGE * MBServerStatics.NPC_TALK_RANGE) {
+				ErrorPopupMsg.sendErrorPopup(player, 14);
+				return;
+			}
+
+			//send open repair window response
+			msg.setRepairWindowAck(npc);
+			dispatch = Dispatch.borrow(player, msg);
+			DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
+
+		}
+		else if (msg.getMsgType() == 0) { //Request RepairObject
+
+			CharacterItemManager itemMan = player.getCharItemManager();
+
+			if (itemMan == null)
+				return;
+
+			Item gold = itemMan.getGoldInventory();
+
+			if (gold == null)
+				return;
+
+			Item toRepair = Item.getFromCache(msg.getItemID());
+
+			if (toRepair == null)
+				return;
+			
+			if (toRepair.getItemBase().isGlass())
+				return;
+
+			//make sure item is in player's inventory or equipment
+			if (!itemMan.inventoryContains(toRepair) && !itemMan.equippedContains(toRepair))
+				return;
+
+			//make sure item is damaged and not destroyed
+			short dur = toRepair.getDurabilityCurrent();
+			short max = toRepair.getDurabilityMax();
+
+			if (dur >= max || dur < 1)
+				return;
+
+			//TODO get cost to repair
+			int cost = (int) ((max - dur) * 80.1);
+			Building b = (!npc.isStatic()) ? npc.getBuilding() : null;
+			
+			if (b != null)
+			if (b.getProtectionState().equals(ProtectionState.NPC))
+				b = null;
+			
+
+			if (b != null && (b.getStrongboxValue() + cost) > b.getMaxGold()) {
+				ErrorPopupMsg.sendErrorPopup(player, 206);
+				return;
+			}
+
+			if (player.getCharItemManager().getGoldInventory().getNumOfItems() - cost < 0)
+				return;
+
+			if (player.getCharItemManager().getGoldInventory().getNumOfItems() - cost > MBServerStatics.PLAYER_GOLD_LIMIT)
+				return;
+
+			if (!itemMan.buyFromNPC(b, cost, cost)) {
+				ErrorPopupMsg.sendErrorPopup(player, 128);
+				return;
+			}
+
+			//update player's goldItem count
+			UpdateGoldMsg ugm = new UpdateGoldMsg(player);
+			ugm.configure();
+			dispatch = Dispatch.borrow(player, ugm);
+			DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
+
+			//update durability to database
+			if (!DbManager.ItemQueries.SET_DURABILITY(toRepair, max))
+				return;
+
+			//repair the item
+			toRepair.setDurabilityCurrent(max);
+
+			//send repair msg
+			msg.setupRepairAck(max - dur);
+			dispatch = Dispatch.borrow(player, msg);
+			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 (pet.getCombatTarget() == null)
+			return;
+		pet.setState(STATE.Attack);
+	}
+
+	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.getState() == STATE.Disabled)
+			return;
+
+		int type = msg.getType();
+
+		if (type == 1) { //stop attack
+			pet.setCombatTarget(null);
+			pc.setCombat(false);
+			pet.setState(STATE.Awake);
+
+		}
+		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);
+		}
+	}
+
+	protected static void HandlePromptRecall(PromptRecallMsg msg, ClientConnection origin) throws MsgSendException {
+
+		PlayerCharacter player = SessionManager.getPlayerCharacter(origin);
+		boolean recallAccepted;
+
+		if (player == null)
+			return;
+
+		boolean confirmed = msg.getConfirmed();
+
+		if (confirmed == true) {
+			long timeElapsed = System.currentTimeMillis() - player.getTimeStamp("PromptRecall");
+			//send fail message
+			recallAccepted = timeElapsed < 15000;
+		}
+		else
+			recallAccepted = false;
+
+		if (recallAccepted == true) {
+			//handle recall
+			long type = player.getTimeStamp("LastRecallType");
+
+			if (type == 1) { //recall to bind
+				player.teleport(player.getBindLoc());
+				player.setSafeMode();
+			}
+			else { //recall to rg
+				float dist = 9999999999f;
+				Building rg = null;
+				Vector3fImmutable rgLoc;
+
+				for (Runegate runegate : Runegate.getRunegates()) {
+
+					if ((runegate.getGateType() == RunegateType.OBLIV) ||
+							(runegate.getGateType() == RunegateType.CHAOS))
+						continue;
+
+					for (Runegate thisGate : Runegate.getRunegates()) {
+
+						rgLoc = thisGate.getGateType().getGateBuilding().getLoc();
+
+						float distanceSquaredToRunegate = player.getLoc().distanceSquared2D(rgLoc);
+
+						if (distanceSquaredToRunegate < sqr(dist))
+							rg = thisGate.getGateType().getGateBuilding();
+
+					}
+				}
+				//nearest runegate found. teleport characterTarget
+
+				if (rg != null) {
+				player.teleport(rg.getLoc());
+					player.setSafeMode();
+				}
+			}
+		}
+	}
+
+}
diff --git a/src/engine/net/client/Protocol.java b/src/engine/net/client/Protocol.java
new file mode 100644
index 00000000..d95becd3
--- /dev/null
+++ b/src/engine/net/client/Protocol.java
@@ -0,0 +1,338 @@
+package engine.net.client;
+
+/* This class defines Magicbane's application network protocol.
+-->  Name / Opcode / Message / Handler
+ */
+
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.client.handlers.*;
+import engine.net.client.msg.*;
+import engine.net.client.msg.chat.*;
+import engine.net.client.msg.commands.ClientAdminCommandMsg;
+import engine.net.client.msg.group.*;
+import engine.net.client.msg.guild.*;
+import engine.net.client.msg.login.*;
+import org.pmw.tinylog.Logger;
+
+import java.lang.reflect.Constructor;
+import java.util.HashMap;
+
+public enum Protocol {
+
+
+    NONE(0x0, null, null),
+    ABANDONASSET(0xFDDBB233, AbandonAssetMsg.class, AbandonAssetMsgHandler.class), // AbandonAsset
+    ACTIVATECHARTER(0x296C0B22, UseCharterMsg.class, null),// Use Guild Charter
+    ACTIVATENPC(0xC9AAE81E, ActivateNPCMessage.class, ActivateNPCMsgHandler.class),
+    ACTIVATEPLEDGE(0x5A694DC0, SwearInMsg.class, SwearInHandler.class), // Swear In
+    ADDFRIEND(0xCFA1C787,AddFriendMessage.class,null),
+    ALLIANCECHANGE(0x0E7D0B57,  AllianceChangeMsg.class, AllianceChangeMsgHandler.class), // Remove From Allies/Enemies List
+    ALLYENEMYLIST(0xAEA443FD, AllyEnemyListMsg.class, AllyEnemyListMsgHandler.class),
+    ARCCOMBATMODEATTACKING(0xD8B10579,  SetCombatModeMsg.class, null), // Attack From Outside Combat Mode
+    ARCHOTZONECHANGE(0xDCFF196F, null, null), //change hotzone
+    ARCIGNORELISTUPDATE(0x4B1B17C2, IgnoreListMsg.class, null), //req/show ignore list
+    ARCLOGINNOTIFY(0x010FED87, ArcLoginNotifyMsg.class, ArcLoginNotifyMsgHandler.class), //Client Confirms entering world
+    ARCMINECHANGEPRODUCTION(0x1EAA993F,  ArcMineChangeProductionMsg.class, null),
+    ARCMINETOWERCRESTUPDATE(0x34164D0D, null, null),
+    ARCMINEWINDOWAVAILABLETIME(0x6C909DE7, ArcMineWindowAvailableTimeMsg.class, null),
+    ARCMINEWINDOWCHANGE(0x92B2148A, ArcMineWindowChangeMsg.class, null),
+    ARCOWNEDMINESLIST(0x59184455, ArcOwnedMinesListMsg.class, null),
+    ARCPETATTACK(0x18CD61AD, PetAttackMsg.class, null), // Pet Attack
+    ARCPETCMD(0x4E80E001, PetCmdMsg.class, null), // Stop ArcPetAttack, Toggle Assist, Toggle Rest
+    ARCPOWERPROJECTILE(0xA2312D3B, null, null),
+    ARCPROMPTRECALL(0xE3196B6E, PromptRecallMsg.class, null), //Recall Prompt
+    ARCREQUESTTRADEBUSY(0xD4BAB4DF, InvalidTradeRequestMsg.class, null), // Attempt trade with someone who is already trading
+    ARCSERVERSTATUS(0x87BA4462, null, null), //Update Server Status
+    ARCSIEGESPIRE(0x36A49BC6, ArcSiegeSpireMsg.class, ArcSiegeSpireMsgHandler.class), // Activate/Deactivate Spires
+    ARCSUMMON(0xFD816A0A, RecvSummonsRequestMsg.class, null), // Suspect Recv Summons Request
+    ARCTRACKINGLIST(0xC89CF08B, TrackWindowMsg.class, null), //Request/Send Track window
+    ARCTRACKOBJECT(0x609B6BA2, TrackArrowMsg.class, null), //Send Track Arrow
+    ARCUNTRAINABILITY(0x548DBF83, RefineMsg.class, null), //Refine
+    ARCUNTRAINLIST(0x38879E90, RefinerScreenMsg.class, null), //Refiner screen
+    ARCVIEWASSETTRANSACTIONS(0xBFA476E4, ArcViewAssetTransactionsMsg.class, ArcViewAssetTransactionsMsgHandler.class),
+    ASSETSUPPORT(0xc481f89D, AssetSupportMsg.class, AssetSupportMsgHandler.class),
+    BANISHMEMBER(0x31AA3368, BanishUnbanishMsg.class, BanishUnbanishHandler.class), // Banish/Unbanish
+    BANKINVENTORY(0x32F3F503, ShowBankInventoryMsg.class, null), // ShowCombatInfo Bank Inventory
+    BREAKFEALTY(0x479A4C19, BreakFealtyMsg.class, BreakFealtyHandler.class),
+    BUYFROMNPC(0xA2B8DFA5, BuyFromNPCMsg.class, null), // Buy Item From NPC
+    CANCELGUILDCREATION(0x385EA922, GuildCreationCloseMsg.class, GuildCreationCloseHandler.class), //Close the window
+    CHANGEALTITUDE(0x624F08BA, ChangeAltitudeMsg.class, ChangeAltitudeHandler.class), //Change Altitude
+    CHANGEGUILDLEADER(0xE40BC95D, ChangeGuildLeaderMsg.class, ChangeGuildLeaderHandler.class),
+    CHANNELMUTE(0xC1BDC53A, ChatFilterMsg.class, ChannelMuteMsgHandler.class), //Chat Channels that are turned on
+    CHARSELECTSCREEN(0x682C935D, null, null), // Character Selection Screen
+    CHATCITY(0x9D402901, ChatCityMsg.class, null), // Chat Channel: /City
+    CHATCSR(0x14EBA1C3, ChatCSRMsg.class, null),	//Chat Channel: CSR
+    CHATGROUP(0xA895B634, ChatGroupMsg.class, null), // Chat Channel: /group
+    CHATGUILD(0xA9D92ED4, ChatGuildMsg.class, null), // Chat Channel: /guild
+    CHATIC(0x00A75F35, ChatICMsg.class, null), // Chat Channel: /IC
+    CHATINFO(0x9D4B61EB, ChatInfoMsg.class, null), // Chat Channel: /Info
+    CHATPVP(0x14EBA570, ChatPvPMsg.class, null), // Chat Channel: PVP
+    CHATSAY(0x14EA0393, ChatSayMsg.class, null), // Chat Channel: /say
+    CHATSHOUT(0xA8D5B560, ChatShoutMsg.class, null), // Chat Channel: /shout
+    CHATTELL(0x9D4AC896, ChatTellMsg.class, null), // Chat Channel: /tell
+    CHECKUNIQUEGUILD(0x689097D7, GuildCreationOptionsMsg.class, GuildCreationOptionsHandler.class), // Set Guild Name/Motto in Use Guild Charter
+    CITYASSET(0x7cae1678, CityAssetMsg.class, null),
+    CITYCHOICE(0x406610BB, CityChoiceMsg.class, CityChoiceMsgHandler.class),
+    CITYDATA(0xB8A947D4, WorldObjectMsg.class, null),			//Realm Data - Optional(?)
+    CITYZONE(0x254947F2, CityZoneMsg.class, null), //For Creating City Object Clientside(Terraform)/Rename City.
+    CLAIMASSET(0x948C62CC, ClaimAssetMsg.class, ClaimAssetMsgHandler.class), // ClaimAsset
+    CLAIMGUILDTREE(0xFD1C6442, ClaimGuildTreeMsg.class, ClaimGuildTreeMsgHandler.class),
+    CLIENTADMINCOMMAND(0x624EAB5F, ClientAdminCommandMsg.class, null),	//Admin Command
+    CLIENTUPDATEVAULT( 0x66EDBECD, UpdateVaultMsg.class, null),
+    COMBATMODE(0xFE4BF353, ToggleCombatMsg.class, null), //Toggle Combat mode
+    CONFIRMPROMOTE(0x153BB5F9, ConfirmPromoteMsg.class, null),
+    COSTTOOPENBANK(0x135BE5E8, AckBankWindowOpenedMsg.class, null), // ACK Bank Window Opened
+    CREATECHAR(0x5D18B5C8, CommitNewCharacterMsg.class, null), // Commit New Character,
+    CREATEPETITION(0xD489CFED, GuildCreationFinalizeMsg.class, GuildCreationFinalizeHandler.class),	//Confirm guild creation
+    CUSTOMERPETITION(0x7F9D7D6D, PetitionReceivedMsg.class, null),
+    DELETEOBJECT(0x57F069D8, DeleteItemMsg.class, null), //Delete Item from Inventory
+    DESTROYBUILDING(0x3CB6FAD3, DestroyBuildingMsg.class, DestroyBuildingHandler.class), // Destroy Building
+    DISBANDGUILD(0x77AABD64, DisbandGuildMsg.class, DisbandGuildHandler.class), //Disband Guild
+    DISMISSGUILD(0x8D2D3D61, DismissGuildMsg.class, DismissGuildHandler.class),
+    DOORTRYOPEN(0xA83DD8C8, DoorTryOpenMsg.class, DoorTryOpenMsgHandler.class), // Open/Close Door
+    ENTERWORLD(0xB9783F85, RequestEnterWorldMsg.class, RequestEnterWorldHandler.class), // Request Enter World
+    EQUIP(0x3CB1AF8C, TransferItemFromInventoryToEquipMsg.class, null), // Transfer Item from Inventory to Equip
+    EXPERIENCE(0xC57802A7, GrantExperienceMsg.class, null), //TODO rename once identified
+    FORGETOBJECTS(0xE307A0E1, UnloadObjectsMsg.class, null), // Unload Objects
+    FRIENDACCEPT(0xCA297870,AcceptFriendMsg.class,FriendAcceptHandler.class),
+    FRIENDDECLINE(0xF08FC279,DeclineFriendMsg.class,FriendDeclineHandler.class),
+    FURNITURE(0xCE7FA503, FurnitureMsg.class, FurnitureHandler.class),
+    GAMESERVERIPRESPONSE(0x6C95CF87, GameServerIPResponseMsg.class, null), // Game Server IP Response
+    GLOBALCHANNELMESSAGE(0x2bf03fd2, null, null),
+    GOLDTOVAULT(0x3ABAEE49, TransferGoldFromInventoryToVaultMsg.class, null), // Transfer Gold from Inventory to Vault
+    GROUPDISBAND(0xE2B85AA4, DisbandGroupMsg.class, DisbandGroupHandler.class), //Disband Group
+    GROUPFOLLOW(0xC61B0476, FormationFollowMsg.class, FormationFollowHandler.class), //Toggle Follow, set Formation
+    GROUPLEADERAPPOINT(0xEF778DD3, AppointGroupLeaderMsg.class, AppointGroupLeaderHandler.class), //Appoint new group leader
+    GROUPREMOVE(0x6E50277C, RemoveFromGroupMsg.class, RemoveFromGroupHandler.class), //Remove from Group
+    GROUPTREASURE(0x01041C66, ToggleGroupSplitMsg.class, ToggleGroupSplitHandler.class), // Toggle Group Split
+    GUILDMEMBERONLINE(0x7B79EB3A, GuildEnterWorldMsg.class, null), // Send Enter World Message to Guild
+    GUILDRANKCHANGE(0x0DEFB21F, ChangeRankMsg.class, ChangeRankHandler.class), // Change Rank
+    GUILDTREESTATUS(0x4B95FB85, GuildTreeStatusMsg.class, null),
+    HIRELINGSERVICE(0xD3D93322,HirelingServiceMsg.class,HirelingServiceMsgHandler.class),
+    IGNORE(0xBD8881EE, IgnoreMsg.class, null), //client sent /ignore command
+    INITIATETRADEHUDS(0x667D29D8, OpenTradeWindowMsg.class, null), // Open Trade Window
+    INVITEGROUP(0x004A2012, GroupInviteMsg.class, GroupInviteHandler.class), // Send/Receive/Deny Group Invite
+    INVITEGUILDFEALTY(0x0274D612, InviteToSubMsg.class, InviteToSubHandler.class), // Invite Guild to Swear
+    INVITETOGUILD(0x6819062A, InviteToGuildMsg.class, InviteToGuildHandler.class), // Invite player to guild, refuse guild invite
+    ITEMHEALTHUPDATE(0xB635F55E, ItemHealthUpdateMsg.class, null), //Update Durability of item
+    ITEMPRODUCTION(0x3CCE8E30, ItemProductionMsg.class, ItemProductionMsgHandler.class),
+    ITEMTOVAULT(0x3ABE4927, TransferItemFromInventoryToVaultMsg.class, null), // Transfer Item to Vault
+    JOINFORPROVINCE(0x1FB369CD, AcceptSubInviteMsg.class, AcceptSubInviteHandler.class), //Response to invite to swear?
+    JOINFORSWORN(0xF6A4170F, null, null),
+    JOINGROUP(0x7EC5E636, GroupInviteResponseMsg.class, GroupInviteResponseHandler.class), // Accept Group Invite
+    JOINGUILD(0xF0C5F2FF, AcceptInviteToGuildMsg.class, AcceptInviteToGuildHandler.class), // Accept guild invite
+    KEEPALIVESERVERCLIENT(0x49EE129C, KeepAliveServerClientMsg.class, KeepAliveServerClientHandler.class), // Keep Alive
+    LEADERBOARD(0x6F0C1386, LeaderboardMessage.class, null),
+    LEADERCHANNELMESSAGE(0x17b306f9, ChatGlobalMsg.class, null),
+    LEAVEGROUP(0xD8037303, LeaveGroupMsg.class, LeaveGroupHandler.class), //Leave Group
+    LEAVEGUILD(0x1801EA32, LeaveGuildMsg.class, LeaveGuildHandler.class), // Leave Guild
+    LEAVEREQUEST(0xC79D775C, LeaveWorldMsg.class, null), //Client Request Leave World
+    LEAVEWORLD(0xB801EAEC, null, null), //Response to client for Request Leave World
+    LOADCHARACTER(0x5756BC53, null, null), // Load Player/NPC/Mob, other then self
+    LOADSTRUCTURE(0xB8A3A654, LoadStructureMsg.class, null), //Load Buildings and World Detail Objects
+    LOCKUNLOCKDOOR(0x8D0E8C44, LockUnlockDoorMsg.class, LockUnlockDoorMsgHandler.class), // Lock/Unlock Door
+    LOGIN(0x3D51E445, ClientLoginInfoMsg.class, null), // Login Information
+    LOGINFAILED(0x47B867F6, null, null), // Login Error
+    LOGINTOGAMESERVER(0x77910FDF, LoginToGameServerMsg.class, LoginToGameServerMsgHandler.class), // Login to Game Server
+    MANAGECITYASSETS(0xCFF01225, ManageCityAssetsMsg.class, ManageCityAssetMsgHandler.class), // Manage city assets
+    MANAGENPC(0x43A273FA, null, null), // Open Hireling Management Page
+    MERCHANT(0x3E645EF4, MerchantMsg.class, MerchantMsgHandler.class), // Open Teleport List, Teleport, Open Shrine, Request Boon, open/manage warehouse window
+    MINIONTRAINING(0xD355F528, MinionTrainingMessage.class, MinionTrainingMsgHandler.class),
+    MODIFYGUILDSTATE(0x38936FEA, ToggleLfgRecruitingMsg.class, null), //Toggle LFGroup/LFGuild/Recruiting
+    MOTD(0xEC841E8D, MOTDMsg.class, MOTDEditHandler.class), //Send/Rec Guild/Nation/IC MOTD Message
+    MOVECORRECTION(0x47FAD1E3, null, null), //Force move to point?
+    MOVEOBJECTTOCONTAINER(0xD1639F7C, LootMsg.class, null), //Send/Recv MoveObjectToContainer Msg
+    MOVETOPOINT(0x49EF7241, MoveToPointMsg.class, MoveToPointHandler.class), // Move to point
+    NAMEVERIFY(0x1B3BF0B1, null, null), // Invalid Name in Character Creation
+    NEWWORLD(0x982E4A77, WorldDataMsg.class, null), // World Data
+    OBJECTACTION(0x06855A36, ObjectActionMsg.class, ObjectActionMsgHandler.class), //Use item
+    OKCOSTTOOPENBANK(0x6F97A502, null, null),
+    OPENFRIENDSCONDEMNLIST(0x49E5FE4F, OpenFriendsCondemnListMsg.class, OpenFriendsCondemnListMsgHandler.class), // Friends/Con   demn/Kill/Death/Heraldry List
+    OPENVAULT(0xBE048E50, OpenVaultMsg.class, null), // Open Vault Window
+    ORDERNPC(0x61C707B1, OrderNPCMsg.class, OrderNPCMsgHandler.class),
+    PASSIVEMESSAGETRIGGER(0x2FF9E2E4, null, null), //PassiveMessageTriggerMsg
+    PET(0x624F3D8C, PetMsg.class, null), //Summon Pet?
+    PLACEASSET(0x940962DF, PlaceAssetMsg.class, PlaceAssetMsgHandler.class),
+    PLAYERDATA(0xB206D352, SendOwnPlayerMsg.class, null), //Enter World, Own Player Data
+    PLAYERFRIENDS(0xDDEF9E7D, FriendRequestMsg.class, FriendRequestHandler.class),
+    POWER(0x3C97A459, PerformActionMsg.class, null), // REQ / CMD Perform Action
+    POWERACTION(0xA0B27EEB, ApplyEffectMsg.class, null), // Apply Effect, add to effects icons
+    POWERACTIONDD(0xD43052F8, ModifyHealthMsg.class, null), //Modify Health/Mana/Stamina using power
+    POWERACTIONDDDIE(0xC27D446B, null, null), //Modify Health/Mana/Stamina using power and kill target
+    POWERTARGNAME(0x5A807CCE, SendSummonsRequestMsg.class, null), // Send Summons Request
+    RAISEATTR(0x5EEB65E0, ModifyStatMsg.class, null), // Modify Stat
+    RANDOM(0xAC5D0135, RandomMsg.class, null), //RequestSend random roll
+    READYTOENTER(0x490E4FE0, EnterWorldReceivedMsg.class, null), //Client Ack Receive Enter World
+    REALMDATA(0x2399B775, null, null),			//Realm Data - Optional(?)
+    RECOMMENDNATION(0x6D4579E9, RecommendNationMsg.class, RecommendNationMsgHandler.class), // Recommend as Ally/Enemy, error
+    RECYCLEPOWER(0x24033B67, RecyclePowerMsg.class, null), //Unlock power for reUse
+    REMOVECHAR(0x5D3F9739, DeleteCharacterMsg.class, null), // Delete Character
+    REMOVEFRIEND(0xE0D5DB42,RemoveFriendMessage.class,RemoveFriendHandler.class),
+    REPAIRBUILDING(0xAF8C2560, RepairBuildingMsg.class, RepairBuildingMsgHandler.class),
+    REPAIROBJECT(0x782219CE, RepairMsg.class, null), //Repair Window Req/Ack, RepairObject item Req/Ack
+    REQUESTCONTENTS(0xA786B0A2, LootWindowRequestMsg.class, null), // MoveObjectToContainer Window Request
+    REQUESTGUILDLIST(0x85DCC6D7, ReqGuildListMsg.class, RequestGuildListHandler.class),
+    REQUESTMELEEATTACK(0x98C71545, AttackCmdMsg.class, null), // Attack
+    REQUESTMEMBERLIST(0x3235E5EA, GuildControlMsg.class, GuildControlHandler.class), // Part of Promote/Demote, Also Player History
+    REQUESTTOOPENBANK(0xF26E453F, null, null), // RequestToOpenBankMsg
+    REQUESTTOTRADE(0x4D84259B, TradeRequestMsg.class, null), // Trade Request
+    REQUESTTRADECANCEL(0xCB0C5735, RejectTradeRequestMsg.class, null), // Reject RequestToTrade
+    REQUESTTRADEOK(0xFFD29841, AcceptTradeRequestMsg.class, null), // Accept Trade Request
+    RESETAFTERDEATH(0xFDCBB98F,RespawnMsg.class, null), //Respawn Request/Response
+    ROTATEMSG(0x57F2088E, RotateObjectMsg.class, null),
+    SAFEMODE(0x9CF3922A, SafeModeMsg.class, null), //Tell client they're in safe mode
+    SCALEOBJECT(0xE2B392D9, null, null), // Adjust scale of object
+    SELECTCHAR(0x7E6A9338, GameServerIPRequestMsg.class, null), // Game Server IP Request
+    SELECTCITY(0x7E6BE630, null, null),
+    SELECTSERVER(0x440D28B7, ServerInfoMsg.class, null), // Server Info Request/Response
+    SELLOBJECT(0x57111C67, SellToNPCMsg.class, null), //Sell to NPC
+    SENDCITYENTRY(0xBC3B5E72, null, null), //Send Teleport/Repledge List
+    SENDGUILDENTRY(0x6D5EF164, null, null),
+    SENDMEMBERENTRY(0x6949C720, GuildListMsg.class, GuildListHandler.class), // ShowCombatInfo guild members list, I think
+    SETITEMFLAG(0xE8C1B53B, null, null),
+    SETMOTD(0xFD21FC7C, MOTDCommitMsg.class, MOTDCommitHandler.class), //Commit Guild/Nation/IC MOTD Message
+    SETOBJVAL(0x08A50FD1, null, null),
+    SETRUNE(0x888E7C64, ApplyRuneMsg.class, null),  //Apply Promotion, Stat Rune (maybe disc also)
+    SETSELECTEDOBECT(0x64E10938, TargetObjectMsg.class, null), // Target an object
+    SHOPINFO(0x267DAB90, SellToNPCWindowMsg.class, null), //open Sell to NPC Window
+    SHOPLIST(0x682DAB4D, BuyFromNPCWindowMsg.class, null), // Open Buy From NPC Window
+    SHOWCOMBATINFO(0x9BF1E5EA, ShowMsg.class, null), // Request/Response /show
+    SHOWVAULTINVENTORY(0xD1FB4842, null, null), // Show Vault Inventory
+    SOCIALCHANNEL(0x2BF58FA6, SocialMsg.class, null), // Socials
+    STANDARDALERT(0xFA0A24BB, ErrorPopupMsg.class, null), //Popup messages
+    STUCK(0x3D04AF3A, StuckCommandMsg.class, null), // /Stuck Command
+    SWEARINGUILD(0x389B66B1, SwearInGuildMsg.class, SwearInGuildHandler.class),
+    SYNC(0x49ec109f, null, null), //Client/Server loc sync
+    SYSTEMBROADCASTCHANNEL(0x2FAD89D1, ChatSystemMsg.class, null), // Chat Channel: System Message
+    SYSTEMCHANNEL(0x29BB4D66, ChatSystemChannelMsg.class, null), // Chat System Channel
+    TARGETEDACTION(0xB79BA48F, TargetedActionMsg.class, null), //Message sent for attacks
+    TAXCITY(0xCD41EAA6, TaxCityMsg.class, TaxCityMsgHandler.class),
+    TAXRESOURCES(0x4AD458AF, TaxResourcesMsg.class, TaxResourcesMsgHandler.class),
+    TELEPORT(0x23E726EA, TeleportToPointMsg.class, null), // Teleport to point
+    TERRITORYCHANGE(0x6B388C8C,TerritoryChangeMessage.class, null), //Hey rich, look what I found? :)
+    TOGGLESITSTAND(0x624F3C0F, ToggleSitStandMsg.class, null), //Toggle Sit/Stand
+    TRADEADDGOLD(0x654ACB45, AddGoldToTradeWindowMsg.class, null), // Add Gold to Trade Window
+    TRADEADDOBJECT(0x55D363E9, AddItemToTradeWindowMsg.class, null), // Add an Item to the Trade Window
+    TRADECLOSE(0x5008D7FC, CloseTradeWindowMsg.class, null), // Cancel trade/ACK trade complete
+    TRADECONFIRM(0x6911E65E, CommitToTradeMsg.class, null), // Commit to trade
+    TRADECONFIRMSTATUS(0x9F85DAFC, null, null), // Other player commit/uncommit/add item
+    TRADEUNCONFIRM(0xEBE280E0, UncommitToTradeMsg.class, null), // Uncommit to trade
+    TRAINERLIST(0x41FABA62, TrainerInfoMsg.class, null), //Req/Send Trainer Info/Pricing
+    TRAINSKILL(0xB0BF68CD, TrainMsg.class, null), //Train skills/powers
+    TRANSFERASSET(0x3EA1C4C9, TransferAssetMsg.class, TransferAssetMsgHandler.class), // Transfer Building
+    TRANSFERGOLDFROMVAULTTOINVENTORY(0x011D0123, TransferGoldFromVaultToInventoryMsg.class, null), // Transfer Gold from Vault to Inventory
+    TRANSFERGOLDTOFROMBUILDING(0x1B1AC8C7, TransferGoldToFromBuildingMsg.class, TransferGoldToFromBuildingMsgHandler.class), // Transfer Gold to/From Building, Transfer Error
+    TRANSFERITEMFROMBANK(0x9D04977B, TransferItemFromBankToInventoryMsg.class, null), // Transfer Item from Bank to Inventory
+    TRANSFERITEMFROMVAULTTOINVENTORY(0x0119A64D, TransferItemFromVaultToInventoryMsg.class, null), // Transfer Item from Vault to Inventory
+    TRANSFERITEMTOBANK(0xD48C46FA, TransferItemFromInventoryToBankMsg.class, null), // Transfer Item from Inventory to Bank
+    UNEQUIP(0xC6BFB907, TransferItemFromEquipToInventoryMsg.class, null), // Transfer Item from Equip to Inventory
+    UNKNOWN(0x238C9259, UnknownMsg.class,null),
+    UPDATECHARORMOB(0xB6D78961, null, null),
+    UPDATECLIENTALLIANCES(0xF3FEB5D4, null, GuildUnknownHandler.class), //AlliancesMsg
+    UPDATECLIENTINVENTORIES(0xE66F533D, UpdateInventoryMsg.class, null), //Update player inventory
+    UPDATEEFFECTS(0xD4675293, null, null), //Update all effects for an item
+    UPDATEFRIENDSTATUS(0x654E2255, UpdateFriendStatusMessage.class, UpdateFriendStatusHandler.class),
+    UPDATEGOLDVALUE(0x6915A3FB, null, null), // Update gold in inventory and/or bank
+    UPDATEGROUP(0x004E6BCE, GroupUpdateMsg.class, GroupUpdateHandler.class), // Update Group Info
+    UPDATEGUILD(0x001D4DF6, GuildInfoMsg.class, GuildInfoHandler.class), // REQ / CMD Promote/Demote Screen
+    UPDATEOBJECT(0x1A724739, null, null),
+    UPDATESTATE(0x001A45FB, UpdateStateMsg.class, null), // REQ / CMD Toggle Run/Walk Sit/Stand :: UpdateStateMessage
+    UPDATETRADEWINDOW(0x406EBDE6, UpdateTradeWindowMsg.class, null), // Trade Complete
+    UPGRADEASSET(0x2B85A865, UpgradeAssetMessage.class, UpgradeAssetMsgHandler.class),
+    VENDORDIALOG(0x98ACD594, VendorDialogMsg.class, null), // Send/Recv Vendor Dialog
+    VERSIONINFO(0x4B7EE463, VersionInfoMsg.class, null), // Version Information
+    VIEWRESOURCES(0xCEFD0346, ViewResourcesMessage.class, null),
+    VISUALUPDATE(0x33402fd2, null, null),
+    WEIGHTINVENTORY(0xF1B6A85C, LootWindowResponseMsg.class, null), // MoveObjectToContainer Window Response
+    WHOREQUEST(0xF431CCE9, WhoRequestMsg.class, null), // Request /who
+    WHORESPONSE(0xD7C36568, WhoResponseMsg.class, null), // Response /who
+	REQUESTBALLLIST(0xE366FF64,RequestBallListMessage.class,RequestBallListHandler.class),
+	SENDBALLENTRY(0xAC2B5EDC,SendBallEntryMessage.class,SendBallEntryHandler.class),
+	UNKNOWN1(-263523523, Unknown1Msg.class,null),
+	DROPGOLD(1461654160,DropGoldMsg.class,null);
+
+    public int opcode;
+    private Class message;
+    private Class handlerClass;
+    public Constructor constructor;
+    public AbstractClientMsgHandler handler;
+
+    Protocol(int opcode, Class message, Class handlerClass) {
+        this.opcode = opcode;
+        this.message = message;
+        this.handlerClass = handlerClass;
+
+        // Create reference to message class constructor.
+
+        if (this.message != null) {
+            Class[] params = {AbstractConnection.class, ByteBufferReader.class};
+
+            try {
+                this.constructor = this.message.getConstructor(params);
+            } catch (NoSuchMethodException e) {
+                e.printStackTrace();
+            }
+        }
+
+       // Create instance of message handler for incoming protocol messages
+
+        if (this.handlerClass != null) {
+            try {
+                handler = (AbstractClientMsgHandler) handlerClass.newInstance();
+            } catch (InstantiationException | IllegalAccessException e) {
+                e.printStackTrace();
+            }
+        }
+    }
+
+    private static HashMap<Integer, Protocol> _protocolMsgByOpcode = new HashMap<>();
+
+    public static Protocol getByOpcode(int opcode) {
+
+        Protocol protocol = _protocolMsgByOpcode.get(opcode);
+
+        if (protocol != null)
+            return protocol;
+
+        return Protocol.NONE;
+    }
+
+    public static void initProtocolLookup() {
+
+        for (Protocol protocol : Protocol.values()) {
+
+        	if (_protocolMsgByOpcode.containsKey(protocol.opcode)){
+        		Logger.error("Duplicate opcodes for " + protocol.name() + " and " + _protocolMsgByOpcode.get(protocol.opcode).name());
+        	}
+            _protocolMsgByOpcode.put(protocol.opcode, protocol);
+        }
+    }
+
+
+public static int FindNextValidOpcode(ByteBufferReader reader){
+    int startPos = reader.position();
+    int bytesLeft = reader.remaining();
+
+    if (bytesLeft < 4)
+        return startPos;
+    int nextPos = startPos;
+    for (int i = 1; i< bytesLeft; i++ ){
+    reader.position(nextPos);
+    if (reader.remaining() < 4)
+        return reader.position();
+    int newOpcode = reader.getInt();
+
+    Protocol foundProtocol = Protocol.getByOpcode(newOpcode);
+    if (foundProtocol.equals(Protocol.NONE)){
+        nextPos += 1;
+        continue;
+    }
+
+    //found opcode. return position - 4 to rewind back to start of opcode, so we can handle it.
+        return reader.position() - 4;
+    }
+
+    return startPos;
+}
+}
diff --git a/src/engine/net/client/handlers/AbandonAssetMsgHandler.java b/src/engine/net/client/handlers/AbandonAssetMsgHandler.java
new file mode 100644
index 00000000..111a2319
--- /dev/null
+++ b/src/engine/net/client/handlers/AbandonAssetMsgHandler.java
@@ -0,0 +1,206 @@
+package engine.net.client.handlers;
+
+import engine.Enum;
+import engine.Enum.BuildingGroup;
+import engine.Enum.GuildState;
+import engine.exception.MsgSendException;
+import engine.gameManager.*;
+import engine.net.client.ClientConnection;
+import engine.net.client.msg.AbandonAssetMsg;
+import engine.net.client.msg.ClientNetMsg;
+import engine.net.client.msg.ErrorPopupMsg;
+import engine.objects.*;
+import org.pmw.tinylog.Logger;
+
+/*
+ * @Author:
+ * @Summary: Processes application protocol message which processes
+ * client requests to abandon a building.
+ */
+public class AbandonAssetMsgHandler extends AbstractClientMsgHandler {
+
+	// Instance variables
+
+	public AbandonAssetMsgHandler() {
+		super(AbandonAssetMsg.class);
+
+	}
+
+	@Override
+	protected boolean _handleNetMsg(ClientNetMsg baseMsg, ClientConnection origin) throws MsgSendException {
+
+		// Member variable declaration
+		PlayerCharacter player;
+		Building building;
+		AbandonAssetMsg msg;
+
+		// Member variable assignment
+		msg = (AbandonAssetMsg) baseMsg;
+
+		player = origin.getPlayerCharacter();
+		building = BuildingManager.getBuildingFromCache(msg.getUUID());
+
+		// Oops!  *** Refactor: Log error
+		if ((player == null) || (building == null))
+			return true;
+
+		// Early exit if object is not owned by the player
+		if (building.getOwnerUUID() != player.getObjectUUID())
+			return true;
+
+		// Cannot abandon a building without a blueprint.
+		// Players do not own rocks or shrubbery.
+		if (building.getBlueprintUUID() == 0)
+			return true;
+
+		// Players cannot abandon shrines
+
+		if ((building.getBlueprint().getBuildingGroup() == BuildingGroup.SHRINE)) {
+			ErrorPopupMsg.sendErrorMsg(player, "Cannot for to abandon shrine!");
+			return true;
+		}
+
+		if ((building.getBlueprint().getBuildingGroup() == BuildingGroup.MINE)) {
+			ErrorPopupMsg.sendErrorMsg(player, "Cannot abandon mine!");
+			return true;
+		}
+
+		if (Blueprint.isMeshWallPiece(building.getBlueprintUUID())) {
+			ErrorPopupMsg.sendErrorMsg(player, "Cannot for to abandon fortress asset!");
+			return true;
+		}
+
+		if ((building.getBlueprint().getBuildingGroup() == BuildingGroup.BARRACK)) {
+			ErrorPopupMsg.sendErrorMsg(player, "Cannot for to abandon fortress asset!");
+			return true;
+		}
+
+		if ((building.getBlueprint().getBuildingGroup() == BuildingGroup.BULWARK)) {
+			ErrorPopupMsg.sendErrorMsg(player, "Cannot for to abandon siege asset!");
+			return true;
+		}
+
+		if ((building.getBlueprint().getBuildingGroup() == BuildingGroup.SIEGETENT)) {
+			ErrorPopupMsg.sendErrorMsg(player, "Cannot for to abandon siege asset!");
+			return true;
+		}
+
+		if ((building.getBlueprint().getBuildingGroup() == BuildingGroup.BANESTONE)) {
+			ErrorPopupMsg.sendErrorMsg(player, "Cannot for to abandon banestone!");
+			return true;
+		}
+
+		// Trees require special handling beyond an individual building
+		if ((building.getBlueprint().getBuildingGroup() == BuildingGroup.TOL))
+			AbandonAllCityObjects(player, building);
+		else
+			AbandonSingleAsset(player, building);
+
+		return true;
+	}
+
+	private static void AbandonSingleAsset(PlayerCharacter sourcePlayer,
+                                           Building targetBuilding) {
+
+		// Transfer the building asset ownership and refresh all clients
+
+		DbManager.BuildingQueries.CLEAR_FRIENDS_LIST(targetBuilding.getObjectUUID());
+		targetBuilding.getFriends().clear();
+
+		// Clear protection status but only if a seige building
+
+        if (targetBuilding.getBlueprint().getBuildingGroup().equals(BuildingGroup.BULWARK) ||
+			targetBuilding.getBlueprint().getBuildingGroup().equals(BuildingGroup.SIEGETENT))
+				targetBuilding.setProtectionState(Enum.ProtectionState.NONE);
+
+		DbManager.BuildingQueries.CLEAR_CONDEMNED_LIST(targetBuilding.getObjectUUID());
+		targetBuilding.getCondemned().clear();
+		targetBuilding.setOwner(null);
+		targetBuilding.refreshGuild();
+
+	}
+
+	private void AbandonAllCityObjects(PlayerCharacter sourcePlayer,
+			Building targetBuilding) {
+		Guild sourceGuild;
+		Zone cityZone;
+
+		sourceGuild = sourcePlayer.getGuild();
+
+		if (sourceGuild == null)
+			return;
+
+		if (sourceGuild.getSubGuildList().size() > 0) {
+			ChatManager.chatCityError(sourcePlayer, "You Cannot abandon a nation city.");
+			return;
+		}
+		
+		
+
+		cityZone = ZoneManager.findSmallestZone(targetBuilding.getLoc());
+
+		// Can't abandon a tree not within a player city zone
+		if (cityZone.isPlayerCity() == false)
+			return;
+		
+		if (targetBuilding.getCity() == null)
+			return;
+		
+		if (targetBuilding.getCity().getBane() != null){
+			ErrorPopupMsg.sendErrorMsg(sourcePlayer, "Can't abandon Tree while a bane exists.");
+			return;
+		}
+
+		if (targetBuilding.getCity().hasBeenTransfered == true) {
+			ChatManager.chatCityError(sourcePlayer, "City can only be abandoned once per rebooting.");
+			return;
+		}
+
+		// Guild no longer owns his tree.
+		if (!DbManager.GuildQueries.SET_GUILD_OWNED_CITY(sourceGuild.getObjectUUID(), 0)) {
+			Logger.error("Failed to update Owned City to Database");
+			return;
+		}
+
+		sourceGuild.setCityUUID(0);
+		sourceGuild.setGuildState(GuildState.Errant);
+		sourceGuild.setNation(null);
+
+		// Transfer the city assets
+		TransferCityAssets(sourcePlayer, targetBuilding);
+
+		GuildManager.updateAllGuildTags(sourceGuild);
+		GuildManager.updateAllGuildBinds(sourceGuild, null);
+
+	}
+
+	private void TransferCityAssets(PlayerCharacter sourcePlayer,
+			Building cityTOL) {
+
+		Zone cityZone;
+
+		// Build list of buildings within this parent zone
+		cityZone = ZoneManager.findSmallestZone(cityTOL.getLoc());
+
+		for (Building cityBuilding : cityZone.zoneBuildingSet) {
+
+			Blueprint cityBlueprint;
+			cityBlueprint = cityBuilding.getBlueprint();
+
+			// Buildings without blueprints cannot be abandoned
+			if (cityBlueprint == null)
+				continue;
+
+			// Transfer ownership of valid city assets
+			if ((cityBlueprint.getBuildingGroup() == BuildingGroup.TOL)
+					|| (cityBlueprint.getBuildingGroup() == BuildingGroup.SPIRE)
+					|| (cityBlueprint.getBuildingGroup() == BuildingGroup.BARRACK)
+					|| (cityBlueprint.isWallPiece())
+					|| (cityBuilding.getBlueprint().getBuildingGroup() == BuildingGroup.SHRINE))
+				AbandonSingleAsset(sourcePlayer, cityBuilding);
+
+		}
+
+	}
+
+}
diff --git a/src/engine/net/client/handlers/AbstractClientMsgHandler.java b/src/engine/net/client/handlers/AbstractClientMsgHandler.java
new file mode 100644
index 00000000..fdc2153b
--- /dev/null
+++ b/src/engine/net/client/handlers/AbstractClientMsgHandler.java
@@ -0,0 +1,33 @@
+package engine.net.client.handlers;
+
+import engine.exception.MsgSendException;
+import engine.net.client.ClientConnection;
+import engine.net.client.msg.ClientNetMsg;
+
+/* @Summary: This is the abstract class from which all message handlers
+ *           for mainline application protocol derive.  Namely those
+ *           routed and executed via ClientMessageHandler.
+ */
+
+public abstract class AbstractClientMsgHandler {
+	private final Class<? extends ClientNetMsg> handler;
+	
+	public AbstractClientMsgHandler(Class<? extends ClientNetMsg> handler) {
+		this.handler = handler;
+	}
+	
+	public boolean handleNetMsg(ClientNetMsg msg) {
+            
+            boolean executionSucceded;
+                
+		try {
+			executionSucceded = _handleNetMsg(msg, (ClientConnection) msg.getOrigin());
+		} catch (MsgSendException e) {
+			e.printStackTrace();
+			executionSucceded = false;
+		}
+		
+		return executionSucceded;
+	}
+        
+protected abstract boolean _handleNetMsg(ClientNetMsg msg, ClientConnection origin) throws MsgSendException;}
diff --git a/src/engine/net/client/handlers/AcceptInviteToGuildHandler.java b/src/engine/net/client/handlers/AcceptInviteToGuildHandler.java
new file mode 100644
index 00000000..ff33c3e9
--- /dev/null
+++ b/src/engine/net/client/handlers/AcceptInviteToGuildHandler.java
@@ -0,0 +1,122 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+
+
+
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.handlers;
+
+import engine.Enum;
+import engine.Enum.GameObjectType;
+import engine.Enum.GuildHistoryType;
+import engine.exception.MsgSendException;
+import engine.gameManager.ChatManager;
+import engine.gameManager.DbManager;
+import engine.gameManager.GuildManager;
+import engine.gameManager.SessionManager;
+import engine.net.Dispatch;
+import engine.net.DispatchMessage;
+import engine.net.client.ClientConnection;
+import engine.net.client.msg.ClientNetMsg;
+import engine.net.client.msg.ErrorPopupMsg;
+import engine.net.client.msg.guild.AcceptInviteToGuildMsg;
+import engine.net.client.msg.guild.GuildInfoMsg;
+import engine.objects.Guild;
+import engine.objects.GuildHistory;
+import engine.objects.PlayerCharacter;
+import org.joda.time.DateTime;
+
+public class AcceptInviteToGuildHandler extends AbstractClientMsgHandler {
+
+	public AcceptInviteToGuildHandler() {
+		super(AcceptInviteToGuildMsg.class);
+	}
+
+	@Override
+	protected boolean _handleNetMsg(ClientNetMsg baseMsg, ClientConnection origin) throws MsgSendException {
+
+		PlayerCharacter player;
+		AcceptInviteToGuildMsg msg;
+		Guild guild;
+
+		msg = (AcceptInviteToGuildMsg) baseMsg;
+
+		// get PlayerCharacter of person accepting invite
+
+		player = SessionManager.getPlayerCharacter(origin);
+
+		if (player == null)
+			return true;
+
+		guild = (Guild) DbManager.getObject(GameObjectType.Guild, msg.getGuildUUID());
+
+
+		if (guild == null)
+			return true;
+
+		if (guild.getGuildType() == null){
+			ErrorPopupMsg.sendErrorPopup(player, GuildManager.NO_CHARTER_FOUND);
+			return true;
+		}
+
+		// verify they accepted for the correct guild
+
+		if (player.getLastGuildToInvite() != msg.getGuildUUID())
+			return true;
+
+		if ( (player.getGuild() != null) &&
+				(player.getGuild().isErrant() == false)) {
+			ChatManager.chatGuildError(player,
+					"You already belongs to a guild!");
+			return true;
+		}
+
+		// verify they are acceptable level for guild
+
+		if (player.getLevel() < guild.getRepledgeMin() || player.getLevel() > guild.getRepledgeMax())
+			return true;
+
+		// Add player to guild
+		player.setGuild(guild);
+
+		// Cleanup guild stuff
+		player.resetGuildStatuses();
+
+		Dispatch dispatch = Dispatch.borrow(player, msg);
+		DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+
+		DispatchMessage.sendToAllInRange(player, new GuildInfoMsg(player, guild, 2));
+
+		player.incVer();
+
+		//Add to guild History
+
+		if (player.getGuild() != null){
+			if (DbManager.GuildQueries.ADD_TO_GUILDHISTORY(player.getGuildUUID(), player, DateTime.now(), GuildHistoryType.JOIN)){
+				GuildHistory guildHistory = new GuildHistory(player.getGuildUUID(),player.getGuild().getName(),DateTime.now(), GuildHistoryType.JOIN) ;
+				player.getGuildHistory().add(guildHistory);
+			}
+		}
+
+		// Send guild join message
+
+		ChatManager.chatGuildInfo(player, player.getFirstName() + " has joined the guild");
+		return true;
+	}
+
+}
diff --git a/src/engine/net/client/handlers/AcceptSubInviteHandler.java b/src/engine/net/client/handlers/AcceptSubInviteHandler.java
new file mode 100644
index 00000000..38fbb3ef
--- /dev/null
+++ b/src/engine/net/client/handlers/AcceptSubInviteHandler.java
@@ -0,0 +1,106 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.handlers;
+
+import engine.Enum;
+import engine.Enum.GameObjectType;
+import engine.Enum.GuildState;
+import engine.exception.MsgSendException;
+import engine.gameManager.ChatManager;
+import engine.gameManager.DbManager;
+import engine.gameManager.SessionManager;
+import engine.net.Dispatch;
+import engine.net.DispatchMessage;
+import engine.net.client.ClientConnection;
+import engine.net.client.msg.ClientNetMsg;
+import engine.net.client.msg.ErrorPopupMsg;
+import engine.net.client.msg.guild.AcceptSubInviteMsg;
+import engine.objects.Guild;
+import engine.objects.GuildStatusController;
+import engine.objects.PlayerCharacter;
+
+import java.util.ArrayList;
+
+public class AcceptSubInviteHandler extends AbstractClientMsgHandler {
+
+	public AcceptSubInviteHandler() {
+		super(AcceptSubInviteMsg.class);
+	}
+
+	@Override
+	protected boolean _handleNetMsg(ClientNetMsg baseMsg, ClientConnection origin) throws MsgSendException {
+
+		AcceptSubInviteMsg msg = (AcceptSubInviteMsg) baseMsg;
+		PlayerCharacter sourcePlayer;
+		Guild sourceGuild;
+		Guild targetGuild;
+		Dispatch dispatch;
+
+		// get PlayerCharacter of person sending sub invite
+
+		sourcePlayer = SessionManager.getPlayerCharacter(origin);
+
+		if (sourcePlayer == null)
+			return true;
+
+		sourceGuild = sourcePlayer.getGuild();
+		targetGuild = (Guild) DbManager.getObject(GameObjectType.Guild, msg.guildUUID());
+
+		//must be source guild to sub to
+
+		if (targetGuild == null) {
+			ErrorPopupMsg.sendErrorPopup(sourcePlayer, 45); // Failure to swear guild
+			return true;
+		}
+		if (sourceGuild == null) {
+			ErrorPopupMsg.sendErrorPopup(sourcePlayer, 45); // Failure to swear guild
+			return true;
+		}
+		
+		if (sourceGuild.equals(targetGuild))
+			return true;
+
+		if (GuildStatusController.isGuildLeader(sourcePlayer.getGuildStatus()) == false) {
+			ErrorPopupMsg.sendErrorMsg(sourcePlayer, "Only a guild leader can accept fealty!");
+			return true;
+		}
+
+		//source guild is limited to 7 subs
+		//TODO this should be based on TOL rank
+
+		if (!targetGuild.canSubAGuild(sourceGuild)) {
+			ErrorPopupMsg.sendErrorPopup(sourcePlayer, 45); // Failure to swear guild
+			return true;
+		}
+
+		//all tests passed, let's Handle code
+		//Update Target Guild State.
+
+		sourceGuild.upgradeGuildState(false);
+
+		//Add sub so GuildMaster can Swear in.
+
+		ArrayList<Guild> subs = targetGuild.getSubGuildList();
+		subs.add(sourceGuild);
+
+		targetGuild.setGuildState(GuildState.Nation);
+
+
+		//Let's send the message back.
+
+		msg.setUnknown02(1);
+		msg.setResponse("Your guild is now a " + sourceGuild.getGuildState().name() + '.');
+		dispatch = Dispatch.borrow(sourcePlayer, msg);
+		DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+
+		ChatManager.chatSystemInfo(sourcePlayer, "Your guild is now a " + sourceGuild.getGuildState().name() + '.');
+		return true;
+	}
+}
diff --git a/src/engine/net/client/handlers/ActivateNPCMsgHandler.java b/src/engine/net/client/handlers/ActivateNPCMsgHandler.java
new file mode 100644
index 00000000..ad204bfd
--- /dev/null
+++ b/src/engine/net/client/handlers/ActivateNPCMsgHandler.java
@@ -0,0 +1,136 @@
+package engine.net.client.handlers;
+
+import engine.Enum;
+import engine.Enum.ItemType;
+import engine.exception.MsgSendException;
+import engine.gameManager.*;
+import engine.math.Vector3fImmutable;
+import engine.net.Dispatch;
+import engine.net.DispatchMessage;
+import engine.net.client.ClientConnection;
+import engine.net.client.msg.ActivateNPCMessage;
+import engine.net.client.msg.ClientNetMsg;
+import engine.net.client.msg.ManageCityAssetsMsg;
+import engine.objects.*;
+import org.pmw.tinylog.Logger;
+
+import java.util.ArrayList;
+
+/*
+ * @Author:
+ * @Summary: Processes application protocol message which keeps
+ * client's tcp connection open.
+ */
+public class ActivateNPCMsgHandler extends AbstractClientMsgHandler {
+
+	public ActivateNPCMsgHandler() {
+		super(ActivateNPCMessage.class);
+	}
+
+	@Override
+	protected boolean _handleNetMsg(ClientNetMsg baseMsg, ClientConnection origin) throws MsgSendException {
+
+		ActivateNPCMessage msg;
+		PlayerCharacter player;
+		Building building;
+		Contract contract;
+		CharacterItemManager itemMan;
+		Zone zone;
+
+		msg = (ActivateNPCMessage) baseMsg;
+		player = SessionManager.getPlayerCharacter(origin);
+		building =  BuildingManager.getBuildingFromCache(msg.buildingUUID());
+
+		if (player == null || building == null)
+			return false;
+
+		ArrayList<Item> ItemLists = new ArrayList<>();
+
+		// Filter hirelings by slot type
+
+		for (Item hirelings : player.getInventory()) {
+			if (hirelings.getItemBase().getType().equals(ItemType.CONTRACT)) {
+				contract = DbManager.ContractQueries.GET_CONTRACT(hirelings.getItemBase().getUUID());
+				if (contract == null)
+					continue;
+				if (contract.canSlotinBuilding(building))
+					ItemLists.add(hirelings);
+			}
+		}
+
+		if (msg.getUnknown01() == 1) {
+			//Request npc list to slot
+			ActivateNPCMessage anm = new ActivateNPCMessage();
+			anm.setSize(ItemLists.size());
+			anm.setItemList(ItemLists);
+			Dispatch dispatch = Dispatch.borrow(player, anm);
+			DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+		}
+
+		if (msg.getUnknown01() == 0) {
+
+			//Slot npc
+
+			if (building.getBlueprintUUID() == 0) {
+				ChatManager.chatSystemError(player, "Unable to load Blueprint for Building Mesh " + building.getMeshUUID());
+				return false;
+			}
+
+			if (building.getBlueprint().getMaxSlots() == building.getHirelings().size())
+				return false;
+
+			Vector3fImmutable NpcLoc = new Vector3fImmutable(building.getLoc());
+
+			Item contractItem = Item.getFromCache(msg.getUnknown04());
+
+			if (contractItem == null)
+				return false;
+
+			if (!player.getCharItemManager().doesCharOwnThisItem(contractItem.getObjectUUID())) {
+				Logger.error(player.getName() + "has attempted to place Hireling : " + contractItem.getName() + "without a valid contract!");
+				return false;
+			}
+
+			itemMan = player.getCharItemManager();
+
+			zone = ZoneManager.findSmallestZone(NpcLoc);
+
+			if (zone == null)
+				return false;
+
+			contract = DbManager.ContractQueries.GET_CONTRACT(contractItem.getItemBase().getUUID());
+
+			if (contract == null)
+				return false;
+
+			// Check if contract can be slotted in this building
+
+			if (contract.canSlotinBuilding(building) == false)
+				return false;
+
+			if (!BuildingManager.addHireling(building, player, NpcLoc, zone, contract, contractItem))
+				return false;
+
+			itemMan.delete(contractItem);
+			itemMan.updateInventory();
+
+			ManageCityAssetsMsg mca1 = new ManageCityAssetsMsg(player, building);
+
+			mca1.actionType = 3;
+
+			mca1.setTargetType(building.getObjectType().ordinal());
+			mca1.setTargetID(building.getObjectUUID());
+			mca1.setTargetType3(building.getObjectType().ordinal());
+			mca1.setTargetID3(building.getObjectUUID());
+			mca1.setAssetName1(building.getName());
+			mca1.setUnknown54(1);
+			Dispatch dispatch = Dispatch.borrow(player, mca1);
+			DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+
+
+		}
+
+		return true;
+	}
+
+}
diff --git a/src/engine/net/client/handlers/AllianceChangeMsgHandler.java b/src/engine/net/client/handlers/AllianceChangeMsgHandler.java
new file mode 100644
index 00000000..db918d6d
--- /dev/null
+++ b/src/engine/net/client/handlers/AllianceChangeMsgHandler.java
@@ -0,0 +1,161 @@
+package engine.net.client.handlers;
+
+import engine.Enum;
+import engine.Enum.AllianceType;
+import engine.exception.MsgSendException;
+import engine.gameManager.SessionManager;
+import engine.net.Dispatch;
+import engine.net.DispatchMessage;
+import engine.net.client.ClientConnection;
+import engine.net.client.msg.AllianceChangeMsg;
+import engine.net.client.msg.ClientNetMsg;
+import engine.objects.Guild;
+import engine.objects.GuildStatusController;
+import engine.objects.PlayerCharacter;
+
+/*
+ * @Author:
+ * @Summary: Processes application protocol message which handles
+ * protecting and unprotecting city assets
+ */
+public class AllianceChangeMsgHandler extends AbstractClientMsgHandler {
+
+	public AllianceChangeMsgHandler() {
+		super(AllianceChangeMsg.class);
+	}
+
+	@Override
+	protected boolean _handleNetMsg(ClientNetMsg baseMsg, ClientConnection origin) throws MsgSendException {
+
+		// Member variable declaration
+
+		PlayerCharacter player;
+		AllianceChangeMsg msg;
+
+
+		// Member variable assignment
+
+		msg = (AllianceChangeMsg) baseMsg;
+
+		player = SessionManager.getPlayerCharacter(origin);
+
+		if (player == null)
+			return true;
+
+
+
+		Guild toGuild = null;
+		toGuild = Guild.getGuild(msg.getSourceGuildID());
+		if (toGuild.isErrant())
+			return true;
+
+		if (player.getGuild().isErrant())
+			return true;
+
+
+
+		switch (msg.getMsgType()){
+		case AllianceChangeMsg.MAKE_ALLY:
+		case 1: //allyfromRecommended
+			player.getGuild().addGuildToAlliance(msg, AllianceType.Ally, toGuild, player);
+			break;
+		case AllianceChangeMsg.MAKE_ENEMY:
+		case 2: //enemy recommend
+			player.getGuild().addGuildToAlliance(msg, AllianceType.Enemy, toGuild, player);
+			break;
+		case 3:
+		case 5:
+		case 7:
+			player.getGuild().removeGuildFromAllAlliances(toGuild);
+			break;
+
+		}
+		msg.setMsgType(AllianceChangeMsg.INFO_SUCCESS);
+		Dispatch dispatch = Dispatch.borrow(player, msg);
+		DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+
+
+
+
+
+
+
+		return true;
+
+	}
+
+	private static void MakeEnemy(Guild fromGuild, Guild toGuild, AllianceChangeMsg msg, ClientConnection origin) {
+
+		// Member variable declaration
+		Dispatch dispatch;
+
+		// Member variable assignment
+
+		if (fromGuild == null)
+			return;
+
+		if (toGuild == null)
+			return;
+
+		if (!Guild.sameGuild(origin.getPlayerCharacter().getGuild(), fromGuild)){
+			msg.setMsgType(AllianceChangeMsg.ERROR_NOT_SAME_GUILD);
+			dispatch = Dispatch.borrow(origin.getPlayerCharacter(), msg);
+			DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+			return;
+		}
+
+		if (!GuildStatusController.isInnerCouncil(origin.getPlayerCharacter().getGuildStatus()) && !GuildStatusController.isGuildLeader(origin.getPlayerCharacter().getGuildStatus())){
+			msg.setMsgType(AllianceChangeMsg.ERROR_NOT_AUTHORIZED);
+			dispatch = Dispatch.borrow(origin.getPlayerCharacter(), msg);
+			DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+			return;
+		}
+
+
+		dispatch = Dispatch.borrow(origin.getPlayerCharacter(), msg);
+		DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+
+
+	}
+
+	private static void makeAlly(Guild fromGuild, Guild toGuild, AllianceChangeMsg msg, ClientConnection origin) {
+
+		// Member variable declaration
+		Dispatch dispatch;
+
+		// Member variable assignment
+
+		if (fromGuild == null)
+			return;
+
+		if (toGuild == null)
+			return;
+
+		dispatch = Dispatch.borrow(origin.getPlayerCharacter(), msg);
+		DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+
+
+	}
+
+	private static void removeFromAlliance(Guild fromGuild, Guild toGuild, AllianceChangeMsg msg, ClientConnection origin) {
+
+		// Member variable declaration
+		Dispatch dispatch;
+
+		// Member variable assignment
+
+		if (fromGuild == null)
+			return;
+
+		if (toGuild == null)
+			return;
+
+		dispatch = Dispatch.borrow(origin.getPlayerCharacter(), msg);
+		DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+
+
+	}
+
+
+
+}
diff --git a/src/engine/net/client/handlers/AllyEnemyListMsgHandler.java b/src/engine/net/client/handlers/AllyEnemyListMsgHandler.java
new file mode 100644
index 00000000..b13bde9b
--- /dev/null
+++ b/src/engine/net/client/handlers/AllyEnemyListMsgHandler.java
@@ -0,0 +1,81 @@
+package engine.net.client.handlers;
+
+import engine.Enum;
+import engine.exception.MsgSendException;
+import engine.gameManager.SessionManager;
+import engine.net.Dispatch;
+import engine.net.DispatchMessage;
+import engine.net.client.ClientConnection;
+import engine.net.client.msg.AllyEnemyListMsg;
+import engine.net.client.msg.ClientNetMsg;
+import engine.objects.Guild;
+import engine.objects.PlayerCharacter;
+
+/*
+ * @Author:
+ * @Summary: Processes application protocol message which handles
+ * protecting and unprotecting city assets
+ */
+public class AllyEnemyListMsgHandler extends AbstractClientMsgHandler {
+
+	public AllyEnemyListMsgHandler() {
+		super(AllyEnemyListMsg.class);
+	}
+
+	@Override
+	protected boolean _handleNetMsg(ClientNetMsg baseMsg, ClientConnection origin) throws MsgSendException {
+
+		// Member variable declaration
+
+		PlayerCharacter player;
+		AllyEnemyListMsg msg;
+
+
+		// Member variable assignment
+
+		msg = (AllyEnemyListMsg) baseMsg;
+
+		player = SessionManager.getPlayerCharacter(origin);
+
+		if (player == null)
+			return true;
+
+
+		AllyEnemyListMsgHandler.showAllyEnemyList(player.getGuild(), Guild.getGuild(msg.getGuildID()), msg, origin);
+
+
+
+
+		//		dispatch = Dispatch.borrow(player, baseMsg);
+		//		DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+
+		return true;
+
+	}
+
+	private static void showAllyEnemyList(Guild fromGuild, Guild toGuild, AllyEnemyListMsg msg, ClientConnection origin) {
+
+		// Member variable declaration
+		Dispatch dispatch;
+
+		// Member variable assignment
+
+		if (fromGuild == null)
+			return;
+
+		if (toGuild == null)
+			return;
+		dispatch = Dispatch.borrow(origin.getPlayerCharacter(), msg);
+		DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+
+		//		UpdateClientAlliancesMsg ucam = new UpdateClientAlliancesMsg();
+		//
+		//		dispatch = Dispatch.borrow(origin.getPlayerCharacter(), ucam);
+		//		DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+
+
+	}
+
+
+
+}
diff --git a/src/engine/net/client/handlers/AppointGroupLeaderHandler.java b/src/engine/net/client/handlers/AppointGroupLeaderHandler.java
new file mode 100644
index 00000000..b7e1f42f
--- /dev/null
+++ b/src/engine/net/client/handlers/AppointGroupLeaderHandler.java
@@ -0,0 +1,85 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.handlers;
+
+import engine.exception.MsgSendException;
+import engine.gameManager.ChatManager;
+import engine.gameManager.GroupManager;
+import engine.gameManager.SessionManager;
+import engine.net.Dispatch;
+import engine.net.DispatchMessage;
+import engine.net.client.ClientConnection;
+import engine.net.client.msg.ClientNetMsg;
+import engine.net.client.msg.group.AppointGroupLeaderMsg;
+import engine.net.client.msg.group.GroupUpdateMsg;
+import engine.objects.Group;
+import engine.objects.PlayerCharacter;
+
+public class AppointGroupLeaderHandler extends AbstractClientMsgHandler {
+
+    public AppointGroupLeaderHandler() {
+        super(AppointGroupLeaderMsg.class);
+    }
+
+    @Override
+    protected boolean _handleNetMsg(ClientNetMsg baseMsg, ClientConnection origin) throws MsgSendException {
+        AppointGroupLeaderMsg msg = (AppointGroupLeaderMsg) baseMsg;
+
+        PlayerCharacter source = SessionManager.getPlayerCharacter(origin);
+        if (source == null) {
+            return false;
+        }
+        Group group = GroupManager.getGroup(source);
+        if (group == null) {
+            return false;
+        }
+        if (group.getGroupLead() != source) {
+            return false;
+        }
+        PlayerCharacter target = SessionManager.getPlayerCharacterByID(msg.getTargetID());
+        if (target == null) {
+            return false;
+        }
+        if (target == source) { // Can't appoint self leader
+            AppointGroupLeaderMsg reply = new AppointGroupLeaderMsg();
+            reply.setResponse(1);
+            Dispatch dispatch = Dispatch.borrow(origin.getPlayerCharacter(), reply);
+            DispatchMessage.dispatchMsgDispatch(dispatch, engine.Enum.DispatchChannel.SECONDARY);
+            return false;
+        }
+
+        // Change Group Leader
+        if (!group.setGroupLead(target.getObjectUUID())) {
+            return false; // failed to update group leader
+        }
+        // Refresh everyones group list
+        GroupUpdateMsg gim = new GroupUpdateMsg();
+        gim.setGroup(group);
+        gim.setPlayer(target);
+        gim.setMessageType(2);
+        gim.addPlayer(source);
+
+        group.sendUpdate(gim);
+
+        // Disable Formation
+        target.setFollow(false);
+        gim = new GroupUpdateMsg();
+        gim.setGroup(group);
+        gim.setPlayer(target);
+        gim.setMessageType(8);
+        group.sendUpdate(gim);
+
+        String text = target.getFirstName() + " is the new group leader.";
+        ChatManager.chatGroupInfo(source, text);
+
+        return true;
+    }
+
+}
diff --git a/src/engine/net/client/handlers/ArcLoginNotifyMsgHandler.java b/src/engine/net/client/handlers/ArcLoginNotifyMsgHandler.java
new file mode 100644
index 00000000..dd951737
--- /dev/null
+++ b/src/engine/net/client/handlers/ArcLoginNotifyMsgHandler.java
@@ -0,0 +1,141 @@
+package engine.net.client.handlers;
+
+import engine.Enum.DispatchChannel;
+import engine.exception.MsgSendException;
+import engine.gameManager.*;
+import engine.job.JobScheduler;
+import engine.net.Dispatch;
+import engine.net.DispatchMessage;
+import engine.net.client.ClientConnection;
+import engine.net.client.msg.ArcLoginNotifyMsg;
+import engine.net.client.msg.ClientNetMsg;
+import engine.net.client.msg.HotzoneChangeMsg;
+import engine.net.client.msg.PetMsg;
+import engine.objects.*;
+import engine.server.MBServerStatics;
+import engine.session.Session;
+import org.pmw.tinylog.Logger;
+
+public class ArcLoginNotifyMsgHandler extends AbstractClientMsgHandler {
+
+	public ArcLoginNotifyMsgHandler() {
+		super(ArcLoginNotifyMsg.class);
+	}
+
+	@Override
+	protected boolean _handleNetMsg(ClientNetMsg baseMsg, ClientConnection origin) throws MsgSendException {
+
+		PlayerCharacter player = SessionManager.getPlayerCharacter(origin);
+
+		if (player == null) {
+			Logger.error(ConfigManager.MB_WORLD_NAME.getValue()+ ".EnterWorld", "Unable to find player for session");
+			origin.kickToLogin(MBServerStatics.LOGINERROR_UNABLE_TO_LOGIN, "Player not found.");
+			return true;
+		}
+
+		// cancel logout Timer if exists
+		if (player.getTimers().containsKey("Logout")) {
+
+			JobScheduler.getInstance().cancelScheduledJob(player.getTimers().get("Logout"));
+			player.getTimers().remove("Logout");
+		}
+		player.setTimeStamp("logout", 0);
+
+		// refresh group window if still in group for both this player
+		// and everyone else in the group
+
+		if (GroupManager.getGroup(player) != null) {
+			GroupManager.RefreshMyGroupList(player, origin);
+			GroupManager.RefreshOthersGroupList(player);
+		}
+
+		player.setEnteredWorld(true);
+		// Set player active
+		player.resetRegenUpdateTime();
+		player.setActive(true);
+
+		//player.sendAllEffects(player.getClientConnection());
+		// Send Enter world message to guild
+
+		ChatManager.GuildEnterWorldMsg(player, origin);
+
+		// Send Guild, Nation and IC MOTD
+		GuildManager.enterWorldMOTD(player);
+		ChatManager.sendSystemMessage(player, ConfigManager.MB_WORLD_GREETING.getValue());
+
+		// Set player mask for QT
+		if (player.getRace() != null && player.getRace().getToken() == -524731385)
+			player.setObjectTypeMask(MBServerStatics.MASK_PLAYER | MBServerStatics.MASK_UNDEAD);
+		else
+			player.setObjectTypeMask(MBServerStatics.MASK_PLAYER);
+
+		// If player not already in world, then set them to bind loc and add
+		// to world
+
+		if (player.newChar)
+			player.newChar = false; // TODO Fix safe mode
+
+		// PowersManager.applyPower(player, player, new
+		// Vector3f(0f, 0f, 0f), -1661758934, 50, false);
+
+		// Add player to the QT for tracking
+
+		player.setLoc(player.getLoc());
+
+		//send online status to friends.
+		PlayerFriends.SendFriendsStatus(player, true);
+
+		// Handle too many simultaneous logins from the same forum account by disconnecting the other account(s)
+
+		Account thisAccount = SessionManager.getAccount(player);
+		int maxAccounts = MBServerStatics.MAX_ACTIVE_GAME_ACCOUNTS_PER_DISCORD_ACCOUNT;
+
+		if (maxAccounts > 0) {
+
+			int count = 1;
+			for (Account othAccount : SessionManager.getAllActiveAccounts()) {
+
+				if (othAccount.equals(thisAccount))
+					continue;
+
+				if (thisAccount.discordAccount.equals(othAccount.discordAccount) == false)
+					continue;
+
+				count++;
+
+				if (count > maxAccounts) {
+					Session otherSession = SessionManager.getSession(othAccount);
+					if (otherSession != null) {
+						ClientConnection otherConn = otherSession.getConn();
+						if (otherConn != null) {
+							ChatManager.chatSystemInfo(player, "Only 4 accounts may be used simultaneously. Account '" + othAccount.getUname() + "' has been disconnected.");
+							otherConn.disconnect();
+						}
+					}
+				}
+			}
+		}
+
+		player.setTimeStamp("logout", 0);
+
+		if (player.getPet() != null) {
+			PetMsg pm = new PetMsg(5, player.getPet());
+			Dispatch dispatch = Dispatch.borrow(player, pm);
+			DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
+		}
+
+		//Send current hotzone
+		Zone hotzone = ZoneManager.getHotZone();
+
+		if (hotzone != null) {
+			HotzoneChangeMsg hcm = new HotzoneChangeMsg(hotzone.getObjectType().ordinal(), hotzone.getObjectUUID());
+			Dispatch dispatch = Dispatch.borrow(player, hcm);
+			DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
+		}
+
+		if (player.getGuild() != null && !player.getGuild().isErrant()) {
+			Guild.UpdateClientAlliancesForPlayer(player);
+		}
+		return true;
+	}
+}
\ No newline at end of file
diff --git a/src/engine/net/client/handlers/ArcSiegeSpireMsgHandler.java b/src/engine/net/client/handlers/ArcSiegeSpireMsgHandler.java
new file mode 100644
index 00000000..9c2b7ed0
--- /dev/null
+++ b/src/engine/net/client/handlers/ArcSiegeSpireMsgHandler.java
@@ -0,0 +1,120 @@
+package engine.net.client.handlers;
+
+import engine.Enum.GameObjectType;
+import engine.exception.MsgSendException;
+import engine.gameManager.DbManager;
+import engine.gameManager.SessionManager;
+import engine.net.client.ClientConnection;
+import engine.net.client.msg.ArcSiegeSpireMsg;
+import engine.net.client.msg.ClientNetMsg;
+import engine.net.client.msg.ErrorPopupMsg;
+import engine.objects.Building;
+import engine.objects.GuildStatusController;
+import engine.objects.PlayerCharacter;
+import org.pmw.tinylog.Logger;
+
+/*
+ * @Author:
+ * @Summary: Processes application protocol message which requests that
+ * siege spires be toggled on or off.
+ */
+
+public class ArcSiegeSpireMsgHandler extends AbstractClientMsgHandler {
+
+	public ArcSiegeSpireMsgHandler() {
+		super(ArcSiegeSpireMsg.class);
+	}
+
+	@Override
+	protected boolean _handleNetMsg(ClientNetMsg baseMsg, ClientConnection origin) throws MsgSendException {
+
+		PlayerCharacter player;
+		Building spire;
+		ArcSiegeSpireMsg msg;
+
+		msg = (ArcSiegeSpireMsg) baseMsg;
+
+		player = SessionManager.getPlayerCharacter(origin);
+
+		if (player == null)
+			return true;
+
+		spire = (Building) DbManager.getObject(GameObjectType.Building, msg.getBuildingUUID());
+
+		if (spire == null)
+			return true;
+
+		if (spire.getCity() == null)
+			return true;
+		
+		spire.getCity().transactionLock.writeLock().lock();
+		
+		try{
+			
+		
+		//can't activate a spire that's not rank 1.
+
+		if (spire.getRank() < 1)
+			return true;
+
+		// can't activate a spire without a city
+
+		if (spire.getCity() == null)
+			return true;
+
+		// Must have management authority for the spire
+
+		if ((player.getGuild().equals(spire.getGuild()) == false)
+				|| (GuildStatusController.isInnerCouncil(player.getGuildStatus()) == false) )
+			return true;
+
+		// Handle case where spire is sabotaged
+
+		if (spire.getTimeStamp("DISABLED") > System.currentTimeMillis()) {
+			ErrorPopupMsg.sendErrorPopup(player, 174); //This siege spire cannot be toggled yet.
+			return true;
+		}
+
+		// Handle case where spire's toggle delay hasn't yet passed
+
+		if (spire.getTimeStamp("TOGGLEDELAY") > System.currentTimeMillis()) {
+			ErrorPopupMsg.sendErrorPopup(player, 174); //This siege spire cannot be toggled yet.
+			return true;
+		}
+
+		// This protocol message is a toggle.  If it's currently active then disable
+		// the spire.
+
+		if (spire.isSpireIsActive()) {
+			spire.disableSpire(false);
+			return true;
+		}
+
+		// Must be enough gold on the spire to turn it on
+
+		if (!spire.hasFunds(5000)){
+			ErrorPopupMsg.sendErrorPopup(player, 127); // Not enough gold in strongbox
+			return true;
+		}
+
+		if (spire.getStrongboxValue() < 5000) {
+			ErrorPopupMsg.sendErrorPopup(player, 127); // Not enough gold in strongbox
+			return true;
+		}
+
+		spire.transferGold(-5000,false);
+		spire.enableSpire();
+
+		// Spire is now enabled.  Reset the toggle delay
+
+		spire.setTimeStamp("TOGGLEDELAY", System.currentTimeMillis() + (long) 10 * 60 * 1000);
+		}catch(Exception e){
+			Logger.error(e);
+		}finally{
+			spire.getCity().transactionLock.writeLock().unlock();
+		}
+		return true;
+		
+	}
+
+}
diff --git a/src/engine/net/client/handlers/ArcViewAssetTransactionsMsgHandler.java b/src/engine/net/client/handlers/ArcViewAssetTransactionsMsgHandler.java
new file mode 100644
index 00000000..c2d0d952
--- /dev/null
+++ b/src/engine/net/client/handlers/ArcViewAssetTransactionsMsgHandler.java
@@ -0,0 +1,54 @@
+package engine.net.client.handlers;
+
+import engine.Enum;
+import engine.exception.MsgSendException;
+import engine.gameManager.SessionManager;
+import engine.net.Dispatch;
+import engine.net.DispatchMessage;
+import engine.net.client.ClientConnection;
+import engine.net.client.msg.ArcViewAssetTransactionsMsg;
+import engine.net.client.msg.ClientNetMsg;
+import engine.objects.PlayerCharacter;
+import engine.objects.Warehouse;
+
+/*
+ * @Author:
+ * @Summary: Processes application protocol message which transfers
+ * gold between a building's strongbox and a player character.
+ */
+
+public class ArcViewAssetTransactionsMsgHandler extends AbstractClientMsgHandler {
+
+    public ArcViewAssetTransactionsMsgHandler() {
+        super(ArcViewAssetTransactionsMsg.class);
+    }
+
+    @Override
+    protected boolean _handleNetMsg(ClientNetMsg baseMsg, ClientConnection origin) throws MsgSendException {
+
+        PlayerCharacter player;
+        ArcViewAssetTransactionsMsg msg;
+        ArcViewAssetTransactionsMsg newMsg;
+        player = SessionManager.getPlayerCharacter(origin);
+        Dispatch dispatch;
+
+        if (player == null)
+            return true;
+
+        msg = (ArcViewAssetTransactionsMsg) baseMsg;
+
+        Warehouse warehouse = Warehouse.warehouseByBuildingUUID.get(msg.getWarehouseID());
+
+        if (warehouse == null)
+        	return true;
+        
+        newMsg = new ArcViewAssetTransactionsMsg(warehouse,msg);
+        newMsg.configure();
+
+        dispatch = Dispatch.borrow(player, newMsg);
+        DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+
+        return true;
+    }
+
+}
diff --git a/src/engine/net/client/handlers/AssetSupportMsgHandler.java b/src/engine/net/client/handlers/AssetSupportMsgHandler.java
new file mode 100644
index 00000000..561aaa0f
--- /dev/null
+++ b/src/engine/net/client/handlers/AssetSupportMsgHandler.java
@@ -0,0 +1,240 @@
+package engine.net.client.handlers;
+
+import engine.Enum;
+import engine.Enum.SupportMsgType;
+import engine.Enum.TaxType;
+import engine.exception.MsgSendException;
+import engine.gameManager.BuildingManager;
+import engine.gameManager.SessionManager;
+import engine.net.Dispatch;
+import engine.net.DispatchMessage;
+import engine.net.client.ClientConnection;
+import engine.net.client.msg.*;
+import engine.objects.*;
+import org.pmw.tinylog.Logger;
+
+/*
+ * @Author:
+ * @Summary: Processes application protocol message which handles
+ * protecting and unprotecting city assets
+ */
+public class AssetSupportMsgHandler extends AbstractClientMsgHandler {
+
+	public AssetSupportMsgHandler() {
+		super(AssetSupportMsg.class);
+	}
+
+	@Override
+	protected boolean _handleNetMsg(ClientNetMsg baseMsg, ClientConnection origin) throws MsgSendException {
+
+		// Member variable declaration
+
+		PlayerCharacter player;
+		NPC vendor;
+		Building targetBuilding;
+		Dispatch dispatch;
+
+		AssetSupportMsg msg;
+		CityAssetMsg outMsg;
+
+		// Member variable assignment
+
+		msg = (AssetSupportMsg) baseMsg;
+
+		player = SessionManager.getPlayerCharacter(origin);
+
+		if (player == null)
+			return true;
+
+		vendor = NPC.getFromCache(msg.getNpcID());
+
+		if (msg.getMessageType() !=6 && msg.getMessageType() != 7){
+			if (vendor == null)
+				return true;
+
+			vendor.getBuilding();
+
+			if (vendor.getBuilding() == null)
+				return true;
+		}
+
+
+	SupportMsgType supportType = SupportMsgType.typeLookup.get(msg.getMessageType());
+
+		if (supportType == null) {
+			supportType = Enum.SupportMsgType.NONE;
+			Logger.error("No enumeration for support type" + msg.getMessageType());
+		}
+		switch (supportType) {
+
+		case PROTECT:
+			targetBuilding =  BuildingManager.getBuildingFromCache(msg.getProtectedBuildingID());
+			protectAsset(msg,targetBuilding, vendor, origin);
+			break;
+		case UNPROTECT:
+			targetBuilding =  BuildingManager.getBuildingFromCache(msg.getProtectedBuildingID());
+			unprotectAsset(targetBuilding, vendor, origin);
+			break;
+		case VIEWUNPROTECTED:
+			outMsg = new CityAssetMsg();
+			outMsg.setBuildingID(msg.getBuildingID());
+			outMsg.configure();
+
+			dispatch = Dispatch.borrow(player, outMsg);
+			DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+			break;
+
+		case REMOVETAX:
+			targetBuilding = BuildingManager.getBuildingFromCache(msg.getBuildingID());
+
+			if (targetBuilding == null)
+				return true;
+
+			targetBuilding.removeTaxes();
+			unprotectAsset(targetBuilding, null, origin);
+
+			ManageCityAssetsMsg mca = new ManageCityAssetsMsg(origin.getPlayerCharacter(),targetBuilding);
+
+			// Action TYPE
+			mca.actionType = 3;
+			mca.setTargetType(targetBuilding.getObjectType().ordinal());
+			mca.setTargetID(targetBuilding.getObjectUUID());
+			mca.setTargetType3(targetBuilding.getObjectType().ordinal());
+			mca.setTargetID3(targetBuilding.getObjectUUID());
+			mca.setAssetName1(targetBuilding.getName());
+			mca.setUnknown54(1);
+			dispatch = Dispatch.borrow(player, mca);
+			DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+			return true;
+
+			case ACCEPTTAX: //AcceptTax
+
+			targetBuilding = BuildingManager.getBuildingFromCache(msg.getBuildingID());
+
+			if (targetBuilding == null)
+				return true;
+
+			targetBuilding.acceptTaxes();
+
+			mca = new ManageCityAssetsMsg(origin.getPlayerCharacter(),targetBuilding);
+
+				// Action TYPE
+				mca.actionType = 3;
+				mca.setTargetType(targetBuilding.getObjectType().ordinal());
+			mca.setTargetID(targetBuilding.getObjectUUID());
+			mca.setTargetType3(targetBuilding.getObjectType().ordinal());
+			mca.setTargetID3(targetBuilding.getObjectUUID());
+			mca.setAssetName1(targetBuilding.getName());
+			mca.setUnknown54(1);
+
+			dispatch = Dispatch.borrow(player, mca);
+			DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+			return true;
+		}
+
+		dispatch = Dispatch.borrow(player, baseMsg);
+		DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+
+		return true;
+
+	}
+
+	private static void protectAsset(AssetSupportMsg msg, Building targetBuilding, NPC vendor, ClientConnection origin) {
+
+		// Member variable declaration
+
+		Zone serverZone;
+		City serverCity;
+		ManageNPCMsg outMsg;
+		int protectionSlots;
+		Dispatch dispatch;
+
+		// Member variable assignment
+
+		if (targetBuilding == null)
+			return;
+
+		serverZone = vendor.getParentZone();
+
+		if (serverZone == null)
+			return;
+
+		serverCity = City.GetCityFromCache(serverZone.getPlayerCityUUID());
+
+		if (serverCity == null)
+			return;
+
+		if (!serverCity.isLocationOnCityZone(targetBuilding.getLoc())){
+			ErrorPopupMsg.sendErrorMsg(origin.getPlayerCharacter(), "Structura must be on city zone for to protect.");
+			return;
+		}
+
+		if ((serverCity.getTOL() == null)|| (serverCity.getTOL().getRank() < 1))
+			return;
+
+		if (serverCity.protectionEnforced == false) {
+			ErrorPopupMsg.sendErrorMsg(origin.getPlayerCharacter(), "Runemaster can not protect structura during bane!");
+			return;
+		}
+
+		if (serverCity.getRuneMaster() == null) {
+			ErrorPopupMsg.sendErrorMsg(origin.getPlayerCharacter(), "Runemaster is needed for to protect structura!");
+			return;
+		}
+
+		// Enforce runemaster protection limits
+
+		protectionSlots = (2 * serverCity.getRuneMaster().getRank())  + 6;
+
+		if (serverCity.getRuneMaster().getProtectedBuildings().size() >=
+				protectionSlots) {
+			ErrorPopupMsg.sendErrorMsg(origin.getPlayerCharacter(), "Runemaster can only protect " + protectionSlots + " structura!");
+			return;
+		}
+
+		if (msg.getWeeklyTax() != 0){
+			if (!serverCity.getTOL().addProtectionTax(targetBuilding, origin.getPlayerCharacter(), TaxType.WEEKLY, msg.getWeeklyTax(), msg.getEnforceKOS() == 1 ? true: false)){
+				ErrorPopupMsg.sendErrorMsg(origin.getPlayerCharacter(), "Failed to add taxes to building.");
+				return;
+			}
+			targetBuilding.setProtectionState(Enum.ProtectionState.PENDING);
+		}else if (msg.getProfitTax() != 0){
+			if (!serverCity.getTOL().addProtectionTax(targetBuilding, origin.getPlayerCharacter(), TaxType.PROFIT, msg.getProfitTax(), msg.getEnforceKOS() == 1 ? true: false)){
+				ErrorPopupMsg.sendErrorMsg(origin.getPlayerCharacter(), "Failed to add taxes to building.");
+				return;
+			}
+			targetBuilding.setProtectionState(Enum.ProtectionState.PENDING);
+		}else
+			targetBuilding.setProtectionState(Enum.ProtectionState.CONTRACT);
+
+
+
+		outMsg = new ManageNPCMsg(vendor);
+		dispatch = Dispatch.borrow(origin.getPlayerCharacter(), outMsg);
+		DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+	}
+
+	private static void unprotectAsset(Building targetBuilding, NPC vendor, ClientConnection origin) {
+
+		if (targetBuilding == null)
+			return;
+
+		// Early exit if UUID < the last database derived building UUID.
+
+		if (targetBuilding.getProtectionState() == Enum.ProtectionState.NPC) {
+			return;
+		}
+
+		if (targetBuilding.getProtectionState() == engine.Enum.ProtectionState.NONE)
+			return;
+
+		if (GuildStatusController.isInnerCouncil(origin.getPlayerCharacter().getGuildStatus()) == false)
+			return;
+
+		targetBuilding.removeTaxes();
+
+		targetBuilding.setProtectionState(engine.Enum.ProtectionState.NONE);
+
+	}
+
+}
diff --git a/src/engine/net/client/handlers/BanishUnbanishHandler.java b/src/engine/net/client/handlers/BanishUnbanishHandler.java
new file mode 100644
index 00000000..7676373c
--- /dev/null
+++ b/src/engine/net/client/handlers/BanishUnbanishHandler.java
@@ -0,0 +1,129 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.handlers;
+
+import engine.Enum;
+import engine.Enum.GuildHistoryType;
+import engine.exception.MsgSendException;
+import engine.gameManager.ChatManager;
+import engine.gameManager.DbManager;
+import engine.gameManager.SessionManager;
+import engine.net.Dispatch;
+import engine.net.DispatchMessage;
+import engine.net.client.ClientConnection;
+import engine.net.client.msg.ClientNetMsg;
+import engine.net.client.msg.ErrorPopupMsg;
+import engine.net.client.msg.guild.BanishUnbanishMsg;
+import engine.net.client.msg.guild.GuildListMsg;
+import engine.objects.Guild;
+import engine.objects.GuildHistory;
+import engine.objects.GuildStatusController;
+import engine.objects.PlayerCharacter;
+import org.joda.time.DateTime;
+
+public class BanishUnbanishHandler extends AbstractClientMsgHandler {
+
+	public BanishUnbanishHandler() {
+		super(BanishUnbanishMsg.class);
+	}
+
+	@Override
+	protected boolean _handleNetMsg(ClientNetMsg baseMsg, ClientConnection origin) throws MsgSendException {
+		BanishUnbanishMsg msg = (BanishUnbanishMsg) baseMsg;
+		Dispatch dispatch;
+
+		int target = msg.getTarget();
+		PlayerCharacter source = origin.getPlayerCharacter();
+
+		if(source == null || source.getGuild().isErrant() || source.getGuild().getObjectUUID() == 0)
+			return true;
+
+		if (GuildStatusController.isGuildLeader(source.getGuildStatus()) == false && GuildStatusController.isInnerCouncil(source.getGuildStatus()) == false)
+			return true;
+
+		if (source.getObjectUUID() == target) {
+			ErrorPopupMsg.sendErrorPopup(source, 103); // You may not banish this char
+			return true;
+		}
+
+		boolean success = false;
+		Guild guild = source.getGuild();
+		PlayerCharacter realizedTarget = PlayerCharacter.getFromCache(target);
+
+		if(realizedTarget != null) {
+			// Guild leader can't leave guild. must pass GL or disband
+			if ( GuildStatusController.isGuildLeader(realizedTarget.getGuildStatus()) == false) {
+				//ICs cannot banish other ICs
+				if (!(GuildStatusController.isInnerCouncil(realizedTarget.getGuildStatus()) && GuildStatusController.isGuildLeader(source.getGuildStatus()) == false)) {
+					success = true;
+					if (msg.getMsgType() == 1){
+						if (!DbManager.GuildQueries.ADD_TO_BANISHED_FROM_GUILDLIST(guild.getObjectUUID(), realizedTarget.getObjectUUID())){
+							ChatManager.chatGuildError(source, "Failed To unbanish " + realizedTarget.getName());
+							return true;
+						}
+						guild.getBanishList().remove(realizedTarget);
+					}
+
+
+					else{
+
+						if (!DbManager.GuildQueries.ADD_TO_BANISHED_FROM_GUILDLIST(guild.getObjectUUID(), realizedTarget.getObjectUUID())){
+							ChatManager.chatGuildError(source, "Failed To Banish " + realizedTarget.getName());
+							return true;
+						}
+						guild.removePlayer(realizedTarget, GuildHistoryType.BANISHED);
+						guild.getBanishList().add(realizedTarget);  //TODO we might encapsulate this a bit better; also not sure that a list of PC objects is really ideal
+					}
+
+				}
+			}
+		} else {
+			if (guild.getGuildLeaderUUID() != target) {
+				PlayerCharacter toBanish = PlayerCharacter.getPlayerCharacter(target);
+				if (toBanish == null)
+					return true;
+				//already added previously.
+				if (SessionManager.getPlayerCharacterByID(toBanish.getObjectUUID()) != null)
+					return true;
+
+				if(DbManager.GuildQueries.BANISH_FROM_GUILD_OFFLINE(target, GuildStatusController.isGuildLeader(source.getGuildStatus())) != 0) {
+
+					success = true;
+
+					//Set guild history
+
+					if (DbManager.GuildQueries.ADD_TO_GUILDHISTORY(guild.getObjectUUID(), toBanish, DateTime.now(), GuildHistoryType.BANISHED)){
+						GuildHistory guildHistory = new GuildHistory(toBanish.getGuildUUID(),toBanish.getGuild().getName(),DateTime.now(), GuildHistoryType.BANISHED) ;
+						toBanish.getGuildHistory().add(guildHistory);
+					}
+				}
+			}
+		}
+
+
+		if(success) {
+			//TODO re enable this once we get unbanish working!!!!
+			//DbManager.GuildQueries.ADD_TO_BANISHED_FROM_GUILDLIST(guild.getobjectUUID(), target);
+
+			// Send left guild message to rest of guild
+			String targetName = PlayerCharacter.getFirstName(target);
+			ChatManager.chatGuildInfo(guild,
+					targetName + " has been banished from " + guild.getName() + '.');
+			GuildListMsg guildListMsg = new GuildListMsg(guild);
+			dispatch = Dispatch.borrow(source, guildListMsg);
+			DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+		} else {
+			ErrorPopupMsg.sendErrorPopup(source, 103); // You may not banish this char
+		}
+		return true;
+
+	}
+
+}
diff --git a/src/engine/net/client/handlers/BreakFealtyHandler.java b/src/engine/net/client/handlers/BreakFealtyHandler.java
new file mode 100644
index 00000000..ba91fbf8
--- /dev/null
+++ b/src/engine/net/client/handlers/BreakFealtyHandler.java
@@ -0,0 +1,193 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.handlers;
+
+import engine.Enum;
+import engine.Enum.GameObjectType;
+import engine.exception.MsgSendException;
+import engine.gameManager.ChatManager;
+import engine.gameManager.DbManager;
+import engine.gameManager.GuildManager;
+import engine.gameManager.SessionManager;
+import engine.net.Dispatch;
+import engine.net.DispatchMessage;
+import engine.net.client.ClientConnection;
+import engine.net.client.msg.ClientNetMsg;
+import engine.net.client.msg.ErrorPopupMsg;
+import engine.net.client.msg.guild.BreakFealtyMsg;
+import engine.net.client.msg.guild.SendGuildEntryMsg;
+import engine.objects.*;
+import engine.server.MBServerStatics;
+import engine.session.Session;
+
+import java.util.ArrayList;
+
+public class BreakFealtyHandler extends AbstractClientMsgHandler {
+
+	public BreakFealtyHandler() {
+		super(BreakFealtyMsg.class);
+	}
+
+	@Override
+	protected boolean _handleNetMsg(ClientNetMsg baseMsg, ClientConnection origin) throws MsgSendException {
+
+		BreakFealtyMsg bfm;
+		PlayerCharacter player;
+		Guild toBreak;
+		Guild guild;
+		Dispatch dispatch;
+
+		bfm = (BreakFealtyMsg) baseMsg;
+
+		// get PlayerCharacter of person accepting invite
+
+		player = SessionManager.getPlayerCharacter(
+				origin);
+
+		if (player == null)
+			return true;
+
+		toBreak = (Guild) DbManager.getObject(GameObjectType.Guild, bfm.getGuildUUID());
+
+		if (toBreak == null) {
+			ErrorPopupMsg.sendErrorMsg(player, "A Serious error has occured. Please post details for to ensure transaction integrity");
+			return true;
+		}
+
+		guild = player.getGuild();
+
+		if (guild == null) {
+			ErrorPopupMsg.sendErrorMsg(player, "You do not belong to a guild!");
+			return true;
+		}
+
+		if (toBreak.isNPCGuild()){
+			if (GuildStatusController.isGuildLeader(player.getGuildStatus()) == false) {
+				ErrorPopupMsg.sendErrorMsg(player, "Only guild leader can break fealty!");
+				return true;
+			}
+
+
+
+
+			if (!DbManager.GuildQueries.UPDATE_PARENT(guild.getObjectUUID(),MBServerStatics.worldUUID)) {
+				ErrorPopupMsg.sendErrorMsg(player, "A Serious error has occurred. Please post details for to ensure transaction integrity");
+				return true;
+			}
+
+			switch (guild.getGuildState()) {
+			case Sworn:
+				guild.setNation(null);
+				GuildManager.updateAllGuildTags(guild);
+				GuildManager.updateAllGuildBinds(guild, null);
+				break;
+			case Province:
+				guild.setNation(guild);
+				GuildManager.updateAllGuildTags(guild);
+				GuildManager.updateAllGuildBinds(guild, guild.getOwnedCity());
+				break;
+			}
+
+			guild.downgradeGuildState();
+
+			SendGuildEntryMsg msg = new SendGuildEntryMsg(player);
+			dispatch = Dispatch.borrow(player, msg);
+			DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+
+			//Update Map.
+
+			final Session s = SessionManager.getSession(player);
+
+			City.lastCityUpdate = System.currentTimeMillis();
+
+
+			ArrayList<PlayerCharacter> guildMembers = SessionManager.getActivePCsInGuildID(guild.getObjectUUID());
+
+			for (PlayerCharacter member : guildMembers) {
+				ChatManager.chatGuildInfo(member, guild.getName() + " has broke fealty from " + toBreak.getName() + '!');
+			}
+
+			ArrayList<PlayerCharacter> breakFealtyMembers = SessionManager.getActivePCsInGuildID(toBreak.getObjectUUID());
+
+			for (PlayerCharacter member : breakFealtyMembers) {
+				ChatManager.chatGuildInfo(member, guild.getName() + " has broken fealty from " + toBreak.getName() + '!');
+			}
+
+			return true;
+
+
+		}
+
+		if (!toBreak.getSubGuildList().contains(guild)) {
+			ErrorPopupMsg.sendErrorMsg(player, "Failure to break fealty!");
+			return true;
+		}
+
+		if (GuildStatusController.isGuildLeader(player.getGuildStatus()) == false) {
+			ErrorPopupMsg.sendErrorMsg(player, "Only guild leader can break fealty!");
+			return true;
+		}
+
+		if (Bane.getBaneByAttackerGuild(guild) != null)
+		{
+			ErrorPopupMsg.sendErrorMsg(player, "You may break fealty with active bane!");
+			return true;
+		}
+
+		if (!DbManager.GuildQueries.UPDATE_PARENT(guild.getObjectUUID(),MBServerStatics.worldUUID)) {
+			ErrorPopupMsg.sendErrorMsg(player, "A Serious error has occurred. Please post details for to ensure transaction integrity");
+			return true;
+		}
+
+		switch (guild.getGuildState()) {
+		case Sworn:
+			guild.setNation(null);
+			GuildManager.updateAllGuildTags(guild);
+			GuildManager.updateAllGuildBinds(guild, null);
+			break;
+		case Province:
+			guild.setNation(guild);
+			GuildManager.updateAllGuildTags(guild);
+			GuildManager.updateAllGuildBinds(guild, guild.getOwnedCity());
+			break;
+		}
+
+		guild.downgradeGuildState();
+		toBreak.getSubGuildList().remove(guild);
+
+		if (toBreak.getSubGuildList().isEmpty())
+			toBreak.downgradeGuildState();
+
+		SendGuildEntryMsg msg = new SendGuildEntryMsg(player);
+		dispatch = Dispatch.borrow(player, msg);
+		DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+
+		//Update Map.
+
+		final Session s = SessionManager.getSession(player);
+
+		City.lastCityUpdate = System.currentTimeMillis();
+
+
+		ArrayList<PlayerCharacter> guildMembers = SessionManager.getActivePCsInGuildID(guild.getObjectUUID());
+
+		for (PlayerCharacter member : guildMembers) {
+			ChatManager.chatGuildInfo(member, guild.getName() + " has broke fealty from " + toBreak.getName() + '!');
+		}
+
+		ArrayList<PlayerCharacter> breakFealtyMembers = SessionManager.getActivePCsInGuildID(toBreak.getObjectUUID());
+
+		for (PlayerCharacter member : breakFealtyMembers) {
+			ChatManager.chatGuildInfo(member, guild.getName() + " has broken fealty from " + toBreak.getName() + '!');
+		}
+
+		return true;
+	}
+}
diff --git a/src/engine/net/client/handlers/ChangeAltitudeHandler.java b/src/engine/net/client/handlers/ChangeAltitudeHandler.java
new file mode 100644
index 00000000..b8b09fb4
--- /dev/null
+++ b/src/engine/net/client/handlers/ChangeAltitudeHandler.java
@@ -0,0 +1,190 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.handlers;
+
+import engine.Enum.DispatchChannel;
+import engine.exception.MsgSendException;
+import engine.net.DispatchMessage;
+import engine.net.client.ClientConnection;
+import engine.net.client.msg.ChangeAltitudeMsg;
+import engine.net.client.msg.ClientNetMsg;
+import engine.objects.AbstractCharacter;
+import engine.objects.Building;
+import engine.objects.PlayerCharacter;
+import engine.objects.Regions;
+import engine.server.MBServerStatics;
+import org.pmw.tinylog.Logger;
+
+public class ChangeAltitudeHandler extends AbstractClientMsgHandler {
+
+	public ChangeAltitudeHandler() {
+		super(ChangeAltitudeMsg.class);
+	}
+
+	@Override
+	protected boolean _handleNetMsg(ClientNetMsg baseMsg,
+			ClientConnection origin) throws MsgSendException {
+
+		ChangeAltitudeMsg msg = (ChangeAltitudeMsg) baseMsg;
+
+		PlayerCharacter pc = origin.getPlayerCharacter();
+		if (pc == null) {
+			return false;
+		}
+
+		if (!AbstractCharacter.CanFly(pc))
+			return false;
+		
+		if (pc.isSwimming())
+			return false;
+		if (pc.getRegion() != null && !pc.getRegion().isOutside())
+			return false;
+		
+
+
+
+
+		
+
+		// Find out if we already have an altitude timer running and if so
+		// do not process more alt change requests
+		
+		if (pc.getTakeOffTime() != 0)
+			return false;
+
+		
+		// remove all movement timers and jobs
+		//TODO: test if they can fly
+
+		float targetAlt;
+		float amount = msg.getAmountToMove();
+		if (amount != 10 && amount != 60)
+			return false;
+		if (pc.getAltitude() == 60 && msg.up())
+			return true;
+		if (pc.getAltitude() == 0 && !msg.up())
+			return true;
+		
+		pc.update();
+		pc.stopMovement(pc.getLoc());
+		msg.setStartAlt(pc.getAltitude());
+		if (msg.up()) {
+
+			pc.landingRegion = null;
+			if (pc.getAltitude() == 0){
+				Regions upRegion = pc.getRegion();
+				if (upRegion != null){
+					float startAlt = 0;
+					Building regionBuilding = Regions.GetBuildingForRegion(upRegion);
+					if (upRegion != null)
+					 startAlt = upRegion.lerpY(pc) - regionBuilding.getLoc().y;
+					float rounded = startAlt *.10f;
+
+					rounded = ((int)rounded) * 10;
+
+					if (rounded < 0)
+						rounded = 0;
+
+					
+						msg.setStartAlt(startAlt);
+					targetAlt = rounded + amount;
+
+					if (targetAlt > 60)
+						targetAlt = 60;
+					
+					pc.setAltitude(startAlt);
+					pc.setDesiredAltitude(targetAlt);
+				}else{
+					msg.setStartAlt(pc.getAltitude());
+					targetAlt = pc.getAltitude() + amount;
+					if (targetAlt > 60)
+						targetAlt = 60;
+				}
+			}else{
+				msg.setStartAlt(pc.getAltitude());
+
+				targetAlt = pc.getAltitude() + amount;
+				if (targetAlt > 60)
+					targetAlt = 60;
+			}
+
+
+		} else {
+			msg.setStartAlt(pc.getAltitude());
+			targetAlt = pc.getAltitude() - amount;
+			if (targetAlt < 0)
+				targetAlt = 0;
+		}
+		msg.setTargetAlt(targetAlt);
+		if (pc.getAltitude() < 1 && targetAlt > pc.getAltitude()) {
+			// char is on the ground and is about to start flight
+			if (pc.getStamina() < 10) {
+				return false;
+			}
+		}
+
+		if (MBServerStatics.MOVEMENT_SYNC_DEBUG) {
+			Logger.info ("Changing Altitude, moving=" + pc.isMoving() +
+					" Current Loc " + pc.getLoc().getX() + ' ' + pc.getLoc().getZ() +
+					" End Loc " + pc.getEndLoc().getX() + ' ' + pc.getEndLoc().getZ());
+		}
+
+		if (msg.up()){
+			pc.update();
+			pc.setDesiredAltitude(targetAlt);
+			pc.setTakeOffTime(System.currentTimeMillis());
+		}
+			
+		else{
+			Regions region = PlayerCharacter.InsideBuildingRegionGoingDown(pc);
+			
+		
+			
+			if (region != null){
+				float landingAltitude = 0;
+				Building building = Regions.GetBuildingForRegion(region);
+				if (building != null)
+					landingAltitude = region.lerpY(pc) - building.getLoc().y;
+				
+				if (landingAltitude >= targetAlt){
+					pc.landingRegion = region;
+					pc.landingAltitude = landingAltitude;
+					pc.setDesiredAltitude(landingAltitude);
+				}else{
+					pc.landingRegion = null;
+					pc.setDesiredAltitude(targetAlt);
+				}
+				
+				
+			}else
+				pc.setDesiredAltitude(targetAlt);
+
+			pc.update();
+			
+		
+			pc.setTakeOffTime(System.currentTimeMillis());
+		}
+
+
+
+		// Add timer for height change cooldown, this also tells getLoc we are not moving
+		//MovementManager.addChangeAltitudeTimer(pc, msg.getStartAlt(), msg.getTargetAlt(), (int)((MBServerStatics.HEIGHT_CHANGE_TIMER_MS * amount) + 100 )  );
+
+		// Add flight timer job to check stam and ground you when you run out
+		//MovementManager.addFlightTimer(pc, msg, MBServerStatics.FLY_FREQUENCY_MS);
+		//Send change altitude to all in range
+		DispatchMessage.dispatchMsgToInterestArea(pc, msg, DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, true, false);
+		
+		
+
+		return true;
+	}
+
+}
diff --git a/src/engine/net/client/handlers/ChangeGuildLeaderHandler.java b/src/engine/net/client/handlers/ChangeGuildLeaderHandler.java
new file mode 100644
index 00000000..585f28a5
--- /dev/null
+++ b/src/engine/net/client/handlers/ChangeGuildLeaderHandler.java
@@ -0,0 +1,147 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.handlers;
+
+import engine.Enum;
+import engine.Enum.GameObjectType;
+import engine.exception.MsgSendException;
+import engine.gameManager.ChatManager;
+import engine.gameManager.DbManager;
+import engine.net.Dispatch;
+import engine.net.DispatchMessage;
+import engine.net.client.ClientConnection;
+import engine.net.client.msg.ChangeGuildLeaderMsg;
+import engine.net.client.msg.ClientNetMsg;
+import engine.net.client.msg.guild.GuildInfoMsg;
+import engine.net.client.msg.guild.GuildListMsg;
+import engine.objects.City;
+import engine.objects.Guild;
+import engine.objects.GuildStatusController;
+import engine.objects.PlayerCharacter;
+
+public class ChangeGuildLeaderHandler extends AbstractClientMsgHandler {
+
+	public ChangeGuildLeaderHandler() {
+		super(ChangeGuildLeaderMsg.class);
+	}
+
+	@Override
+	protected boolean _handleNetMsg(ClientNetMsg baseMsg, ClientConnection origin) throws MsgSendException {
+
+		ChangeGuildLeaderMsg msg;
+		PlayerCharacter sourcePlayer;
+		PlayerCharacter targetPlayer;
+		City city;
+
+		msg = (ChangeGuildLeaderMsg) baseMsg;
+		sourcePlayer = origin.getPlayerCharacter();
+		if (sourcePlayer == null)
+			return true;
+		if (GuildStatusController.isGuildLeader(sourcePlayer.getGuildStatus()) == false)
+			return true;
+
+
+
+		Guild glGuild = sourcePlayer.getGuild();
+
+		if (glGuild == null)
+			return true;
+
+		if (!glGuild.isGuildLeader(sourcePlayer.getObjectUUID()))
+			return true;
+		targetPlayer = (PlayerCharacter) DbManager.getObject(GameObjectType.PlayerCharacter, msg.getTargetID());
+
+
+		if (targetPlayer == null)
+			return true;
+
+		if (GuildStatusController.isGuildLeader(targetPlayer.getGuildStatus()))
+			return true;
+
+		if (!Guild.sameGuild(sourcePlayer.getGuild(),targetPlayer.getGuild()))
+			return false;
+
+
+		//updateSource will generate a new promote/demote screen for sourcePlayer
+		//updateTarget will sync guild info for the target and all players in range
+
+
+
+
+		String targetName = null;
+		boolean isMale = true;
+		boolean updateTarget;
+
+		Enum.GuildType t = Enum.GuildType.getGuildTypeFromInt(sourcePlayer.getGuild().getCharter());
+
+
+		if (!DbManager.GuildQueries.SET_GUILD_LEADER(targetPlayer.getObjectUUID(), glGuild.getObjectUUID())){
+			ChatManager.chatGuildError(sourcePlayer, "Failed to change guild leader!");
+			return false;
+		}
+
+		glGuild.setGuildLeader(targetPlayer);
+
+
+
+		if (glGuild.getOwnedCity() != null){
+			city = glGuild.getOwnedCity();
+			if (!city.transferGuildLeader(targetPlayer)){
+				ChatManager.chatGuildError(sourcePlayer, "Failed to Transfer City Objects. Contact CCR!");
+				return false;
+			}
+
+		}
+
+
+
+		targetPlayer.setGuildLeader(true);
+		targetPlayer.setInnerCouncil(true);
+		targetPlayer.setFullMember(true);
+		targetPlayer.incVer();
+		targetName = targetPlayer.getFirstName();
+		updateTarget = true;
+
+
+		ChatManager.chatGuildInfo(sourcePlayer.getGuild(),
+				targetName + " has been promoted to "
+						+ "guild leader!");
+
+		//These values record a change, not the new value...
+
+
+
+		//updateOldGuildLeader
+		sourcePlayer.setInnerCouncil(true);
+		sourcePlayer.setFullMember(true);
+		sourcePlayer.setGuildLeader(false);
+		sourcePlayer.incVer();
+		
+		GuildInfoMsg guildInfoMsg = new GuildInfoMsg(sourcePlayer, sourcePlayer.getGuild(), 2);
+		 Dispatch dispatch = Dispatch.borrow(sourcePlayer, guildInfoMsg);
+		DispatchMessage.dispatchMsgDispatch(dispatch, engine.Enum.DispatchChannel.SECONDARY);
+
+		GuildListMsg guildListMsg = new GuildListMsg(sourcePlayer.getGuild());
+
+		dispatch = Dispatch.borrow(sourcePlayer, guildListMsg);
+		DispatchMessage.dispatchMsgDispatch(dispatch, engine.Enum.DispatchChannel.SECONDARY);
+		
+		
+
+		if (targetPlayer != null) {
+			if (updateTarget)
+				DispatchMessage.sendToAllInRange(targetPlayer, new GuildInfoMsg(targetPlayer, targetPlayer.getGuild(), 2));
+		}
+
+		return true;
+
+	}
+
+}
diff --git a/src/engine/net/client/handlers/ChangeRankHandler.java b/src/engine/net/client/handlers/ChangeRankHandler.java
new file mode 100644
index 00000000..cc0de5ca
--- /dev/null
+++ b/src/engine/net/client/handlers/ChangeRankHandler.java
@@ -0,0 +1,176 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+
+
+
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.handlers;
+
+import engine.Enum;
+import engine.Enum.GameObjectType;
+import engine.exception.MsgSendException;
+import engine.gameManager.ChatManager;
+import engine.gameManager.DbManager;
+import engine.net.Dispatch;
+import engine.net.DispatchMessage;
+import engine.net.client.ClientConnection;
+import engine.net.client.msg.ClientNetMsg;
+import engine.net.client.msg.guild.ChangeRankMsg;
+import engine.net.client.msg.guild.GuildInfoMsg;
+import engine.net.client.msg.guild.GuildListMsg;
+import engine.objects.GuildStatusController;
+import engine.objects.PlayerCharacter;
+
+public class ChangeRankHandler extends AbstractClientMsgHandler {
+
+    public ChangeRankHandler() {
+        super(ChangeRankMsg.class);
+    }
+
+    @Override
+    protected boolean _handleNetMsg(ClientNetMsg baseMsg, ClientConnection origin) throws MsgSendException {
+
+        ChangeRankMsg msg;
+        PlayerCharacter sourcePlayer;
+        PlayerCharacter targetPlayer;
+
+        msg = (ChangeRankMsg) baseMsg;
+        sourcePlayer = origin.getPlayerCharacter();
+
+        targetPlayer = (PlayerCharacter) DbManager.getObject(GameObjectType.PlayerCharacter, msg.getPlayerUUID());
+
+        if (msg.getPlayerUUID() == 0)
+            targetPlayer = sourcePlayer;
+
+	//updateSource will generate a new promote/demote screen for sourcePlayer
+        //updateTarget will sync guild info for the target and all players in range
+        
+        boolean updateSource = false, updateTarget = false;
+
+        if (sourcePlayer == null ||GuildStatusController.isInnerCouncil(sourcePlayer.getGuildStatus()) == false)
+            return true;
+
+        if (targetPlayer != null && (targetPlayer.getGuild().equals(sourcePlayer.getGuild()) == false))
+            return true;
+
+        String targetName = null;
+        boolean isMale;
+
+        if (msg.getPreviousRank() != msg.getNewRank()) {
+            Enum.GuildType t = Enum.GuildType.getGuildTypeFromInt(sourcePlayer.getGuild().getCharter());
+
+            if (targetPlayer != null) {
+                targetPlayer.setGuildTitle(msg.getNewRank());
+
+                targetName = targetPlayer.getFirstName();
+                isMale = targetPlayer.getRace().getRaceType().getCharacterSex().equals(Enum.CharacterSex.MALE);
+            } else {
+                DbManager.GuildQueries.UPDATE_GUILD_RANK_OFFLINE(msg.getPlayerUUID(), msg.getNewRank(), sourcePlayer.getGuild().getObjectUUID());
+
+                targetName = PlayerCharacter.getFirstName(msg.getPlayerUUID());
+                isMale = true;
+            }
+
+            ChatManager.chatGuildInfo(sourcePlayer.getGuild(),
+                    targetName + " has been "
+                    + ((msg.getNewRank() > msg.getPreviousRank()) ? "pro" : "de") + "moted to "
+                    + t.getRankForGender(msg.getNewRank(), isMale) + '!');
+            updateSource = true;
+        }
+
+        //These values record a change, not the new value...
+        boolean icUpdate = false, recruitUpdate = false, taxUpdate = false;
+
+        //Handle the offline case..
+        if (targetPlayer == null) {
+            int updateMask = DbManager.GuildQueries.UPDATE_GUILD_STATUS_OFFLINE(msg.getPlayerUUID(),
+                    msg.getIc() > 0,
+                    msg.getRec() > 0,
+                    msg.getTax() > 0,
+                    sourcePlayer.getGuild().getObjectUUID());
+
+            //These values come from the updateIsStatusOffline function
+            icUpdate = (updateMask & 4) > 0;
+            recruitUpdate = (updateMask & 2) > 0;
+            taxUpdate = (updateMask & 1) > 0;
+
+            if (targetName == null && updateMask > 0)
+                targetName = PlayerCharacter.getFirstName(msg.getPlayerUUID());
+        } else {
+            icUpdate = (GuildStatusController.isInnerCouncil(targetPlayer.getGuildStatus()) != (msg.getIc() > 0)) && GuildStatusController.isGuildLeader(sourcePlayer.getGuildStatus());
+            recruitUpdate = (GuildStatusController.isRecruiter(targetPlayer.getGuildStatus()) != (msg.getRec() > 0)) && GuildStatusController.isGuildLeader(sourcePlayer.getGuildStatus());
+            taxUpdate = (GuildStatusController.isTaxCollector(targetPlayer.getGuildStatus()) != (msg.getTax() > 0)) && GuildStatusController.isGuildLeader(sourcePlayer.getGuildStatus());
+
+            //This logic branch only executes if targetPlayer has passed a null check...
+            if (icUpdate){
+            	 targetPlayer.setInnerCouncil(msg.getIc() > 0);
+            	targetPlayer.setFullMember(true);
+            	targetPlayer.incVer();
+            }
+
+            if (recruitUpdate)
+                targetPlayer.setRecruiter(msg.getRec() > 0);
+
+            if (taxUpdate)
+                targetPlayer.setTaxCollector(msg.getTax() > 0);
+
+            if (targetName == null)
+                targetName = targetPlayer.getFirstName();
+        }
+
+        if (icUpdate) {
+            ChatManager.chatGuildInfo(sourcePlayer.getGuild(),
+                    (msg.getIc() > 0)
+                    ? targetName + " has been appointed to the inner council."
+                    : targetName + " is no longer a member of the inner council.");
+
+            updateSource = true;
+            updateTarget = true;
+        }
+
+        if (recruitUpdate) {
+            updateSource = true;
+            updateTarget = true;
+        }
+
+        if (taxUpdate) {
+            updateSource = true;
+            updateTarget = true;
+        }
+
+        if (targetPlayer != null) {
+            targetPlayer.incVer();
+            if (updateTarget)
+                DispatchMessage.sendToAllInRange(targetPlayer, new GuildInfoMsg(targetPlayer, targetPlayer.getGuild(), 2));
+        }
+
+        if (updateSource) {
+
+            Dispatch dispatch = Dispatch.borrow(sourcePlayer, new GuildInfoMsg(sourcePlayer, sourcePlayer.getGuild(), 2));
+            DispatchMessage.dispatchMsgDispatch(dispatch, engine.Enum.DispatchChannel.SECONDARY);
+
+            dispatch = Dispatch.borrow(sourcePlayer, new GuildListMsg(sourcePlayer.getGuild()));
+            DispatchMessage.dispatchMsgDispatch(dispatch, engine.Enum.DispatchChannel.SECONDARY);
+
+        }
+
+        return true;
+
+    }
+
+}
diff --git a/src/engine/net/client/handlers/ChannelMuteMsgHandler.java b/src/engine/net/client/handlers/ChannelMuteMsgHandler.java
new file mode 100644
index 00000000..ba6480d8
--- /dev/null
+++ b/src/engine/net/client/handlers/ChannelMuteMsgHandler.java
@@ -0,0 +1,123 @@
+package engine.net.client.handlers;
+
+import engine.Enum.BuildingGroup;
+import engine.Enum.GuildState;
+import engine.exception.MsgSendException;
+import engine.gameManager.ChatManager;
+import engine.gameManager.DbManager;
+import engine.gameManager.GuildManager;
+import engine.gameManager.ZoneManager;
+import engine.net.client.ClientConnection;
+import engine.net.client.msg.ChatFilterMsg;
+import engine.net.client.msg.ClientNetMsg;
+import engine.objects.*;
+import org.pmw.tinylog.Logger;
+
+/*
+ * @Author:
+ * @Summary: Processes application protocol message which processes
+ * client requests to abandon a building.
+ */
+public class ChannelMuteMsgHandler extends AbstractClientMsgHandler {
+
+	// Instance variables
+
+	public ChannelMuteMsgHandler() {
+		super(ChatFilterMsg.class);
+
+	}
+
+	@Override
+	protected boolean _handleNetMsg(ClientNetMsg baseMsg, ClientConnection origin) throws MsgSendException {
+
+		return true;
+	}
+
+	private static void AbandonSingleAsset(PlayerCharacter sourcePlayer,
+                                           Building targetBuilding) {
+
+		// Transfer the building asset ownership and refresh all clients
+
+		DbManager.BuildingQueries.CLEAR_FRIENDS_LIST(targetBuilding.getObjectUUID());
+		targetBuilding.getFriends().clear();
+
+		DbManager.BuildingQueries.CLEAR_CONDEMNED_LIST(targetBuilding.getObjectUUID());
+		targetBuilding.getCondemned().clear();
+		targetBuilding.setOwner(null);
+		targetBuilding.refreshGuild();
+	}
+
+	private void AbandonAllCityObjects(PlayerCharacter sourcePlayer,
+			Building targetBuilding) {
+		Guild sourceGuild;
+		Zone cityZone;
+
+		sourceGuild = sourcePlayer.getGuild();
+
+		if (sourceGuild == null)
+			return;
+
+		if (sourceGuild.getSubGuildList().size() > 0) {
+			ChatManager.chatCityError(sourcePlayer, "You Cannot abandon a nation city.");
+			return;
+		}
+
+		cityZone = ZoneManager.findSmallestZone(targetBuilding.getLoc());
+
+		// Can't abandon a tree not within a player city zone
+		if (cityZone.isPlayerCity() == false)
+			return;
+
+		if (targetBuilding.getCity().hasBeenTransfered == true) {
+			ChatManager.chatCityError(sourcePlayer, "City can only be abandoned once per rebooting.");
+			return;
+		}
+
+		// Guild no longer owns his tree.
+		if (!DbManager.GuildQueries.SET_GUILD_OWNED_CITY(sourceGuild.getObjectUUID(), 0)) {
+			Logger.error( "Failed to update Owned City to Database");
+			return;
+		}
+
+		sourceGuild.setCityUUID(0);
+		sourceGuild.setGuildState(GuildState.Errant);
+		sourceGuild.setNation(null);
+
+		// Transfer the city assets
+		TransferCityAssets(sourcePlayer, targetBuilding);
+
+		GuildManager.updateAllGuildTags(sourceGuild);
+		GuildManager.updateAllGuildBinds(sourceGuild, null);
+
+	}
+
+	private void TransferCityAssets(PlayerCharacter sourcePlayer,
+			Building cityTOL) {
+
+		Zone cityZone;
+
+		// Build list of buildings within this parent zone
+		cityZone = ZoneManager.findSmallestZone(cityTOL.getLoc());
+
+		for (Building cityBuilding : cityZone.zoneBuildingSet) {
+
+			Blueprint cityBlueprint;
+			cityBlueprint = cityBuilding.getBlueprint();
+
+			// Buildings without blueprints cannot be abandoned
+			if (cityBlueprint == null)
+				continue;
+
+			// Transfer ownership of valid city assets
+			if ((cityBlueprint.getBuildingGroup() == BuildingGroup.TOL)
+					|| (cityBlueprint.getBuildingGroup() == BuildingGroup.SPIRE)
+					|| (cityBlueprint.getBuildingGroup() == BuildingGroup.BARRACK)
+					|| (cityBlueprint.isWallPiece())
+					|| (cityBuilding.getBlueprint().getBuildingGroup() == BuildingGroup.SHRINE))
+				AbandonSingleAsset(sourcePlayer, cityBuilding);
+
+		}
+
+	}
+
+}
diff --git a/src/engine/net/client/handlers/CityChoiceMsgHandler.java b/src/engine/net/client/handlers/CityChoiceMsgHandler.java
new file mode 100644
index 00000000..7dd79a04
--- /dev/null
+++ b/src/engine/net/client/handlers/CityChoiceMsgHandler.java
@@ -0,0 +1,69 @@
+package engine.net.client.handlers;
+
+import engine.Enum;
+import engine.Enum.DispatchChannel;
+import engine.exception.MsgSendException;
+import engine.gameManager.GuildManager;
+import engine.net.Dispatch;
+import engine.net.DispatchMessage;
+import engine.net.client.ClientConnection;
+import engine.net.client.msg.CityChoiceMsg;
+import engine.net.client.msg.ClientNetMsg;
+import engine.net.client.msg.TeleportRepledgeListMsg;
+import engine.objects.City;
+import engine.objects.Guild;
+import engine.objects.PlayerCharacter;
+
+/*
+ * @Author:
+ * @Summary: Processes application protocol message which keeps
+ * client's tcp connection open.
+ */
+
+public class CityChoiceMsgHandler extends AbstractClientMsgHandler {
+
+	public CityChoiceMsgHandler() {
+		super(CityChoiceMsg.class);
+	}
+
+	@Override
+	protected boolean _handleNetMsg(ClientNetMsg baseMsg, ClientConnection origin) throws MsgSendException {
+
+		PlayerCharacter player = origin.getPlayerCharacter();
+		Dispatch dispatch;
+
+		CityChoiceMsg msg = (CityChoiceMsg) baseMsg;
+
+		if (player == null)
+			return true;
+
+		switch (msg.getMsgType()) {
+			case 5:
+				TeleportRepledgeListMsg trlm = new TeleportRepledgeListMsg(player, false);
+				trlm.configure();
+				dispatch = Dispatch.borrow(player, trlm);
+				DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
+				break;
+			case 3:
+				City city = City.getCity(msg.getCityID());
+
+				if (city == null)
+					return true;
+
+				Guild cityGuild = city.getGuild();
+
+				if (cityGuild == null)
+					return true;
+
+				if (player.getLevel() < cityGuild.getRepledgeMin() || player.getLevel() > cityGuild.getRepledgeMax())
+					return true;
+
+				//if repledge, reguild the player but set his building now.
+
+				GuildManager.joinGuild(player, cityGuild, city.getObjectUUID(), Enum.GuildHistoryType.JOIN);
+				break;
+		}
+
+		return true;
+	}
+}
\ No newline at end of file
diff --git a/src/engine/net/client/handlers/ClaimAssetMsgHandler.java b/src/engine/net/client/handlers/ClaimAssetMsgHandler.java
new file mode 100644
index 00000000..50ffb9d6
--- /dev/null
+++ b/src/engine/net/client/handlers/ClaimAssetMsgHandler.java
@@ -0,0 +1,139 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.handlers;
+
+import engine.Enum;
+import engine.Enum.BuildingGroup;
+import engine.db.archive.CityRecord;
+import engine.db.archive.DataWarehouse;
+import engine.exception.MsgSendException;
+import engine.gameManager.BuildingManager;
+import engine.net.client.ClientConnection;
+import engine.net.client.msg.ClaimAssetMsg;
+import engine.net.client.msg.ClientNetMsg;
+import engine.objects.Blueprint;
+import engine.objects.Building;
+import engine.objects.PlayerCharacter;
+import org.pmw.tinylog.Logger;
+
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+import static engine.math.FastMath.sqr;
+
+/*
+ * @Author:
+ * @Summary: Processes application protocol message which keeps
+ * client's tcp connection open.
+ */
+public class ClaimAssetMsgHandler extends AbstractClientMsgHandler {
+
+    // Instance variables
+
+    private final ReentrantReadWriteLock claimLock = new ReentrantReadWriteLock();
+    
+	public ClaimAssetMsgHandler() {
+		super(ClaimAssetMsg.class);
+             
+	}
+
+	@Override
+	protected boolean _handleNetMsg(ClientNetMsg baseMsg, ClientConnection origin) throws MsgSendException {
+	
+            // Member variable declaration
+            this.claimLock.writeLock().lock();
+            
+            try{
+            	PlayerCharacter sourcePlayer;
+                Building targetBuilding;
+                Blueprint blueprint;
+                ClaimAssetMsg msg;
+                int targetUUID;
+                
+                msg = (ClaimAssetMsg) baseMsg;
+                targetUUID = msg.getUUID();
+                
+                sourcePlayer = origin.getPlayerCharacter();
+                targetBuilding = BuildingManager.getBuildingFromCache(targetUUID);
+
+                // Oops!  *** Refactor: Log error
+       
+                if ((sourcePlayer == null) ||
+                     (targetBuilding == null))
+                     return true;
+
+                // Player must be reasonably close to building in order to claim
+
+                if (sourcePlayer.getLoc().distanceSquared2D(targetBuilding.getLoc()) > sqr(100))
+                    return true;
+
+                // Early exit if object to be claimed is not errant
+                
+                if (targetBuilding.getOwnerUUID() != 0)
+                    return true;
+
+
+                // Early exit if UUID < the last database derived building UUID.
+
+                if (targetBuilding.getProtectionState() == Enum.ProtectionState.NPC) {
+                    return true;
+                }
+
+                // Early exit if claiming player does not
+                // have a guild.
+
+                // Errant players cannot claim
+                
+                if (sourcePlayer.getGuild().isErrant())
+                    return true;
+                
+                // Can't claim an object without a blueprint
+
+                if (targetBuilding.getBlueprintUUID() == 0)
+                    return true;
+                            
+                blueprint = targetBuilding.getBlueprint();
+                
+                //cant claim mine this way.
+                if (blueprint.getBuildingGroup() == BuildingGroup.MINE)
+                	return true;
+
+                // Players cannot claim shrines
+
+                if ((targetBuilding.getBlueprint().getBuildingGroup() == BuildingGroup.SHRINE))
+                    return true;
+
+                // Can't claim a tree if your guild already owns one
+                // *** Refactor : Send error to player here
+                
+                if ((sourcePlayer.getGuild().isNation()) &&
+                    (blueprint.getBuildingGroup() == BuildingGroup.TOL))
+                     return true;
+                
+                // Process the transfer of the building(s)
+                
+                if (blueprint.getBuildingGroup() == BuildingGroup.TOL) {
+                    targetBuilding.getCity().claim(sourcePlayer);
+
+                    // Push transfer of city to data warehouse
+                    CityRecord cityRecord = CityRecord.borrow(targetBuilding.getCity(), Enum.RecordEventType.TRANSFER);
+                    DataWarehouse.pushToWarehouse(cityRecord);
+
+                } else
+                    targetBuilding.claim(sourcePlayer);
+               
+            } catch(Exception e){
+            	Logger.error("ClaimAssetMsgHandler", e.getMessage());
+            }finally{
+            	this.claimLock.writeLock().unlock();	
+            }
+            return true;
+        }
+
+}
\ No newline at end of file
diff --git a/src/engine/net/client/handlers/ClaimGuildTreeMsgHandler.java b/src/engine/net/client/handlers/ClaimGuildTreeMsgHandler.java
new file mode 100644
index 00000000..639a984c
--- /dev/null
+++ b/src/engine/net/client/handlers/ClaimGuildTreeMsgHandler.java
@@ -0,0 +1,191 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.handlers;
+
+import engine.Enum;
+import engine.Enum.BuildingGroup;
+import engine.exception.MsgSendException;
+import engine.gameManager.BuildingManager;
+import engine.gameManager.ChatManager;
+import engine.net.Dispatch;
+import engine.net.DispatchMessage;
+import engine.net.client.ClientConnection;
+import engine.net.client.msg.*;
+import engine.objects.*;
+import org.pmw.tinylog.Logger;
+
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+
+/*
+ * @Author:
+ * @Summary: Processes application protocol message which keeps
+ * client's tcp connection open.
+ */
+public class ClaimGuildTreeMsgHandler extends AbstractClientMsgHandler {
+
+	// Instance variables
+
+	private final ReentrantReadWriteLock claimLock = new ReentrantReadWriteLock();
+	private static final int RENAME_TREE = 2;
+	private static final int BIND_TREE = 3;
+	private static final int OPEN_CITY = 4;
+	private static final int CLOSE_CITY = 5;
+
+	public ClaimGuildTreeMsgHandler() {
+		super(ClaimGuildTreeMsg.class);
+	}
+
+	@Override
+	protected boolean _handleNetMsg(ClientNetMsg baseMsg, ClientConnection origin) throws MsgSendException {
+
+		// Member variable declaration
+		this.claimLock.writeLock().lock();
+
+		try{
+			PlayerCharacter sourcePlayer;
+			Building building;
+			Blueprint blueprint;
+			Zone playerZone= null;
+			City playerCity= null;
+			ClaimGuildTreeMsg msg;
+			int targetUUID;
+			Dispatch dispatch;
+
+			msg = (ClaimGuildTreeMsg) baseMsg;
+			targetUUID = msg.getTargetID();
+
+			sourcePlayer = origin.getPlayerCharacter();
+			building = BuildingManager.getBuildingFromCache(targetUUID);
+
+			if (building != null)
+				playerZone = building.getParentZone();
+
+			if (playerZone != null)
+				playerCity = City.getCity(playerZone.getPlayerCityUUID());
+
+			// Oops!  *** Refactor: Log error
+			switch (msg.getMessageType()){
+			case RENAME_TREE:
+				if ((sourcePlayer == null) ||
+						(building == null) || playerZone == null ||playerCity == null)
+					return true;
+
+				// Early exit if object to be claimed is not errant
+
+				if (building.getOwnerUUID() == 0)
+					return true;
+
+				// Errant players cannot rename
+
+				if (sourcePlayer.getGuild().isErrant())
+					return true;
+
+				// Can't rename an object without a blueprint
+
+				if (building.getBlueprintUUID() == 0)
+					return true;
+
+				blueprint = building.getBlueprint();
+
+				//can only rename tree this way.
+				if (blueprint.getBuildingGroup() != BuildingGroup.TOL)
+					return true;
+
+				//dont rename if guild is null
+				if (building.getGuild().isErrant())
+					return true;
+
+				if (!ManageCityAssetMsgHandler.playerCanManageNotFriends(sourcePlayer, building))
+					return true;
+
+
+				if (!playerCity.renameCity(msg.getTreeName())){
+					ChatManager.chatSystemError(sourcePlayer, "Failed to rename city!");
+					return true;
+				}
+
+				GuildTreeStatusMsg gtsm = new GuildTreeStatusMsg(building,sourcePlayer);
+				gtsm.configure();
+				dispatch = Dispatch.borrow(sourcePlayer, gtsm);
+				DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+
+				CityZoneMsg czm = new CityZoneMsg(2,playerZone.getLoc().x, playerZone.getLoc().y, playerZone.getLoc().z, playerCity.getCityName(),playerZone, Enum.CityBoundsType.ZONE.extents, Enum.CityBoundsType.ZONE.extents);
+				DispatchMessage.dispatchMsgToAll(czm);
+
+				break;
+			case BIND_TREE:
+
+				Guild pcGuild = sourcePlayer.getGuild();
+
+				//test tree is valid for binding, same guild or same nation
+				if (!Guild.sameNation(pcGuild, building.getGuild())) {
+
+					return true;
+				}
+
+				if (building.getGuild().isErrant())
+					return true;
+
+
+				//get bind city
+				Zone zone =building.getParentZone();
+
+				if (zone == null) {
+					ErrorPopupMsg.sendErrorMsg(sourcePlayer, "A Serious error has occurred. Please post details for to ensure transaction integrity");
+					return true;
+				}
+
+				if (playerCity == null && building.getGuild() != null)
+					playerCity = building.getGuild().getOwnedCity();
+
+				if (playerCity == null)
+					return true;
+
+				
+				sourcePlayer.setBindBuildingID(building.getObjectUUID());
+				dispatch = Dispatch.borrow(sourcePlayer, msg);
+				DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+				break;
+			case OPEN_CITY:
+			case CLOSE_CITY:
+				if ((sourcePlayer == null) ||
+						(building == null) || playerZone == null ||playerCity == null)
+					return true;
+
+				if (!ManageCityAssetMsgHandler.playerCanManageNotFriends(sourcePlayer, building))
+					return true;
+
+				boolean open = (msg.getMessageType() == OPEN_CITY);
+
+				if (!playerCity.openCity(open)){
+					ErrorPopupMsg.sendErrorMsg(sourcePlayer, "A Serious error has occurred. Please post details for to ensure transaction integrity");
+					return true;
+				}
+
+				dispatch = Dispatch.borrow(sourcePlayer, msg);
+				DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+				break;
+			default:
+				break;
+			}
+
+		} catch(Exception e){
+			Logger.error( e.getMessage());
+		}finally{
+			try{
+				this.claimLock.writeLock().unlock();
+			}catch(Exception e){
+				Logger.info("failClaimsync");
+			}
+		}
+		return true;
+	}
+}
\ No newline at end of file
diff --git a/src/engine/net/client/handlers/DestroyBuildingHandler.java b/src/engine/net/client/handlers/DestroyBuildingHandler.java
new file mode 100644
index 00000000..7e53d1be
--- /dev/null
+++ b/src/engine/net/client/handlers/DestroyBuildingHandler.java
@@ -0,0 +1,95 @@
+package engine.net.client.handlers;
+
+import engine.Enum;
+import engine.Enum.BuildingGroup;
+import engine.InterestManagement.WorldGrid;
+import engine.exception.MsgSendException;
+import engine.gameManager.BuildingManager;
+import engine.net.client.ClientConnection;
+import engine.net.client.msg.ClientNetMsg;
+import engine.net.client.msg.DestroyBuildingMsg;
+import engine.objects.Blueprint;
+import engine.objects.Building;
+import engine.objects.City;
+import engine.objects.PlayerCharacter;
+
+/*
+ * @Author:
+ * @Summary: Processes application protocol message where the
+ * client is requesting a building be destroyed.
+ */
+
+public class DestroyBuildingHandler extends AbstractClientMsgHandler {
+
+	public DestroyBuildingHandler() {
+		super(DestroyBuildingMsg.class);
+	}
+
+	@Override
+	protected boolean _handleNetMsg(ClientNetMsg baseMsg, ClientConnection origin) throws MsgSendException {
+		
+		PlayerCharacter pc = origin.getPlayerCharacter();
+
+		DestroyBuildingMsg msg;
+		msg = (DestroyBuildingMsg) baseMsg;
+
+		int buildingUUID = msg.getUUID();
+
+		Building building = BuildingManager.getBuildingFromCache(buildingUUID);
+;
+		if (pc == null || building == null)
+			return true;
+
+		Blueprint blueprint;
+
+		blueprint = building.getBlueprint();
+
+		// Can't destroy buildings without a blueprint.
+
+		if (blueprint == null)
+			return true;
+
+		// Cannot destroy Oblivion database derived buildings.
+
+		if (building.getProtectionState() == Enum.ProtectionState.NPC) {
+			return true;
+		}
+
+		if (!BuildingManager.PlayerCanControlNotOwner(building, pc))
+			return true;
+
+		// Can't destroy a tree of life
+		if (blueprint.getBuildingGroup() == BuildingGroup.TOL)
+			return true;
+
+		// Can't destroy a shrine
+		if (blueprint.getBuildingGroup() == BuildingGroup.SHRINE)
+			return true;
+
+		if (blueprint.getBuildingGroup() == BuildingGroup.MINE)
+			return true;
+
+		if (blueprint.getBuildingGroup() == BuildingGroup.RUNEGATE)
+			return true;
+
+		// Turn off spire if destoying
+		if (blueprint.getBuildingGroup() == BuildingGroup.SPIRE)
+			building.disableSpire(true);
+
+		if (blueprint.getBuildingGroup() == BuildingGroup.WAREHOUSE) {
+
+			City city = building.getCity();
+
+			if (city != null)
+				city.setWarehouseBuildingID(0);
+		}
+
+		building.setRank(-1);
+		WorldGrid.RemoveWorldObject(building);
+		WorldGrid.removeObject(building);
+		building.getParentZone().zoneBuildingSet.remove(building);
+
+		return true;
+	}
+
+}
\ No newline at end of file
diff --git a/src/engine/net/client/handlers/DisbandGroupHandler.java b/src/engine/net/client/handlers/DisbandGroupHandler.java
new file mode 100644
index 00000000..5a749f50
--- /dev/null
+++ b/src/engine/net/client/handlers/DisbandGroupHandler.java
@@ -0,0 +1,56 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.handlers;
+
+import engine.exception.MsgSendException;
+import engine.gameManager.GroupManager;
+import engine.gameManager.SessionManager;
+import engine.net.client.ClientConnection;
+import engine.net.client.msg.ClientNetMsg;
+import engine.net.client.msg.group.DisbandGroupMsg;
+import engine.net.client.msg.group.GroupUpdateMsg;
+import engine.objects.Group;
+import engine.objects.PlayerCharacter;
+
+public class DisbandGroupHandler extends AbstractClientMsgHandler {
+
+    public DisbandGroupHandler() {
+        super(DisbandGroupMsg.class);
+    }
+
+    @Override
+    protected boolean _handleNetMsg(ClientNetMsg baseMsg, ClientConnection origin) throws MsgSendException {
+        
+        PlayerCharacter source = SessionManager.getPlayerCharacter(origin);
+        if (source == null) {
+            return false;
+        }
+        Group group = GroupManager.getGroup(source);
+        if (group == null) {
+            return false;
+        }
+        if (group.getGroupLead() != source) {
+            return false;
+        }
+
+        // Clear all group member lists
+        GroupUpdateMsg gim = new GroupUpdateMsg();
+        gim.setGroup(group);
+        gim.setMessageType(4);
+
+        // send the disbanded popup to everyone in group
+        group.sendUpdate(gim);
+
+        // cleanup group
+        GroupManager.deleteGroup(group);
+        return true;
+
+    }
+}
diff --git a/src/engine/net/client/handlers/DisbandGuildHandler.java b/src/engine/net/client/handlers/DisbandGuildHandler.java
new file mode 100644
index 00000000..a36e88ca
--- /dev/null
+++ b/src/engine/net/client/handlers/DisbandGuildHandler.java
@@ -0,0 +1,121 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.handlers;
+
+import engine.Enum;
+import engine.Enum.GuildHistoryType;
+import engine.InterestManagement.WorldGrid;
+import engine.db.archive.DataWarehouse;
+import engine.db.archive.GuildRecord;
+import engine.exception.MsgSendException;
+import engine.gameManager.ChatManager;
+import engine.gameManager.DbManager;
+import engine.gameManager.SessionManager;
+import engine.net.Dispatch;
+import engine.net.DispatchMessage;
+import engine.net.client.ClientConnection;
+import engine.net.client.msg.ClientNetMsg;
+import engine.net.client.msg.ErrorPopupMsg;
+import engine.net.client.msg.guild.DisbandGuildMsg;
+import engine.net.client.msg.guild.LeaveGuildMsg;
+import engine.objects.Bane;
+import engine.objects.Guild;
+import engine.objects.GuildStatusController;
+import engine.objects.PlayerCharacter;
+
+public class DisbandGuildHandler extends AbstractClientMsgHandler {
+
+	public DisbandGuildHandler() {
+		super(DisbandGuildMsg.class);
+	}
+
+	@Override
+	protected boolean _handleNetMsg(ClientNetMsg baseMsg, ClientConnection origin) throws MsgSendException {
+
+		PlayerCharacter player;
+		Guild guild;
+		Dispatch dispatch;
+
+		player = SessionManager.getPlayerCharacter(origin);
+
+		//don't allow non guild leaders to disband guild.
+
+		if (player == null ||  GuildStatusController.isGuildLeader(player.getGuildStatus()) == false)
+			return true;
+
+		guild = player.getGuild();
+
+		if (guild == null || guild.isErrant())
+			return true;
+
+		// Don't allow disbanding if a city is owned
+		// *** Refactor: We should allow this by abandoning the tree first.
+
+		if (guild.getOwnedCity() != null) {
+			ErrorPopupMsg.sendErrorMsg(player, "You cannot disband a soverign guild!");
+			return true;
+		}
+
+		Bane guildBane = Bane.getBaneByAttackerGuild(guild);
+
+		if (guildBane != null) {
+			ErrorPopupMsg.sendErrorMsg(player, "You cannot disband a guild with an active bane!");
+			return true;
+		}
+
+		if (guild.getSubGuildList().size() > 0) {
+			ErrorPopupMsg.sendErrorMsg(player, "You cannot disband a nation!");
+			return true;
+		}
+
+		// Send message to guild (before kicking everyone out of it)
+
+		ChatManager.chatGuildInfo(guild, guild.getName() + " has been disbanded!");
+
+		// Log event to data warehous
+
+		GuildRecord guildRecord = GuildRecord.borrow(guild, Enum.RecordEventType.DISBAND);
+		DataWarehouse.pushToWarehouse(guildRecord);
+
+		// Remove us as a subguild of our nation
+
+		if (guild.getNation() != null && Guild.sameGuild(guild, guild.getNation()) == false && guild.getNation().isErrant() == false)
+			guild.getNation().removeSubGuild(guild);
+
+		// Update all online guild players
+
+		for (PlayerCharacter pcs : Guild.GuildRoster(guild)) {
+
+			guild.removePlayer(pcs,GuildHistoryType.DISBAND);
+		}
+
+		//Save Guild data
+
+		player.setGuildLeader(false);
+		player.setInnerCouncil(false);
+		guild.setGuildLeaderUUID(0);
+		guild.setNation(null);
+
+		DbManager.GuildQueries.DELETE_GUILD(guild);
+
+		DbManager.removeFromCache(guild);
+		WorldGrid.removeObject(guild, player);
+
+		// Send message back to client
+
+		LeaveGuildMsg leaveGuildMsg = new LeaveGuildMsg();
+		leaveGuildMsg.setMessage("You guild has been disbanded!");
+		dispatch = Dispatch.borrow(player, leaveGuildMsg);
+		DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+
+		return true;
+	}
+
+}
diff --git a/src/engine/net/client/handlers/DismissGuildHandler.java b/src/engine/net/client/handlers/DismissGuildHandler.java
new file mode 100644
index 00000000..57631c5f
--- /dev/null
+++ b/src/engine/net/client/handlers/DismissGuildHandler.java
@@ -0,0 +1,147 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.handlers;
+
+import engine.Enum;
+import engine.Enum.GameObjectType;
+import engine.exception.MsgSendException;
+import engine.gameManager.ChatManager;
+import engine.gameManager.DbManager;
+import engine.gameManager.GuildManager;
+import engine.gameManager.SessionManager;
+import engine.net.Dispatch;
+import engine.net.DispatchMessage;
+import engine.net.client.ClientConnection;
+import engine.net.client.msg.ClientNetMsg;
+import engine.net.client.msg.ErrorPopupMsg;
+import engine.net.client.msg.guild.DismissGuildMsg;
+import engine.net.client.msg.guild.SendGuildEntryMsg;
+import engine.objects.*;
+import engine.server.MBServerStatics;
+import engine.session.Session;
+
+import java.util.ArrayList;
+
+public class DismissGuildHandler extends AbstractClientMsgHandler {
+
+    public DismissGuildHandler() {
+        super(DismissGuildMsg.class);
+    }
+
+    @Override
+    protected boolean _handleNetMsg(ClientNetMsg baseMsg, ClientConnection origin) throws MsgSendException {
+
+        DismissGuildMsg dismissMsg;
+        PlayerCharacter player;
+        Guild toDismiss;
+        Guild nation;
+        Dispatch dispatch;
+
+        dismissMsg = (DismissGuildMsg) baseMsg;
+
+        player = SessionManager.getPlayerCharacter(origin);
+
+        if (player == null)
+            return true;
+
+        toDismiss = (Guild) DbManager.getObject(GameObjectType.Guild, dismissMsg.getGuildID());
+
+        if (toDismiss == null) {
+             ErrorPopupMsg.sendErrorMsg(player, "A Serious error has occured. Please post details for to ensure transaction integrity");
+            return true;
+        }
+
+        nation = player.getGuild();
+
+        if (nation == null) {
+             ErrorPopupMsg.sendErrorMsg(player, "Nothing to disband, your guild is not a nation!");
+            return true;
+        }
+
+        if (!nation.getSubGuildList().contains(toDismiss)) {
+             ErrorPopupMsg.sendErrorMsg(player, "You do not have authority to dismiss this guild!");
+            return true;
+        }
+
+        if (GuildStatusController.isGuildLeader(player.getGuildStatus()) == false) {
+           ErrorPopupMsg.sendErrorMsg(player, "Only a guild leader can dismiss a subguild!");
+            return true;
+        }
+
+        // Restriction on active bane desubbing
+
+        if (Bane.getBaneByAttackerGuild(toDismiss) != null)
+        {
+            ErrorPopupMsg.sendErrorMsg(player, "You may not dismiss subguild with active bane!");
+            return true;
+        }
+
+        switch (toDismiss.getGuildState()) {
+            case Sworn:
+
+                if (!DbManager.GuildQueries.UPDATE_PARENT(toDismiss.getObjectUUID(), MBServerStatics.worldUUID)) {
+                     ErrorPopupMsg.sendErrorMsg(player, "A Serious error has occured. Please post details for to ensure transaction integrity");
+                    return true;
+                }
+                nation.getSubGuildList().remove(toDismiss);
+                toDismiss.downgradeGuildState();
+                toDismiss.setNation(null);
+                GuildManager.updateAllGuildBinds(toDismiss, null);
+
+                break;
+            case Province:
+                if (!DbManager.GuildQueries.UPDATE_PARENT(toDismiss.getObjectUUID(),MBServerStatics.worldUUID)) {
+                    ErrorPopupMsg.sendErrorMsg(player, "A Serious error has occured. Please post details for to ensure transaction integrity");
+                    return true;
+                }
+                nation.getSubGuildList().remove(toDismiss);
+                toDismiss.downgradeGuildState();
+                toDismiss.setNation(toDismiss);
+
+                break;
+            case Petitioner:
+                nation.getSubGuildList().remove(toDismiss);
+                toDismiss.downgradeGuildState();
+                break;
+            case Protectorate:
+                nation.getSubGuildList().remove(toDismiss);
+                toDismiss.downgradeGuildState();
+                break;
+        }
+
+        GuildManager.updateAllGuildTags(toDismiss);
+
+        if (nation.getSubGuildList().isEmpty())
+            nation.downgradeGuildState();
+
+        SendGuildEntryMsg msg = new SendGuildEntryMsg(player);
+        dispatch = Dispatch.borrow(player, msg);
+        DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+
+        final Session s = SessionManager.getSession(player);
+
+        City.lastCityUpdate = System.currentTimeMillis();
+
+
+        ArrayList<PlayerCharacter> guildMembers = SessionManager.getActivePCsInGuildID(nation.getObjectUUID());
+
+        for (PlayerCharacter member : guildMembers) {
+            ChatManager.chatGuildInfo(member, toDismiss.getName() + " has been dismissed as a subguild!");
+        }
+
+        ArrayList<PlayerCharacter> dismissedMembers = SessionManager.getActivePCsInGuildID(toDismiss.getObjectUUID());
+
+        for (PlayerCharacter member : dismissedMembers) {
+            ChatManager.chatGuildInfo(member, nation.getName() + "has dismissed you as a sub!");
+        }
+
+        return true;
+    }
+}
diff --git a/src/engine/net/client/handlers/DoorTryOpenMsgHandler.java b/src/engine/net/client/handlers/DoorTryOpenMsgHandler.java
new file mode 100644
index 00000000..8fb76b03
--- /dev/null
+++ b/src/engine/net/client/handlers/DoorTryOpenMsgHandler.java
@@ -0,0 +1,88 @@
+package engine.net.client.handlers;
+
+import engine.Enum;
+import engine.Enum.DoorState;
+import engine.InterestManagement.WorldGrid;
+import engine.exception.MsgSendException;
+import engine.gameManager.BuildingManager;
+import engine.net.Dispatch;
+import engine.net.DispatchMessage;
+import engine.net.client.ClientConnection;
+import engine.net.client.msg.ClientNetMsg;
+import engine.net.client.msg.DoorTryOpenMsg;
+import engine.objects.AbstractWorldObject;
+import engine.objects.Blueprint;
+import engine.objects.Building;
+import engine.objects.PlayerCharacter;
+import engine.server.MBServerStatics;
+import org.pmw.tinylog.Logger;
+
+import java.util.HashSet;
+
+/*
+ * @Author:
+ * @Summary: Processes application protocol message which handle
+ * open and close door requests to and from the client.
+ * 
+ */
+public class DoorTryOpenMsgHandler extends AbstractClientMsgHandler {
+
+    public DoorTryOpenMsgHandler() {
+        super(DoorTryOpenMsg.class);
+    }
+
+    @Override
+    protected boolean _handleNetMsg(ClientNetMsg baseMsg, ClientConnection origin) throws MsgSendException {
+
+        // Member variable declaration
+        
+        PlayerCharacter player;
+        DoorTryOpenMsg msg;
+        Building targetBuilding;
+        int doorNumber;
+        
+        // Member variable assignment
+        
+        msg = (DoorTryOpenMsg)baseMsg;
+        player = origin.getPlayerCharacter();
+        targetBuilding =  BuildingManager.getBuildingFromCache(msg.getBuildingUUID());
+
+        if (player == null || targetBuilding == null) {
+            Logger.error("Player or Building returned NULL in OpenCloseDoor handling.");
+            return true;
+        }
+
+        // Must be within x distance from door to manipulate it
+        
+        if (player.getLoc().distanceSquared2D(targetBuilding.getLoc()) > MBServerStatics.OPENCLOSEDOORDISTANCE * MBServerStatics.OPENCLOSEDOORDISTANCE)
+            return true;
+
+        doorNumber = Blueprint.getDoorNumberbyMesh(msg.getDoorID());
+
+        if ((targetBuilding.isDoorLocked(doorNumber) == true) && msg.getToggle() == 0x01) {
+            msg.setUnk1(2);
+            Dispatch dispatch = Dispatch.borrow(player, msg);
+            DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+            return true; //don't open a locked door
+        }
+
+        if (msg.getToggle() == 0x01) {
+            targetBuilding.setDoorState(doorNumber, DoorState.OPEN);
+            targetBuilding.submitOpenDoorJob(msg.getDoorID());
+        } else {
+            targetBuilding.setDoorState(doorNumber, DoorState.CLOSED);
+        }
+
+        HashSet<AbstractWorldObject> container = WorldGrid.getObjectsInRangePartial(targetBuilding, MBServerStatics.CHARACTER_LOAD_RANGE,
+                MBServerStatics.MASK_PLAYER);
+
+        for (AbstractWorldObject awo : container) {
+            PlayerCharacter playerCharacter = (PlayerCharacter)awo;
+            Dispatch dispatch = Dispatch.borrow(playerCharacter, msg);
+            DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+        }
+
+        return true;
+    }
+
+}
diff --git a/src/engine/net/client/handlers/FormationFollowHandler.java b/src/engine/net/client/handlers/FormationFollowHandler.java
new file mode 100644
index 00000000..ff78f996
--- /dev/null
+++ b/src/engine/net/client/handlers/FormationFollowHandler.java
@@ -0,0 +1,75 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.handlers;
+
+import engine.exception.MsgSendException;
+import engine.gameManager.ChatManager;
+import engine.gameManager.GroupManager;
+import engine.gameManager.SessionManager;
+import engine.net.client.ClientConnection;
+import engine.net.client.msg.ClientNetMsg;
+import engine.net.client.msg.group.FormationFollowMsg;
+import engine.net.client.msg.group.GroupUpdateMsg;
+import engine.objects.Group;
+import engine.objects.PlayerCharacter;
+
+public class FormationFollowHandler extends AbstractClientMsgHandler {
+
+    public FormationFollowHandler() {
+        super(FormationFollowMsg.class);
+    }
+
+    @Override
+    protected boolean _handleNetMsg(ClientNetMsg baseMsg, ClientConnection origin)
+            throws MsgSendException {
+        FormationFollowMsg msg = (FormationFollowMsg) baseMsg;
+
+        PlayerCharacter source = SessionManager.getPlayerCharacter(origin);
+
+        if (source == null)
+            return false;
+
+        Group group = GroupManager.getGroup(source);
+
+        if (group == null)
+            return false;
+
+        if (msg.isFollow()) {// Toggle follow
+            source.toggleFollow();
+            if (group.getGroupLead() == source) {
+                if (source.getFollow()) {
+                    ChatManager.chatGroupInfo(source, "falls into formation");
+                } else {
+                    ChatManager.chatGroupInfo(source, "breaks formation");
+                }
+            } else {
+                if (source.getFollow()) {
+                    ChatManager.chatGroupInfo(source, source.getFirstName() + " falls into formation");
+                } else {
+                    ChatManager.chatGroupInfo(source, source.getFirstName() + " breaks formation");
+                }
+            }
+            GroupUpdateMsg gum = new GroupUpdateMsg();
+            gum.setGroup(group);
+            gum.setPlayer(source);
+            gum.setMessageType(8);
+
+            group.sendUpdate(gum);
+        } else {// Set Formation
+            if (group.getGroupLead() != source) {
+                return false;
+            }
+            group.setFormation(msg.getFormation());
+
+        }
+        return true;
+    }
+
+}
diff --git a/src/engine/net/client/handlers/FriendAcceptHandler.java b/src/engine/net/client/handlers/FriendAcceptHandler.java
new file mode 100644
index 00000000..473d04d8
--- /dev/null
+++ b/src/engine/net/client/handlers/FriendAcceptHandler.java
@@ -0,0 +1,78 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.handlers;
+
+import engine.Enum.DispatchChannel;
+import engine.exception.MsgSendException;
+import engine.gameManager.ChatManager;
+import engine.gameManager.SessionManager;
+import engine.net.Dispatch;
+import engine.net.DispatchMessage;
+import engine.net.client.ClientConnection;
+import engine.net.client.msg.AcceptFriendMsg;
+import engine.net.client.msg.AddFriendMessage;
+import engine.net.client.msg.ClientNetMsg;
+import engine.net.client.msg.ErrorPopupMsg;
+import engine.objects.PlayerCharacter;
+import engine.objects.PlayerFriends;
+
+public class FriendAcceptHandler extends AbstractClientMsgHandler {
+
+	public FriendAcceptHandler() {
+		super(AcceptFriendMsg.class);
+	}
+
+	@Override
+	protected boolean _handleNetMsg(ClientNetMsg baseMsg,
+			ClientConnection origin) throws MsgSendException {
+		
+		PlayerCharacter player = origin.getPlayerCharacter();
+		
+		if (player == null)
+			return true;
+		
+
+		AcceptFriendMsg msg = (AcceptFriendMsg)baseMsg;
+		
+		
+			HandleAcceptFriend(player,msg);
+			
+		return true;
+	}
+	
+
+	
+	//change to Request
+	public static void HandleAcceptFriend(PlayerCharacter player, AcceptFriendMsg msg){
+	PlayerCharacter sourceFriend = SessionManager.getPlayerCharacterByLowerCaseName(msg.sourceName);
+	
+	if (sourceFriend == null){
+		ErrorPopupMsg.sendErrorMsg(player, "Could not find player " + msg.sourceName);
+		return;
+	}
+	
+	PlayerFriends.AddToFriends(sourceFriend.getObjectUUID(), player.getObjectUUID());
+	PlayerFriends.AddToFriends(player.getObjectUUID(), sourceFriend.getObjectUUID());
+	
+	
+	AddFriendMessage outMsg = new AddFriendMessage(player);
+	
+	Dispatch dispatch = Dispatch.borrow(sourceFriend, outMsg);
+	DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
+	
+	outMsg = new AddFriendMessage(sourceFriend);
+	 dispatch = Dispatch.borrow(player, outMsg);
+	DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
+	
+	ChatManager.chatSystemInfo(sourceFriend, player.getFirstName() + " has agreed to be your friend.");
+	
+	}
+	
+}
diff --git a/src/engine/net/client/handlers/FriendDeclineHandler.java b/src/engine/net/client/handlers/FriendDeclineHandler.java
new file mode 100644
index 00000000..af72702a
--- /dev/null
+++ b/src/engine/net/client/handlers/FriendDeclineHandler.java
@@ -0,0 +1,63 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.handlers;
+
+import engine.Enum.DispatchChannel;
+import engine.exception.MsgSendException;
+import engine.gameManager.SessionManager;
+import engine.net.Dispatch;
+import engine.net.DispatchMessage;
+import engine.net.client.ClientConnection;
+import engine.net.client.msg.ClientNetMsg;
+import engine.net.client.msg.DeclineFriendMsg;
+import engine.net.client.msg.ErrorPopupMsg;
+import engine.objects.PlayerCharacter;
+
+public class FriendDeclineHandler extends AbstractClientMsgHandler {
+
+	public FriendDeclineHandler() {
+		super(DeclineFriendMsg.class);
+	}
+
+	@Override
+	protected boolean _handleNetMsg(ClientNetMsg baseMsg,
+			ClientConnection origin) throws MsgSendException {
+		
+		PlayerCharacter player = origin.getPlayerCharacter();
+		
+		if (player == null)
+			return true;
+		
+
+		DeclineFriendMsg msg = (DeclineFriendMsg)baseMsg;
+		
+		
+			HandleDeclineFriend(player,msg);
+			
+		return true;
+	}
+	
+
+	
+	//change to Request
+	public static void HandleDeclineFriend(PlayerCharacter player, DeclineFriendMsg msg){
+		PlayerCharacter sourceFriend = SessionManager.getPlayerCharacterByLowerCaseName(msg.sourceName);
+		
+		if (sourceFriend == null){
+			ErrorPopupMsg.sendErrorMsg(player, "Could not find player " + msg.sourceName);
+			return;
+		}
+	
+	Dispatch dispatch = Dispatch.borrow(sourceFriend, msg);
+	DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
+	
+	}
+	
+}
diff --git a/src/engine/net/client/handlers/FriendRequestHandler.java b/src/engine/net/client/handlers/FriendRequestHandler.java
new file mode 100644
index 00000000..56e94914
--- /dev/null
+++ b/src/engine/net/client/handlers/FriendRequestHandler.java
@@ -0,0 +1,69 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.handlers;
+
+import engine.Enum.DispatchChannel;
+import engine.exception.MsgSendException;
+import engine.gameManager.ChatManager;
+import engine.gameManager.SessionManager;
+import engine.net.Dispatch;
+import engine.net.DispatchMessage;
+import engine.net.client.ClientConnection;
+import engine.net.client.msg.ClientNetMsg;
+import engine.net.client.msg.ErrorPopupMsg;
+import engine.net.client.msg.FriendRequestMsg;
+import engine.objects.PlayerCharacter;
+
+public class FriendRequestHandler extends AbstractClientMsgHandler {
+
+	public FriendRequestHandler() {
+		super(FriendRequestMsg.class);
+	}
+
+	@Override
+	protected boolean _handleNetMsg(ClientNetMsg baseMsg,
+			ClientConnection origin) throws MsgSendException {
+		
+		PlayerCharacter player = origin.getPlayerCharacter();
+		
+		if (player == null)
+			return true;
+		
+
+		FriendRequestMsg msg = (FriendRequestMsg)baseMsg;
+		
+			HandleRequestFriend(player,msg);
+			
+		return true;
+	}
+	
+
+	
+	public static void HandleRequestFriend(PlayerCharacter player, FriendRequestMsg msg){
+	PlayerCharacter targetFriend = SessionManager.getPlayerCharacterByLowerCaseName(msg.friendName);
+	
+	if (targetFriend == null){
+		ErrorPopupMsg.sendErrorMsg(player, "Could not find player " + msg.friendName);
+		return;
+	}
+	
+	if (targetFriend.equals(player))
+		return;
+	
+	
+	
+	
+	Dispatch dispatch = Dispatch.borrow(targetFriend, msg);
+	DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
+	ChatManager.chatSystemInfo(player, "Your friend request has been sent.");
+	
+	}
+	
+}
diff --git a/src/engine/net/client/handlers/FurnitureHandler.java b/src/engine/net/client/handlers/FurnitureHandler.java
new file mode 100644
index 00000000..65dce932
--- /dev/null
+++ b/src/engine/net/client/handlers/FurnitureHandler.java
@@ -0,0 +1,48 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.handlers;
+
+import engine.Enum.DispatchChannel;
+import engine.exception.MsgSendException;
+import engine.net.Dispatch;
+import engine.net.DispatchMessage;
+import engine.net.client.ClientConnection;
+import engine.net.client.msg.ClientNetMsg;
+import engine.net.client.msg.FurnitureMsg;
+import engine.objects.PlayerCharacter;
+
+public class FurnitureHandler extends AbstractClientMsgHandler {
+
+	public FurnitureHandler() {
+		super(FurnitureMsg.class);
+	}
+
+	@Override
+	protected boolean _handleNetMsg(ClientNetMsg baseMsg,
+			ClientConnection origin) throws MsgSendException {
+
+		FurnitureMsg msg = (FurnitureMsg) baseMsg;
+
+		PlayerCharacter pc = origin.getPlayerCharacter();
+		if (pc == null) {
+			return false;
+		}
+		
+		if (msg.getType() == 1)
+		msg.setType(2);
+		
+		if (msg.getType() == 3)
+			msg.setType(2);
+		 Dispatch dispatch = Dispatch.borrow(pc, msg);
+         DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
+		return true;
+	}
+
+}
diff --git a/src/engine/net/client/handlers/GroupInviteHandler.java b/src/engine/net/client/handlers/GroupInviteHandler.java
new file mode 100644
index 00000000..7f0916d0
--- /dev/null
+++ b/src/engine/net/client/handlers/GroupInviteHandler.java
@@ -0,0 +1,126 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.handlers;
+
+import engine.Enum;
+import engine.Enum.GameObjectType;
+import engine.exception.MsgSendException;
+import engine.gameManager.GroupManager;
+import engine.gameManager.SessionManager;
+import engine.net.Dispatch;
+import engine.net.DispatchMessage;
+import engine.net.client.ClientConnection;
+import engine.net.client.msg.ClientNetMsg;
+import engine.net.client.msg.group.GroupInviteMsg;
+import engine.net.client.msg.group.GroupUpdateMsg;
+import engine.objects.Group;
+import engine.objects.PlayerCharacter;
+
+public class GroupInviteHandler extends AbstractClientMsgHandler {
+
+	public GroupInviteHandler() {
+		super(GroupInviteMsg.class);
+	}
+
+	@Override
+	protected boolean _handleNetMsg(ClientNetMsg baseMsg,
+
+			ClientConnection origin) throws MsgSendException {
+		GroupInviteMsg msg = (GroupInviteMsg) baseMsg;
+		PlayerCharacter source = SessionManager.getPlayerCharacter(origin);
+
+		if (source == null)
+			return false;
+
+		Group group = GroupManager.getGroup(source);
+
+		// Group is new, create it.
+
+		if (group == null)
+			group = GroupInviteHandler.createGroup(source, origin);
+
+		if (group == null)
+			return false;
+
+		if (!group.isGroupLead(source)) // person doing invite must be group lead
+			return true;
+
+		PlayerCharacter target = null;
+
+		if (msg.getInvited() == 1) { // Use name for invite
+			target = SessionManager.getPlayerCharacterByLowerCaseName(msg.getName().toLowerCase());
+		} else { // Use ID for invite
+			target = SessionManager.getPlayerCharacterByID(msg.getTargetID());
+		}
+
+		if (target == null)
+			return false;
+
+		// Client must be online
+
+		if (SessionManager.getClientConnection(target) == null)
+			return false;
+
+		if (source == target) // Inviting self, so we're done
+			return false;
+
+
+		//Skip invite if target is ignoring source
+
+		if (target.isIgnoringPlayer(source))
+			return false;
+
+
+		// dont block invites to people already in a group and
+		// dont check for pending invites, the client does it
+		// Send invite message to target
+
+		msg.setSourceType(GameObjectType.PlayerCharacter.ordinal());
+		msg.setSourceID(source.getObjectUUID());
+		msg.setTargetType(0);
+		msg.setTargetID(0);
+		msg.setGroupType(GameObjectType.Group.ordinal());
+		msg.setGroupID(group.getObjectUUID());
+		msg.setInvited(1);
+		msg.setName(source.getFirstName());
+
+		Dispatch dispatch = Dispatch.borrow(target, msg);
+		DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+
+		return true;
+	}
+
+	// this can only be called if you already know you are not in a group
+	// and have issued an invite
+
+	private static Group createGroup(PlayerCharacter pc, ClientConnection origin) {
+
+		if (pc == null)
+			return null;
+
+		Group group = new Group(pc, GroupManager.incrGroupCount());
+		group.addGroupMember(pc);
+		GroupManager.addNewGroup(group);
+
+		pc.setFollow(false);
+		// Send add self to group message
+		GroupUpdateMsg msg = new GroupUpdateMsg();
+		msg.setGroup(group);
+		msg.setPlayer(pc);
+		msg.setMessageType(1);
+		Dispatch dispatch = Dispatch.borrow(pc, msg);
+		DispatchMessage.dispatchMsgDispatch(dispatch, engine.Enum.DispatchChannel.SECONDARY);
+
+		group.addUpdateGroupJob();
+
+		return group;
+	}
+
+}
diff --git a/src/engine/net/client/handlers/GroupInviteResponseHandler.java b/src/engine/net/client/handlers/GroupInviteResponseHandler.java
new file mode 100644
index 00000000..8db3182d
--- /dev/null
+++ b/src/engine/net/client/handlers/GroupInviteResponseHandler.java
@@ -0,0 +1,112 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.handlers;
+
+import engine.Enum;
+import engine.exception.MsgSendException;
+import engine.gameManager.ChatManager;
+import engine.gameManager.GroupManager;
+import engine.net.Dispatch;
+import engine.net.DispatchMessage;
+import engine.net.client.ClientConnection;
+import engine.net.client.msg.ClientNetMsg;
+import engine.net.client.msg.group.GroupInviteResponseMsg;
+import engine.net.client.msg.group.GroupUpdateMsg;
+import engine.objects.Group;
+import engine.objects.PlayerCharacter;
+
+import java.util.Set;
+
+import static engine.net.client.handlers.KeyCloneAudit.KEYCLONEAUDIT;
+
+public class GroupInviteResponseHandler extends AbstractClientMsgHandler {
+
+    public GroupInviteResponseHandler() {
+        super(GroupInviteResponseMsg.class);
+    }
+
+    @Override
+    protected boolean _handleNetMsg(ClientNetMsg baseMsg,
+            ClientConnection origin) throws MsgSendException {
+
+        GroupInviteResponseMsg msg = (GroupInviteResponseMsg) baseMsg;
+
+        PlayerCharacter player = origin.getPlayerCharacter();
+
+        if (player == null)
+            return false;
+
+        // if we are already in a group we are leaving it
+
+        Group currGroup = GroupManager.getGroup(player);
+
+        if (currGroup != null) // if we are already in a group we are leaving it
+            GroupManager.LeaveGroup(player);
+
+	// not sure we need to test for invites to wrong grp as only
+        // 1 invite can be on screen at a time
+        //if (invitesPending.get(player) != msg.getGroupID()) // Can't accept
+        // invite to
+        // wrong group
+        //	return;
+
+        Group group = GroupManager.getGroup(msg.getGroupID());
+
+        if (group == null)
+            return false;
+
+        if (group.addGroupMember(player) == false)
+            return false;
+        {
+            player.setFollow(true);
+            GroupManager.addPlayerGroupMapping(player, group);
+            Set<PlayerCharacter> members = group.getMembers();
+            GroupUpdateMsg groupUpdateMsg;
+
+            // Send all group members to player added
+            for (PlayerCharacter groupMember : members) {
+
+                groupUpdateMsg = new GroupUpdateMsg();
+                groupUpdateMsg.setGroup(group);
+                groupUpdateMsg.setMessageType(1);
+
+                if (groupMember == null)
+                    continue;
+
+                if (groupMember.equals(player))
+                    continue;
+
+                groupUpdateMsg.setPlayer(groupMember);
+
+               Dispatch dispatch = Dispatch.borrow(player, groupUpdateMsg);
+               DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+
+            }
+
+            // send new group member to everyone in group.
+            groupUpdateMsg = new GroupUpdateMsg();
+            groupUpdateMsg.setGroup(group);
+            groupUpdateMsg.setMessageType(1);
+            groupUpdateMsg.setPlayer(player);
+            group.sendUpdate(groupUpdateMsg);
+
+            String text = player.getFirstName() + " has joined the group.";
+            ChatManager.chatGroupInfo(player, text);
+
+            // Run Keyclone Audit
+
+            KEYCLONEAUDIT.audit(player, group);
+
+        return true;
+    }
+    }
+}
+
+
diff --git a/src/engine/net/client/handlers/GroupUpdateHandler.java b/src/engine/net/client/handlers/GroupUpdateHandler.java
new file mode 100644
index 00000000..944a3b5b
--- /dev/null
+++ b/src/engine/net/client/handlers/GroupUpdateHandler.java
@@ -0,0 +1,33 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.handlers;
+
+import engine.exception.MsgSendException;
+import engine.net.client.ClientConnection;
+import engine.net.client.msg.ClientNetMsg;
+import engine.net.client.msg.group.GroupUpdateMsg;
+
+public class GroupUpdateHandler extends AbstractClientMsgHandler {
+
+    public GroupUpdateHandler() {
+        super(GroupUpdateMsg.class);
+    }
+
+    @Override
+    protected boolean _handleNetMsg(ClientNetMsg baseMsg,
+            ClientConnection origin) throws MsgSendException {
+
+		//GroupUpdateMsg msg = (GroupUpdateMsg) baseMsg;
+		// not sure what to do with these as we spend our time sending them
+        // to the whole group ourselves
+        return true;
+    }
+
+}
diff --git a/src/engine/net/client/handlers/GuildControlHandler.java b/src/engine/net/client/handlers/GuildControlHandler.java
new file mode 100644
index 00000000..112b70d2
--- /dev/null
+++ b/src/engine/net/client/handlers/GuildControlHandler.java
@@ -0,0 +1,74 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.handlers;
+
+import engine.Enum;
+import engine.exception.MsgSendException;
+import engine.gameManager.SessionManager;
+import engine.net.Dispatch;
+import engine.net.DispatchMessage;
+import engine.net.client.ClientConnection;
+import engine.net.client.msg.ClientNetMsg;
+import engine.net.client.msg.ErrorPopupMsg;
+import engine.net.client.msg.guild.GuildControlMsg;
+import engine.net.client.msg.guild.GuildListMsg;
+import engine.objects.GuildStatusController;
+import engine.objects.PlayerCharacter;
+
+public class GuildControlHandler extends AbstractClientMsgHandler {
+
+	public GuildControlHandler() {
+		super(GuildControlMsg.class);
+	}
+	
+	// TODO Don't think this protocolMsg (0x3235E5EA) is actually player history. so
+	// take further look at it.
+	@Override
+	protected boolean _handleNetMsg(ClientNetMsg baseMsg, ClientConnection origin) throws MsgSendException {
+		GuildControlMsg msg = (GuildControlMsg) baseMsg;
+        Dispatch dispatch;
+
+		// until we know what it's for, just echo it back.
+		msg.setUnknown05((byte) 1);
+
+		//Send a GuildList msg
+		if(msg.getUnknown01() == 1) {
+
+			PlayerCharacter player = SessionManager.getPlayerCharacter(origin);
+			//TODO figure out why GL can't be changed and IC can't be banished
+			//Bounce back the rank options
+			msg.setGM((byte) (GuildStatusController.isGuildLeader(player.getGuildStatus()) ? 1 : 0));
+
+            dispatch = Dispatch.borrow(player, msg);
+            DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+
+            if (GuildStatusController.isInnerCouncil(player.getGuildStatus()) || GuildStatusController.isGuildLeader(player.getGuildStatus())) {
+                dispatch = Dispatch.borrow(player, new GuildListMsg(player.getGuild()));
+                DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+            }   else
+                ErrorPopupMsg.sendErrorMsg(player, "Only guild leader and inner council have such authority!");
+
+
+        } else if(msg.getUnknown01() == 2) {
+			PlayerCharacter player = SessionManager.getPlayerCharacter(origin);
+			
+			//If we don't get a valid PC for whatever reason.. just ignore it.
+			PlayerCharacter pc = PlayerCharacter.getFromCache(msg.getUnknown03());
+
+			if(pc != null) {
+				dispatch = Dispatch.borrow(player,new GuildListMsg(pc));
+				DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+			}
+		}
+		
+		return true;
+	}
+
+}
diff --git a/src/engine/net/client/handlers/GuildCreationCloseHandler.java b/src/engine/net/client/handlers/GuildCreationCloseHandler.java
new file mode 100644
index 00000000..f72edff8
--- /dev/null
+++ b/src/engine/net/client/handlers/GuildCreationCloseHandler.java
@@ -0,0 +1,30 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.handlers;
+
+import engine.exception.MsgSendException;
+import engine.net.client.ClientConnection;
+import engine.net.client.msg.ClientNetMsg;
+import engine.net.client.msg.guild.GuildCreationCloseMsg;
+
+public class GuildCreationCloseHandler extends AbstractClientMsgHandler {
+
+	public GuildCreationCloseHandler() {
+		super(GuildCreationCloseMsg.class);
+	}
+
+	@Override
+	protected boolean _handleNetMsg(ClientNetMsg baseMsg, ClientConnection origin) throws MsgSendException {
+//	GuildCreationCloseMsg msg = (GuildCreationCloseMsg) baseMsg;
+//	origin.sendMsg(msg);
+		return true;
+	}
+
+}
diff --git a/src/engine/net/client/handlers/GuildCreationFinalizeHandler.java b/src/engine/net/client/handlers/GuildCreationFinalizeHandler.java
new file mode 100644
index 00000000..57295aa1
--- /dev/null
+++ b/src/engine/net/client/handlers/GuildCreationFinalizeHandler.java
@@ -0,0 +1,165 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+
+
+
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.handlers;
+
+import engine.Enum;
+import engine.Enum.DispatchChannel;
+import engine.Enum.GuildHistoryType;
+import engine.Enum.ItemType;
+import engine.Enum.OwnerType;
+import engine.exception.MsgSendException;
+import engine.gameManager.ChatManager;
+import engine.gameManager.DbManager;
+import engine.gameManager.GuildManager;
+import engine.gameManager.SessionManager;
+import engine.net.Dispatch;
+import engine.net.DispatchMessage;
+import engine.net.client.ClientConnection;
+import engine.net.client.msg.ClientNetMsg;
+import engine.net.client.msg.ErrorPopupMsg;
+import engine.net.client.msg.guild.GuildCreationFinalizeMsg;
+import engine.net.client.msg.guild.GuildInfoMsg;
+import engine.objects.*;
+import engine.util.StringUtils;
+
+public class GuildCreationFinalizeHandler extends AbstractClientMsgHandler {
+
+	public GuildCreationFinalizeHandler() {
+		super(GuildCreationFinalizeMsg.class);
+	}
+
+	@Override
+	protected boolean _handleNetMsg(ClientNetMsg baseMsg, ClientConnection origin) throws MsgSendException {
+
+		PlayerCharacter player;
+		GuildCreationFinalizeMsg msg;
+		Enum.GuildType charterType;
+		Guild newGuild;
+		ItemBase itemBase;
+		Item charter;
+		Dispatch dispatch;
+
+		msg = (GuildCreationFinalizeMsg) baseMsg;
+
+		player = SessionManager.getPlayerCharacter(origin);
+
+		boolean isGuildLeader = GuildStatusController.isGuildLeader(player.getGuildStatus());
+		if (GuildStatusController.isGuildLeader(player.getGuildStatus()) || player.getGuild() != null && player.getGuild().getGuildLeaderUUID() == player.getObjectUUID()) {
+			ErrorPopupMsg.sendErrorPopup(player, GuildManager.MUST_LEAVE_GUILD);
+			return true;
+		}
+
+		//Validate the Charter
+
+		charter = msg.getCharter();
+
+		if (charter == null || charter.getOwnerType() != OwnerType.PlayerCharacter || charter.getOwnerID() != player.getObjectUUID()) {
+			ErrorPopupMsg.sendErrorPopup(player, GuildManager.NO_CHARTER_FOUND);
+			return true;
+		}
+
+		
+
+		itemBase = charter.getItemBase();
+
+
+		// Item must be a valid charterType (type 10 in db)
+
+		if (itemBase == null || (itemBase.getType().equals(ItemType.GUILDCHARTER) == false)) {
+			ErrorPopupMsg.sendErrorPopup(player, GuildManager.NO_CHARTER_FOUND);
+			return true;
+		}
+		charterType = Enum.GuildType.getGuildTypeFromCharter(itemBase);
+
+
+
+		if (charterType == null){
+			ErrorPopupMsg.sendErrorPopup(player, GuildManager.NO_CHARTER_FOUND);
+			return true;
+		}
+
+
+
+
+
+		//Validate Guild Tags
+
+		if (!msg.getGuildTag().isValid()) {
+			ErrorPopupMsg.sendErrorPopup(player, GuildManager.CREST_RESERVED);
+			return true;
+		}
+
+		// Validation passes.  Leave current guild and create new one.
+
+		if (player.getGuild() != null && player.getGuild().getObjectUUID() != 0)
+			player.getGuild().removePlayer(player,GuildHistoryType.LEAVE);
+
+
+
+		int leadershipType = ((msg.getICVoteFlag() << 1) | msg.getMemberVoteFlag());
+
+		newGuild = new Guild( msg.getName(),null, charterType.ordinal(),
+				charterType.getLeadershipType(leadershipType), msg.getGuildTag(),
+				StringUtils.truncate(msg.getMotto(), 120));
+
+		newGuild.setGuildLeaderForCreate(player);
+
+		synchronized (this) {
+			if (!DbManager.GuildQueries.IS_NAME_UNIQUE(msg.getName())) {
+				ErrorPopupMsg.sendErrorPopup(player, GuildManager.UNIQUE_NAME);
+				return true;
+			}
+
+			if (!DbManager.GuildQueries.IS_CREST_UNIQUE(msg.getGuildTag())) {
+				ErrorPopupMsg.sendErrorPopup(player, GuildManager.UNIQUE_CREST);
+				return true;
+			}
+
+			newGuild = DbManager.GuildQueries.SAVE_TO_DATABASE(newGuild);
+		}
+
+		if (newGuild == null) {
+			ErrorPopupMsg.sendErrorPopup(player, GuildManager.FAILURE_TO_SWEAR_GUILD);
+			return true;
+		}
+		
+		dispatch = Dispatch.borrow(player, msg);
+		DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
+
+		GuildManager.joinGuild(player, newGuild, GuildHistoryType.CREATE);
+		
+		newGuild.setGuildLeader(player);
+		player.setGuildLeader(true);
+		player.setInnerCouncil(true);
+		player.setFullMember(true);
+		player.setGuildTitle(charterType.getNumberOfRanks() - 1);
+		player.getCharItemManager().delete(charter);
+		player.getCharItemManager().updateInventory();
+		player.incVer();
+
+		DispatchMessage.sendToAllInRange(player, new GuildInfoMsg(player, newGuild, 2));
+
+		ChatManager.chatSystemInfo(player, msg.getName() + " has arrived on Grief server!");
+
+		return true;
+	}
+}
diff --git a/src/engine/net/client/handlers/GuildCreationOptionsHandler.java b/src/engine/net/client/handlers/GuildCreationOptionsHandler.java
new file mode 100644
index 00000000..6dbbb680
--- /dev/null
+++ b/src/engine/net/client/handlers/GuildCreationOptionsHandler.java
@@ -0,0 +1,47 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.handlers;
+
+import engine.exception.MsgSendException;
+import engine.net.Dispatch;
+import engine.net.DispatchMessage;
+import engine.net.client.ClientConnection;
+import engine.net.client.msg.ClientNetMsg;
+import engine.net.client.msg.guild.GuildCreationOptionsMsg;
+import engine.objects.PlayerCharacter;
+
+public class GuildCreationOptionsHandler extends AbstractClientMsgHandler {
+
+	public GuildCreationOptionsHandler() {
+		super(GuildCreationOptionsMsg.class);
+	}
+
+	@Override
+	protected boolean _handleNetMsg(ClientNetMsg baseMsg, ClientConnection origin) throws MsgSendException {
+		GuildCreationOptionsMsg msg = (GuildCreationOptionsMsg) baseMsg;
+        PlayerCharacter sourcePlayer = origin.getPlayerCharacter();
+        Dispatch dispatch;
+
+		if(msg.getScreenType() == 1) {
+			msg.setScreenType(3);
+		} else if(msg.getScreenType() == 2) {
+			msg.setScreenType(4);
+		}
+
+        if (sourcePlayer == null)
+            return true;
+
+        dispatch = Dispatch.borrow(sourcePlayer, msg);
+        DispatchMessage.dispatchMsgDispatch(dispatch, engine.Enum.DispatchChannel.SECONDARY);
+
+		return true;
+	}
+
+}
diff --git a/src/engine/net/client/handlers/GuildInfoHandler.java b/src/engine/net/client/handlers/GuildInfoHandler.java
new file mode 100644
index 00000000..8e490a97
--- /dev/null
+++ b/src/engine/net/client/handlers/GuildInfoHandler.java
@@ -0,0 +1,67 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.handlers;
+
+import engine.Enum.GameObjectType;
+import engine.exception.MsgSendException;
+import engine.gameManager.SessionManager;
+import engine.net.Dispatch;
+import engine.net.DispatchMessage;
+import engine.net.client.ClientConnection;
+import engine.net.client.msg.ClientNetMsg;
+import engine.net.client.msg.guild.GuildInfoMsg;
+import engine.objects.PlayerCharacter;
+
+public class GuildInfoHandler extends AbstractClientMsgHandler {
+
+	public GuildInfoHandler() {
+		super(GuildInfoMsg.class);
+	}
+
+	@Override
+	protected boolean _handleNetMsg(ClientNetMsg baseMsg, ClientConnection origin) throws MsgSendException {
+		GuildInfoMsg msg = (GuildInfoMsg) baseMsg;
+        Dispatch dispatch;
+
+		// get source player
+		PlayerCharacter sourcePlayer = SessionManager
+				.getPlayerCharacter(origin);
+
+		if (sourcePlayer == null)
+			return true;
+		
+		if(msg.getMsgType() == 1) {
+			dispatch = Dispatch.borrow(sourcePlayer, new GuildInfoMsg(sourcePlayer, sourcePlayer.getGuild(), 4));
+            DispatchMessage.dispatchMsgDispatch(dispatch, engine.Enum.DispatchChannel.SECONDARY);
+		} else if(msg.getMsgType() == 5) {
+			
+			if(msg.getObjectType() == GameObjectType.PlayerCharacter.ordinal()) {
+				PlayerCharacter pc = PlayerCharacter.getPlayerCharacter(msg.getObjectID());
+                dispatch = Dispatch.borrow(sourcePlayer, new GuildInfoMsg(pc, pc.getGuild(), 5));
+                DispatchMessage.dispatchMsgDispatch(dispatch, engine.Enum.DispatchChannel.SECONDARY);
+			} else {
+				//TODO Change this to a null object when we make a null object.
+
+                dispatch = Dispatch.borrow(sourcePlayer,new GuildInfoMsg(sourcePlayer, sourcePlayer.getGuild(), 1));
+                DispatchMessage.dispatchMsgDispatch(dispatch, engine.Enum.DispatchChannel.SECONDARY);
+			}
+		}
+		
+		// Send PromoteDemoteScreen info message response. 0x001D4DF6
+
+		// Send guild member list? 0x6949C720
+		// GuildList(source, origin);
+
+		// send 0x3235E5EA? See what that is
+		
+		return true;
+	}
+
+}
diff --git a/src/engine/net/client/handlers/GuildListHandler.java b/src/engine/net/client/handlers/GuildListHandler.java
new file mode 100644
index 00000000..f4eb052c
--- /dev/null
+++ b/src/engine/net/client/handlers/GuildListHandler.java
@@ -0,0 +1,36 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.handlers;
+
+import engine.exception.MsgSendException;
+import engine.net.client.ClientConnection;
+import engine.net.client.msg.ClientNetMsg;
+import engine.net.client.msg.guild.GuildListMsg;
+
+public class GuildListHandler extends AbstractClientMsgHandler {
+
+	public GuildListHandler() {
+		super(GuildListMsg.class);
+	}
+
+	@Override
+	protected boolean _handleNetMsg(ClientNetMsg baseMsg, ClientConnection origin) throws MsgSendException {
+//		GuildListMsg msg = (GuildListMsg) baseMsg;
+		
+		// GuildListMsg msg = new GuildListMsg(origin);
+		// GuildTableList gtl = new GuildTableList();
+		// gtl.setUUID(pc.getUUID()); gtl.setName(pc.getName());
+		// msg.add(gtl);
+		// origin.sendMsg(msg);
+		
+		return true;
+	}
+
+}
diff --git a/src/engine/net/client/handlers/GuildUnknownHandler.java b/src/engine/net/client/handlers/GuildUnknownHandler.java
new file mode 100644
index 00000000..c488e0cf
--- /dev/null
+++ b/src/engine/net/client/handlers/GuildUnknownHandler.java
@@ -0,0 +1,29 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.handlers;
+
+import engine.exception.MsgSendException;
+import engine.net.client.ClientConnection;
+import engine.net.client.msg.ClientNetMsg;
+import engine.net.client.msg.guild.GuildUnknownMsg;
+
+public class GuildUnknownHandler extends AbstractClientMsgHandler {
+
+	public GuildUnknownHandler() {
+		super(GuildUnknownMsg.class);
+	}
+
+	@Override
+	protected boolean _handleNetMsg(ClientNetMsg baseMsg, ClientConnection origin) throws MsgSendException {
+//		GuildUnknownMsg msg = (GuildUnknownMsg) baseMsg;
+		return true;
+	}
+
+}
diff --git a/src/engine/net/client/handlers/HirelingServiceMsgHandler.java b/src/engine/net/client/handlers/HirelingServiceMsgHandler.java
new file mode 100644
index 00000000..c5c90a70
--- /dev/null
+++ b/src/engine/net/client/handlers/HirelingServiceMsgHandler.java
@@ -0,0 +1,94 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+
+
+
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.handlers;
+
+
+import engine.Enum.DispatchChannel;
+import engine.exception.MsgSendException;
+import engine.gameManager.BuildingManager;
+import engine.gameManager.SessionManager;
+import engine.net.Dispatch;
+import engine.net.DispatchMessage;
+import engine.net.client.ClientConnection;
+import engine.net.client.msg.ClientNetMsg;
+import engine.net.client.msg.HirelingServiceMsg;
+import engine.net.client.msg.ManageNPCMsg;
+import engine.objects.Building;
+import engine.objects.NPC;
+import engine.objects.PlayerCharacter;
+
+public class HirelingServiceMsgHandler extends AbstractClientMsgHandler {
+
+	public HirelingServiceMsgHandler() {
+		super(HirelingServiceMsg.class);
+	}
+
+	@Override
+	protected boolean _handleNetMsg(ClientNetMsg baseMsg, ClientConnection origin) throws MsgSendException {
+
+		PlayerCharacter player;
+		HirelingServiceMsg msg;
+
+		msg = (HirelingServiceMsg) baseMsg;
+
+		// get PlayerCharacter of person accepting invite
+
+		player = SessionManager.getPlayerCharacter(origin);
+
+		if (player == null)
+			return true;
+		
+	switch (msg.messageType){
+	case HirelingServiceMsg.SETREPAIRCOST:
+		Building building = BuildingManager.getBuildingFromCache(msg.buildingID);
+		
+		if (building == null)
+			return true;
+		
+		NPC npc = NPC.getFromCache(msg.npcID);
+		
+		if (npc == null)
+			return true;
+		
+		if (!BuildingManager.playerCanManage(player, building))
+			return true;
+		
+	
+		
+		npc.setRepairCost(msg.repairCost);
+		ManageNPCMsg outMsg = new ManageNPCMsg(npc);
+		Dispatch dispatch = Dispatch.borrow(player, msg);
+		
+		DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
+		dispatch = Dispatch.borrow(player, outMsg);
+		
+		DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
+		break;
+	}
+		
+		
+		return true;
+
+		
+	}
+
+}
diff --git a/src/engine/net/client/handlers/InviteToGuildHandler.java b/src/engine/net/client/handlers/InviteToGuildHandler.java
new file mode 100644
index 00000000..4ccadb4c
--- /dev/null
+++ b/src/engine/net/client/handlers/InviteToGuildHandler.java
@@ -0,0 +1,151 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.handlers;
+
+
+import engine.Enum;
+import engine.Enum.GameObjectType;
+import engine.exception.MsgSendException;
+import engine.gameManager.ChatManager;
+import engine.gameManager.GuildManager;
+import engine.gameManager.SessionManager;
+import engine.net.Dispatch;
+import engine.net.DispatchMessage;
+import engine.net.client.ClientConnection;
+import engine.net.client.msg.ClientNetMsg;
+import engine.net.client.msg.ErrorPopupMsg;
+import engine.net.client.msg.guild.InviteToGuildMsg;
+import engine.objects.GuildStatusController;
+import engine.objects.PlayerCharacter;
+
+public class InviteToGuildHandler extends AbstractClientMsgHandler {
+
+	public InviteToGuildHandler() {
+		super(InviteToGuildMsg.class);
+	}
+
+	@Override
+	protected boolean _handleNetMsg(ClientNetMsg baseMsg, ClientConnection origin) throws MsgSendException {
+		InviteToGuildMsg msg;
+		PlayerCharacter sourcePlayer;
+		PlayerCharacter targetPlayer;
+		Dispatch dispatch;
+
+		msg = (InviteToGuildMsg) baseMsg;
+
+		// First see if this is a refusal to another guild invite
+
+		if (msg.getResponse() == 4)
+			return true; // Player refused invite
+
+		// get sourcePlayer player
+
+		sourcePlayer = SessionManager.getPlayerCharacter(origin);
+
+		if (sourcePlayer == null)
+			return true;
+
+		if (msg.getTargetUUID() == 0) {
+			// get targetPlayer player by name
+			targetPlayer = SessionManager.getPlayerCharacterByLowerCaseName(msg.getTargetName());
+
+			if (targetPlayer == null) {
+				ChatManager.chatGuildError(sourcePlayer,
+						"No such player exists!");
+				return true;
+			}
+		} else
+			if (msg.getTargetType() == GameObjectType.PlayerCharacter.ordinal()) {
+
+				targetPlayer = SessionManager.getPlayerCharacterByID(msg.getTargetUUID());
+
+				if (targetPlayer == null) {
+					ChatManager.chatGuildError(sourcePlayer,
+							"No such player exists!");
+					return true;
+				}
+			} else {
+				ChatManager.chatGuildError(sourcePlayer,
+						"You cannot invite that character!");
+				return true;
+			}
+
+		// get sourcePlayer guild. Verify sourcePlayer player is in guild
+
+		if (sourcePlayer.getGuild().getObjectUUID() == 0 || sourcePlayer.getGuild().isErrant()) {
+			ChatManager.chatGuildError(sourcePlayer,
+					"You cannot invite someone for errant!");
+			return true;
+		}
+
+		Enum.GuildType guildType = Enum.GuildType.values()[sourcePlayer.getGuild().getCharter()];
+
+		if (guildType == null){
+			ErrorPopupMsg.sendErrorPopup(sourcePlayer, GuildManager.NO_CHARTER_FOUND);
+			return true;
+		}
+
+
+
+		// verify sourcePlayer player is full member so they can invite
+
+		if (GuildStatusController.isFullMember(sourcePlayer.getGuildStatus()) == false) {
+			ChatManager.chatGuildError(sourcePlayer,
+					"You do not have authority to invite!");
+			return true;
+		}
+
+		//block invite is targetPlayer is ignoring sourcePlayer
+
+		if (targetPlayer.isIgnoringPlayer(sourcePlayer))
+			return true;
+
+		if ((targetPlayer.getGuild().isErrant() == false)) {
+			ChatManager.chatGuildError(sourcePlayer,
+					targetPlayer.getFirstName() + " already belongs to a guild!");
+			return true;
+		}
+
+		// verify targetPlayer player is not on banish list
+
+		if (sourcePlayer.getGuild().getBanishList().contains(targetPlayer)) {
+			ErrorPopupMsg.sendErrorPopup(sourcePlayer, 135);// Character is considered BANISHED by guild leadership
+			return true;
+		}
+
+		//verify targetPlayer meets level requirements of guild
+
+		if ((targetPlayer.getLevel() < sourcePlayer.getGuild().getRepledgeMin()) || targetPlayer.getLevel() > sourcePlayer.getGuild().getRepledgeMax()) {
+			ErrorPopupMsg.sendErrorPopup(sourcePlayer, 135);// you do not meet the level required for this SWORN guild
+			return true;
+		}
+
+		targetPlayer.setLastGuildToInvite(sourcePlayer.getGuild().getObjectUUID());
+
+		// setup guild invite message to send to targetPlayer
+
+		msg.setSourceType(sourcePlayer.getObjectType().ordinal());
+		msg.setSourceUUID(sourcePlayer.getObjectUUID());
+		msg.setTargetType(targetPlayer.getObjectType().ordinal());
+
+		msg.setTargetUUID(targetPlayer.getObjectUUID());
+		msg.setGuildTag(sourcePlayer.getGuild().getGuildTag());
+		msg.setGuildName(sourcePlayer.getGuild().getName());
+		msg.setGuildType(sourcePlayer.getGuild().getObjectType().ordinal());
+		msg.setGuildUUID(sourcePlayer.getGuild().getObjectUUID());
+		msg.setTargetName("");
+
+		dispatch = Dispatch.borrow(targetPlayer, msg);
+		DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+
+		return true;
+	}
+
+}
diff --git a/src/engine/net/client/handlers/InviteToSubHandler.java b/src/engine/net/client/handlers/InviteToSubHandler.java
new file mode 100644
index 00000000..9c16ce90
--- /dev/null
+++ b/src/engine/net/client/handlers/InviteToSubHandler.java
@@ -0,0 +1,133 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.handlers;
+
+import engine.Enum;
+import engine.Enum.GameObjectType;
+import engine.exception.MsgSendException;
+import engine.gameManager.ChatManager;
+import engine.gameManager.DbManager;
+import engine.gameManager.SessionManager;
+import engine.net.Dispatch;
+import engine.net.DispatchMessage;
+import engine.net.client.ClientConnection;
+import engine.net.client.msg.ClientNetMsg;
+import engine.net.client.msg.ErrorPopupMsg;
+import engine.net.client.msg.guild.InviteToSubMsg;
+import engine.objects.Guild;
+import engine.objects.GuildStatusController;
+import engine.objects.PlayerCharacter;
+
+public class InviteToSubHandler extends AbstractClientMsgHandler {
+
+	public InviteToSubHandler() {
+		super(InviteToSubMsg.class);
+	}
+
+	@Override
+	protected boolean _handleNetMsg(ClientNetMsg baseMsg, ClientConnection origin) throws MsgSendException {
+
+		PlayerCharacter source;
+		PlayerCharacter target;
+		Guild sourceGuild;
+		Guild targetGuild;
+		InviteToSubMsg msg = (InviteToSubMsg) baseMsg;
+		Dispatch dispatch;
+
+		source = SessionManager.getPlayerCharacter(origin);
+
+		if (source == null)
+			return true;
+
+		target = (PlayerCharacter) DbManager.getObject(GameObjectType.PlayerCharacter, msg.getTargetUUID());
+
+		if (target == null) {
+			ErrorPopupMsg.sendErrorMsg(source, "A Serious error has occured. Please post details for to ensure transaction integrity");
+			return true;
+		}
+
+		//Ignore invites to sub if ignoring player
+
+		if (target.isIgnoringPlayer(source))
+			return true;
+
+		sourceGuild = source.getGuild();
+		targetGuild = target.getGuild();
+
+		//source must be in guild
+
+		if (sourceGuild == null) {
+			sendChat(source, "You must be in a guild to invite to sub.");
+			return true;
+		}
+
+		if (sourceGuild.isErrant()){
+			sendChat(source, "You must be in a guild to invite to sub.");
+			return true;
+		}
+
+		//source must be GL or IC
+
+		if (GuildStatusController.isInnerCouncil(source.getGuildStatus()) == false) {
+			sendChat(source, "Only guild leadership can invite to sub.");
+			return true;
+		}
+
+		if (sourceGuild.getNation().isErrant())
+			return true;
+
+		//target must be in a guild
+
+		if (targetGuild == null)
+			return true;
+		
+		if (sourceGuild.equals(targetGuild))
+			return true;
+
+		//target must be GL or IC
+
+		if (GuildStatusController.isInnerCouncil(target.getGuildStatus()) == false && GuildStatusController.isGuildLeader(target.getGuildStatus()) == false) {
+			sendChat(source, "Target player is not guild leadership.");
+			return true;
+		}
+
+		//Can't already be same nation or errant
+		//source guild is limited to 7 subs
+		//TODO this should be based on TOL rank
+
+
+		if (!sourceGuild.canSubAGuild(targetGuild)) {
+			sendChat(source, "This Guild can't be subbed.");
+			return true;
+		}
+
+		//all tests passed, let's send invite.
+
+		if (target.getClientConnection() != null) {
+			msg.setGuildTag(sourceGuild.getGuildTag());
+			msg.setGuildName(sourceGuild.getName());
+			msg.setGuildUUID(sourceGuild.getObjectUUID());
+			msg.setUnknown02(1);
+
+			dispatch = Dispatch.borrow(target, msg);
+			DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+
+		} else {
+			sendChat(source, "Failed to send sub invite to target.");
+			return true;
+		}
+
+		return true;
+	}
+
+	private static void sendChat(PlayerCharacter source, String msg) {
+		ChatManager.chatGuildError(source, msg);
+	}
+}
diff --git a/src/engine/net/client/handlers/ItemProductionMsgHandler.java b/src/engine/net/client/handlers/ItemProductionMsgHandler.java
new file mode 100644
index 00000000..f0848bf6
--- /dev/null
+++ b/src/engine/net/client/handlers/ItemProductionMsgHandler.java
@@ -0,0 +1,521 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.handlers;
+
+
+import engine.Enum;
+import engine.Enum.GameObjectType;
+import engine.Enum.ItemType;
+import engine.exception.MsgSendException;
+import engine.gameManager.ChatManager;
+import engine.gameManager.DbManager;
+import engine.net.Dispatch;
+import engine.net.DispatchMessage;
+import engine.net.client.ClientConnection;
+import engine.net.client.msg.ClientNetMsg;
+import engine.net.client.msg.ErrorPopupMsg;
+import engine.net.client.msg.ItemProductionMsg;
+import engine.net.client.msg.ManageNPCMsg;
+import engine.objects.*;
+import org.pmw.tinylog.Logger;
+
+import java.util.HashMap;
+
+/*
+ * @Summary: Processes application protocol message which modifies
+ * hireling inventory through rolling, junking or depositing.
+ */
+public class ItemProductionMsgHandler extends AbstractClientMsgHandler {
+
+	private static final int ACTION_PRODUCE = 1;
+	private static final int ACTION_JUNK = 2;
+	private static final int ACTION_RECYCLE = 3;
+	private static final int ACTION_COMPLETE = 4;
+	private static final int ACTION_DEPOSIT = 6;
+	private static final int ACTION_SETPRICE = 5;
+	private static final int ACTION_TAKE = 7;
+	private static final int ACTION_CONFIRM_SETPRICE = 9;  // Unsure. Sent by client
+	private static final int ACTION_CONFIRM_DEPOSIT = 10;  // Unsure. Sent by client
+	private static final int ACTION_CONFIRM_TAKE = 11;     // Unsure. Sent by client
+
+	public ItemProductionMsgHandler() {
+		super(ItemProductionMsg.class);
+	}
+
+	@Override
+	protected boolean _handleNetMsg(ClientNetMsg baseMsg, ClientConnection origin) throws MsgSendException {
+
+		// Member variable declaration
+
+		PlayerCharacter player;
+		NPC vendorNPC;
+		ItemProductionMsg msg;
+		Dispatch dispatch;
+
+		// Member variable assignment
+
+		msg = (ItemProductionMsg) baseMsg;
+		player = origin.getPlayerCharacter();
+
+		if (player == null)
+			return true;
+
+		// Grab reference to vendor we are interacting with
+
+		vendorNPC = (NPC) DbManager.getObject(engine.Enum.GameObjectType.NPC, msg.getNpcUUID());
+
+		// Oops?
+
+		if (vendorNPC == null)
+			return true;
+
+		// Process Request
+
+		switch (msg.getActionType()) {
+
+		case ACTION_PRODUCE:
+			boolean isRandom = false;
+			if (msg.getUnknown03() != 0 && msg.getpToken() == 0 && msg.getsToken() == 0)
+				isRandom = true;
+			//Create Multiple Item Function.. Fill all empty slots
+			if (msg.isMultiple()){
+				int emptySlots = vendorNPC.getRank() - vendorNPC.getRolling().size();
+				if (emptySlots > 0){
+					for (int i = 0;i<emptySlots;i++){
+						vendorNPC.produceItem(player.getObjectUUID(),msg.getTotalProduction(),isRandom,msg.getpToken(),msg.getsToken(),msg.getName(),msg.getItemUUID());
+					}
+				}
+			}else
+				vendorNPC.produceItem(player.getObjectUUID(),msg.getTotalProduction(),isRandom,msg.getpToken(),msg.getsToken(),msg.getName(),msg.getItemUUID());
+			break;
+		case ACTION_JUNK:
+			junkItem(msg.getItemUUID(), vendorNPC, origin);
+			break;
+		case ACTION_RECYCLE:
+			recycleItem(msg.getItemIDtoTypeMap(), vendorNPC, origin);
+			msg.setActionType(7);
+			dispatch = Dispatch.borrow(player, msg);
+			DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+			break;
+		case ACTION_COMPLETE:
+
+			vendorNPC.completeItem(msg.getItemUUID());
+
+			//			ManageNPCMsg outMsg = new ManageNPCMsg(vendorNPC);
+			//			outMsg.setMessageType(1);
+			//
+			//			dispatch = Dispatch.borrow(player, outMsg);
+			//			DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+
+
+
+
+
+
+			break;
+		case ACTION_DEPOSIT:
+			depositItem(msg.getItemUUID(), vendorNPC, origin);
+			break;
+		case ACTION_SETPRICE:
+			setItemPrice(msg.getItemType(),msg.getItemUUID(), msg.getItemPrice(), vendorNPC, origin);
+			break;
+		case ACTION_TAKE:
+			takeItem(msg.getItemIDtoTypeMap(), vendorNPC, origin);
+			dispatch = Dispatch.borrow(player, msg);
+			DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+			break;
+
+		}
+		return true;
+	}
+
+	// Method sets the price on an item in the vendor inventory
+
+	private static void setItemPrice(int itemType, int itemUUID, int itemPrice, NPC vendor, ClientConnection origin) {
+
+		Item targetItem;
+		ItemProductionMsg outMsg;
+		Dispatch dispatch;
+
+		PlayerCharacter player = origin.getPlayerCharacter();
+
+		if (player == null)
+			return;
+
+		if (itemType == GameObjectType.Item.ordinal())
+			targetItem = Item.getFromCache(itemUUID);
+		else if (itemType == GameObjectType.MobLoot.ordinal())
+			targetItem = MobLoot.getFromCache(itemUUID);
+		else
+			targetItem = null;
+
+
+		if (targetItem == null)
+			return;
+
+		if (targetItem.getObjectType() == GameObjectType.Item){
+			if (!DbManager.ItemQueries.UPDATE_VALUE(targetItem, itemPrice)) {
+				ChatManager.chatInfoError(origin.getPlayerCharacter(), "Failed to set price! Contact CCR For help.");
+				return;
+			}
+			targetItem.setValue(itemPrice);
+			outMsg = new ItemProductionMsg(vendor.getBuilding(), vendor, targetItem, ACTION_DEPOSIT, true);
+			dispatch = Dispatch.borrow(player, outMsg);
+			DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+
+			outMsg = new ItemProductionMsg(vendor.getBuilding(), vendor, targetItem, ACTION_SETPRICE, true);
+			dispatch = Dispatch.borrow(player, outMsg);
+			DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+		}else if (targetItem.getObjectType() == GameObjectType.MobLoot){
+			MobLoot mobLoot = (MobLoot)targetItem;
+			if (!DbManager.NPCQueries.UPDATE_ITEM_PRICE(mobLoot.getObjectUUID(), vendor.getObjectUUID(), itemPrice)) {
+				ChatManager.chatInfoError(origin.getPlayerCharacter(), "Failed to set price! Contact CCR For help.");
+				return;
+			}
+			targetItem.setValue(itemPrice);
+			outMsg = new ItemProductionMsg(vendor.getBuilding(), vendor, targetItem, ACTION_DEPOSIT, true);
+			dispatch = Dispatch.borrow(player, outMsg);
+			DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+
+			outMsg = new ItemProductionMsg(vendor.getBuilding(), vendor, targetItem, ACTION_SETPRICE, true);
+			dispatch = Dispatch.borrow(player, outMsg);
+			DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+		}
+
+		// Set item's price
+
+
+	}
+
+	// Method adds an item from the players inventory to the vendor.
+
+	private static void depositItem(int itemUUID, NPC vendor, ClientConnection origin) {
+
+		Item targetItem;
+		ItemProductionMsg outMsg;
+		CharacterItemManager itemMan;
+		Dispatch dispatch;
+
+		PlayerCharacter player = origin.getPlayerCharacter();
+
+		if (player == null)
+			return;
+
+		if (origin.sellLock.tryLock()) {
+			try {
+				targetItem = Item.getFromCache(itemUUID);
+
+				if (targetItem == null)
+					return;
+
+				if (targetItem.getItemBase().getType() == ItemType.GOLD)
+					return;
+
+				if (!vendor.getCharItemManager().hasRoomInventory(targetItem.getItemBase().getWeight())){
+
+					ErrorPopupMsg.sendErrorPopup(player, 21);
+					return;
+				}
+
+				itemMan = origin.getPlayerCharacter().getCharItemManager();
+
+				if (itemMan == null)
+					return;
+
+				if (vendor.getCharItemManager().getInventoryWeight() > 500) {
+					ErrorPopupMsg.sendErrorPopup(player, 21);
+					return;
+				}
+
+				if (!targetItem.validForInventory(origin, player, itemMan)){
+					ErrorPopupMsg.sendErrorPopup(player, 19);
+					return;
+				}
+
+				// Transfer item from player to vendor's inventory
+
+				if (!itemMan.sellToNPC(targetItem, vendor)){
+					ErrorPopupMsg.sendErrorPopup(player, 109);
+					return;
+				}
+
+
+				outMsg = new ItemProductionMsg(vendor.getBuilding(), vendor, targetItem, ACTION_DEPOSIT, true);
+				dispatch = Dispatch.borrow(player, outMsg);
+				DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+
+				outMsg = new ItemProductionMsg(vendor.getBuilding(), vendor, targetItem, ACTION_CONFIRM_DEPOSIT, true);
+				dispatch = Dispatch.borrow(player, outMsg);
+				DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+
+				origin.getPlayerCharacter().getCharItemManager().updateInventory();
+			}catch (Exception e){
+				Logger.error(e);
+			}finally {
+				origin.sellLock.unlock();
+			}
+				
+			}
+		
+
+
+	}
+
+	// Method completes an item that has been previously rolled
+	// adding it to the NPC's inventory
+
+	private static void completeItem(int itemUUID, NPC vendor, ClientConnection origin, ItemProductionMsg msg) {
+
+		Item targetItem;
+		ManageNPCMsg outMsg;
+		Dispatch dispatch;
+
+		PlayerCharacter player = origin.getPlayerCharacter();
+
+		if (player == null)
+			return;
+
+		if (origin.buyLock.tryLock()) {
+			try {
+				targetItem = Item.getFromCache(itemUUID);
+
+				if (targetItem == null)
+					return;
+
+
+				if (!vendor.getCharItemManager().forgeContains(targetItem, vendor))
+					return;
+
+				boolean worked = DbManager.ItemQueries.UPDATE_FORGE_TO_INVENTORY(targetItem);
+				if (!worked) {
+					Guild guild = vendor.getGuild();
+					if (guild == null)
+						return;
+					//ChatManager.chatGuildInfo(guild, "Failed to complete Item " + targetItem.getName());
+					return;
+				}
+
+				targetItem.containerType = Enum.ItemContainerType.INVENTORY;
+				targetItem.setOwner(vendor);
+				vendor.getCharItemManager().addItemToInventory(targetItem);
+
+				vendor.removeItemFromForge(targetItem);
+
+				outMsg = new ManageNPCMsg(vendor);
+				outMsg.setMessageType(ACTION_PRODUCE);
+				dispatch = Dispatch.borrow(player, outMsg);
+				DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+			} finally {
+				origin.buyLock.unlock();
+			}
+		}
+	}
+
+	// Method handles recycling of an item
+
+	private static void recycleItem(HashMap<Integer, Integer> itemList, NPC vendor, ClientConnection origin) {
+
+		Item targetItem;
+		ItemProductionMsg outMsg;
+		int totalValue = 0;
+		int currentStrongbox;
+		Dispatch dispatch;
+
+		if (vendor.getBuilding() == null)
+			return;
+
+		PlayerCharacter player = origin.getPlayerCharacter();
+
+		if (player == null)
+			return;
+
+		if (itemList == null)
+			return;
+		
+		if (origin.sellLock.tryLock()) {
+			try {
+
+
+
+				for (int itemUUID : itemList.keySet()) {
+					int itemValue = 0;
+
+					int type = itemList.get(itemUUID);
+
+					if (type == GameObjectType.Item.ordinal())
+						targetItem = Item.getFromCache(itemUUID);
+					else
+						targetItem = MobLoot.getFromCache(itemUUID);
+
+					if (targetItem == null)
+						continue;
+
+					if (targetItem.getItemBase().getType() == ItemType.GOLD)
+						return;
+
+					if (!vendor.getCharItemManager().doesCharOwnThisItem(targetItem.getObjectUUID()))
+						continue;
+					if (vendor.getCharItemManager().inventoryContains(targetItem) == false)
+						continue;
+
+					itemValue = targetItem.getBaseValue();
+
+					if (vendor.getBuilding().getStrongboxValue() + itemValue > vendor.getBuilding().getMaxGold()) {
+						ErrorPopupMsg.sendErrorPopup(player, 201);
+						break;
+					}
+
+					switch (targetItem.getItemBase().getType()) {
+					case CONTRACT:
+					case GUILDCHARTER:
+					case DEED:
+					case REALMCHARTER:
+					case SCROLL:
+					case TEARS:
+						itemValue = 0;
+						continue;
+					}
+					totalValue += itemValue;
+					long start = System.currentTimeMillis();
+					vendor.getCharItemManager().recycle(targetItem);
+					long end = System.currentTimeMillis();
+					long timetook = end - start;
+
+					//					ChatManager.chatSystemInfo(player, "Took " + timetook + " ms to finish");
+
+					outMsg = new ItemProductionMsg(vendor.getBuilding(), vendor, targetItem, ACTION_TAKE, true);
+
+					dispatch = Dispatch.borrow(origin.getPlayerCharacter(), outMsg);
+					DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+				}
+
+				// Refund a portion of the gold
+
+				if (!vendor.getBuilding().transferGold(totalValue,false))
+				return;
+				
+				
+
+		}catch (Exception e){
+			Logger.error(e);
+		}finally {
+		
+			origin.sellLock.unlock();
+		}
+
+		}
+
+		// Refresh vendor's inventory to client
+
+	}
+
+	// Method junks an item that has been rolled but not completed
+
+	private static void junkItem(int itemUUID, NPC vendor, ClientConnection origin) {
+
+		MobLoot targetItem;
+		ManageNPCMsg outMsg;
+		Dispatch dispatch;
+
+		if (origin.sellLock.tryLock()) {
+			try {
+				targetItem = MobLoot.getFromCache(itemUUID);
+
+				PlayerCharacter player = origin.getPlayerCharacter();
+
+				if (player == null)
+					return;
+
+				// Can't junk nothing!
+
+				if (targetItem == null)
+					return;
+
+
+
+				if (!vendor.getCharItemManager().forgeContains(targetItem, vendor))
+					return;
+
+				// Cannot junk items without a forge!
+
+				if (vendor.getBuilding() == null)
+					return;
+
+				// Delete the item and cancel any pending rolling timer jobs
+
+				targetItem.recycle(vendor);
+				vendor.removeItemFromForge(targetItem);
+
+				// Refresh vendor's inventory to client
+
+				outMsg = new ManageNPCMsg(vendor);
+				outMsg.setMessageType(1);
+				dispatch = Dispatch.borrow(player, outMsg);
+				DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+				;
+			} finally {
+				origin.sellLock.unlock();
+			}
+		}
+
+	}
+
+	// Method removes item from an NPC's inventory and transferes it to a player
+
+	private static void takeItem(HashMap<Integer, Integer> itemList, NPC vendor, ClientConnection origin) {
+
+		Item targetItem;
+
+
+		PlayerCharacter player = origin.getPlayerCharacter();
+
+		if (player == null)
+			return;
+
+
+
+		for (int itemUUID : itemList.keySet()) {
+
+			int type = itemList.get(itemUUID);
+			if (type == GameObjectType.Item.ordinal()){
+				targetItem = Item.getFromCache(itemUUID);
+
+			}
+			else{
+				targetItem = MobLoot.getFromCache(itemUUID);
+
+			}
+
+			if (targetItem == null)
+				return;
+
+
+			if (targetItem.getItemBase().getType() == ItemType.GOLD)
+				return;
+			if (vendor.getCharItemManager().inventoryContains(targetItem) == false)
+				return;
+
+			if (player.getCharItemManager().hasRoomInventory(targetItem.getItemBase().getWeight()) == false)
+				return;
+
+			player.getCharItemManager().buyFromNPC(targetItem, vendor);
+
+		}
+
+		player.getCharItemManager().updateInventory();
+
+		// Update NPC inventory to client
+
+
+	}
+
+	// Method handles rolling item requests from the client
+
+}
diff --git a/src/engine/net/client/handlers/KeepAliveServerClientHandler.java b/src/engine/net/client/handlers/KeepAliveServerClientHandler.java
new file mode 100644
index 00000000..827590df
--- /dev/null
+++ b/src/engine/net/client/handlers/KeepAliveServerClientHandler.java
@@ -0,0 +1,48 @@
+package engine.net.client.handlers;
+
+import engine.Enum.DispatchChannel;
+import engine.exception.MsgSendException;
+import engine.net.Dispatch;
+import engine.net.DispatchMessage;
+import engine.net.client.ClientConnection;
+import engine.net.client.msg.ClientNetMsg;
+import engine.net.client.msg.KeepAliveServerClientMsg;
+import engine.objects.PlayerCharacter;
+
+/*
+ * @Author:
+ * @Summary: Processes application protocol message which keeps
+ * client's tcp connection open.
+ */
+
+public class KeepAliveServerClientHandler extends AbstractClientMsgHandler {
+
+	public KeepAliveServerClientHandler() {
+		super(KeepAliveServerClientMsg.class);
+	}
+
+	@Override
+	protected boolean _handleNetMsg(ClientNetMsg baseMsg, ClientConnection origin) throws MsgSendException {
+		
+		PlayerCharacter pc = origin.getPlayerCharacter();
+		
+	
+	
+            // Member variable declaration
+            
+            KeepAliveServerClientMsg msg;
+            
+            // Member variable assignment
+            
+            msg = (KeepAliveServerClientMsg) baseMsg;
+            
+        
+            // Send ping to client
+            
+            Dispatch dispatch = Dispatch.borrow(pc, msg);
+            DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.PRIMARY);
+            
+            return true;
+	}
+
+}
\ No newline at end of file
diff --git a/src/engine/net/client/handlers/KeyCloneAudit.java b/src/engine/net/client/handlers/KeyCloneAudit.java
new file mode 100644
index 00000000..94e7a162
--- /dev/null
+++ b/src/engine/net/client/handlers/KeyCloneAudit.java
@@ -0,0 +1,32 @@
+package engine.net.client.handlers;
+
+import engine.gameManager.DbManager;
+import engine.objects.Group;
+import engine.objects.PlayerCharacter;
+import org.pmw.tinylog.Logger;
+
+public enum KeyCloneAudit {
+    KEYCLONEAUDIT;
+
+    void audit(PlayerCharacter player, Group group) {
+
+        int machineCount = 0;
+        String machineID;
+
+        machineID = player.getClientConnection().machineID;
+
+        for (PlayerCharacter member : group.getMembers())
+            if (machineID.equals(member.getClientConnection().machineID))
+                machineCount = machineCount + 1;
+
+            // (int) ConfigManager.WORLDSERVER.config.get("keyclone")
+            if (machineCount > 4) {
+                Logger.error("Keyclone detected from: " + player.getAccount().getUname() +
+                        " with machine count of: " + machineCount);
+                DbManager.AccountQueries.SET_TRASH(machineID);
+            }
+        // Refactor to separate file to log keyclones
+       // DbManager.AccountQueries.EMPTY_TRASH(machineID);
+
+    }
+}
diff --git a/src/engine/net/client/handlers/LeaveGroupHandler.java b/src/engine/net/client/handlers/LeaveGroupHandler.java
new file mode 100644
index 00000000..e181dca0
--- /dev/null
+++ b/src/engine/net/client/handlers/LeaveGroupHandler.java
@@ -0,0 +1,31 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.handlers;
+
+import engine.exception.MsgSendException;
+import engine.gameManager.GroupManager;
+import engine.net.client.ClientConnection;
+import engine.net.client.msg.ClientNetMsg;
+import engine.net.client.msg.group.LeaveGroupMsg;
+
+public class LeaveGroupHandler extends AbstractClientMsgHandler {
+
+    public LeaveGroupHandler() {
+        super(LeaveGroupMsg.class);
+    }
+
+    @Override
+    protected boolean _handleNetMsg(ClientNetMsg baseMsg,
+            ClientConnection origin) throws MsgSendException {
+        GroupManager.LeaveGroup(origin);
+        return true;
+    }
+
+}
diff --git a/src/engine/net/client/handlers/LeaveGuildHandler.java b/src/engine/net/client/handlers/LeaveGuildHandler.java
new file mode 100644
index 00000000..3764f7c8
--- /dev/null
+++ b/src/engine/net/client/handlers/LeaveGuildHandler.java
@@ -0,0 +1,72 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.handlers;
+
+import engine.Enum.GuildHistoryType;
+import engine.exception.MsgSendException;
+import engine.gameManager.ChatManager;
+import engine.gameManager.SessionManager;
+import engine.net.Dispatch;
+import engine.net.DispatchMessage;
+import engine.net.client.ClientConnection;
+import engine.net.client.msg.ClientNetMsg;
+import engine.net.client.msg.guild.LeaveGuildMsg;
+import engine.objects.Guild;
+import engine.objects.GuildStatusController;
+import engine.objects.PlayerCharacter;
+
+public class LeaveGuildHandler extends AbstractClientMsgHandler {
+
+	public LeaveGuildHandler() {
+		super(LeaveGuildMsg.class);
+	}
+
+	@Override
+	protected boolean _handleNetMsg(ClientNetMsg baseMsg, ClientConnection origin) throws MsgSendException {
+		LeaveGuildMsg msg = (LeaveGuildMsg) baseMsg;
+		Dispatch dispatch;
+
+		// get PlayerCharacter of person leaving invite
+
+		PlayerCharacter sourcePlayer = SessionManager.getPlayerCharacter(origin);
+
+		if (sourcePlayer == null)
+			return true;
+
+		// Guild leader can't leave guild. must pass GL or disband
+
+		if (GuildStatusController.isGuildLeader(sourcePlayer.getGuildStatus())) {
+			msg.setMessage("You must switch leadership of your guild before leaving!");
+			dispatch = Dispatch.borrow(sourcePlayer, msg);
+			DispatchMessage.dispatchMsgDispatch(dispatch, engine.Enum.DispatchChannel.SECONDARY);
+			return true;
+		}
+
+		// get old Guild
+		Guild oldGuild = sourcePlayer.getGuild();
+
+		if (oldGuild == null || oldGuild.isErrant()) {
+			return true;
+		}
+
+		// Send left guild message to rest of guild
+		ChatManager.chatGuildInfo(oldGuild, sourcePlayer.getFirstName() + " has left the guild.");
+
+		oldGuild.removePlayer(sourcePlayer, GuildHistoryType.LEAVE);
+
+		// Send message back to client
+		msg.setMessage("You have left the guild.");
+		dispatch = Dispatch.borrow(sourcePlayer, msg);
+		DispatchMessage.dispatchMsgDispatch(dispatch, engine.Enum.DispatchChannel.SECONDARY);
+
+		return true;
+	}
+
+}
diff --git a/src/engine/net/client/handlers/LockUnlockDoorMsgHandler.java b/src/engine/net/client/handlers/LockUnlockDoorMsgHandler.java
new file mode 100644
index 00000000..367c6911
--- /dev/null
+++ b/src/engine/net/client/handlers/LockUnlockDoorMsgHandler.java
@@ -0,0 +1,88 @@
+package engine.net.client.handlers;
+
+import engine.exception.MsgSendException;
+import engine.gameManager.BuildingManager;
+import engine.gameManager.SessionManager;
+import engine.net.Dispatch;
+import engine.net.DispatchMessage;
+import engine.net.client.ClientConnection;
+import engine.net.client.msg.ClientNetMsg;
+import engine.net.client.msg.LockUnlockDoorMsg;
+import engine.objects.Blueprint;
+import engine.objects.Building;
+import engine.objects.PlayerCharacter;
+import engine.server.MBServerStatics;
+import org.pmw.tinylog.Logger;
+
+/*
+ * @Author:
+ * @Summary: Processes application protocol message which handle
+ * lock and unlock door requests to and from the client.
+ * 
+ */
+
+public class LockUnlockDoorMsgHandler extends AbstractClientMsgHandler {
+
+    public LockUnlockDoorMsgHandler() {
+        super(LockUnlockDoorMsg.class);
+    }
+
+    @Override
+    protected boolean _handleNetMsg(ClientNetMsg baseMsg, ClientConnection origin) throws MsgSendException {
+
+        // Member variable declarations
+        
+        PlayerCharacter player;
+        Building targetBuilding;
+        int doorNum;
+        LockUnlockDoorMsg msg;
+
+        // Member variable assignment
+        
+        msg = (LockUnlockDoorMsg) baseMsg;
+        player = SessionManager.getPlayerCharacter(origin);
+        targetBuilding = BuildingManager.getBuilding((int) msg.getTargetID());
+
+        if (player == null || targetBuilding == null) {
+            Logger.warn("Player or Building returned NULL in LockUnlock msg handling.");
+            return true;
+        }
+
+        if (player.getLoc().distanceSquared2D(targetBuilding.getLoc()) > MBServerStatics.OPENCLOSEDOORDISTANCE * MBServerStatics.OPENCLOSEDOORDISTANCE) {
+            return true;
+        }
+
+        if (!BuildingManager.playerCanManage(player, targetBuilding)) {
+            return true;
+        }
+
+        doorNum = Blueprint.getDoorNumberbyMesh(msg.getDoorID());
+
+        // Debugging code
+        
+        // Logger.debug("DoorLockUnlock", "Door mesh: " + msg.getDoorID() + " Door number: " + doorNum);
+        
+        boolean stateChanged;
+
+        if (targetBuilding.isDoorLocked(doorNum)) {
+            stateChanged = targetBuilding.setDoorState(doorNum, engine.Enum.DoorState.UNLOCKED);
+        } else {
+            stateChanged = targetBuilding.setDoorState(doorNum, engine.Enum.DoorState.LOCKED);
+        }
+
+        if (stateChanged == false) {
+            Logger.error("WorldServerMsgHandler.LockUnlockDoor", "Failed to update db for building: " + targetBuilding.getObjectUUID() + ", door: " + msg.getDoorID());
+        }
+
+        if (targetBuilding.isDoorLocked(doorNum)) {
+            msg.setUnk1(1); // Which is this, locked or unlocaked?
+        } else {
+            msg.setUnk1(0);
+        }
+
+        Dispatch dispatch = Dispatch.borrow(player, msg);
+        DispatchMessage.dispatchMsgDispatch(dispatch, engine.Enum.DispatchChannel.SECONDARY);
+
+        return true;
+    }
+}
diff --git a/src/engine/net/client/handlers/LoginToGameServerMsgHandler.java b/src/engine/net/client/handlers/LoginToGameServerMsgHandler.java
new file mode 100644
index 00000000..175dccc4
--- /dev/null
+++ b/src/engine/net/client/handlers/LoginToGameServerMsgHandler.java
@@ -0,0 +1,119 @@
+package engine.net.client.handlers;
+
+import engine.Enum;
+import engine.InterestManagement.WorldGrid;
+import engine.exception.MsgSendException;
+import engine.gameManager.DbManager;
+import engine.gameManager.SessionManager;
+import engine.net.client.ClientConnection;
+import engine.net.client.msg.ClientNetMsg;
+import engine.net.client.msg.LoginToGameServerMsg;
+import engine.net.client.msg.login.LoginErrorMsg;
+import engine.objects.Account;
+import engine.objects.PlayerCharacter;
+import engine.server.MBServerStatics;
+import engine.session.CSSession;
+import engine.session.Session;
+import org.pmw.tinylog.Logger;
+
+/*
+ * @Author:
+ * @Summary: Processes application protocol message which keeps
+ * logs the character nto the game using the CSession key generated
+ * by the Login server.
+ */
+
+public class LoginToGameServerMsgHandler extends AbstractClientMsgHandler {
+
+	public LoginToGameServerMsgHandler() {
+		super(LoginToGameServerMsg.class);
+	}
+
+	@Override
+	protected boolean _handleNetMsg(ClientNetMsg baseMsg, ClientConnection origin) throws MsgSendException {
+
+		LoginToGameServerMsg msg = (LoginToGameServerMsg) baseMsg;
+
+		CSSession sessionInfo = CSSession.getCrossServerSession(msg.getSecKey());
+
+		if (sessionInfo == null) {
+			Logger.error("Failed to validate session information from " + origin.getLocalAddressAndPortAsString());
+			origin.kickToLogin(MBServerStatics.LOGINERROR_UNABLE_TO_LOGIN, "Unable to validate session data");
+			// TODO Evaluate if we need to delete CSSessions here. We couldn't
+			// find it before, why would this attempt be different?
+
+			return true;
+		}
+
+		Account acc = sessionInfo.getAccount();
+
+		if (acc == null) {
+			String err = "Session returned NULL Account.  Conn:" + origin.getLocalAddressAndPortAsString();
+			Logger.error(err);
+			origin.kickToLogin(MBServerStatics.LOGINERROR_UNABLE_TO_LOGIN, err);
+			return true;
+		}
+
+		PlayerCharacter pc = sessionInfo.getPlayerCharacter();
+
+		if (pc == null) {
+			String err = "Session returned NULL PlayerCharacter.  Conn:" + origin.getLocalAddressAndPortAsString();
+
+			Logger.error(err);
+			origin.kickToLogin(MBServerStatics.LOGINERROR_UNABLE_TO_LOGIN, err);
+			return true;
+		}
+
+		// If account is suspended, kick
+
+		if (acc.status.equals(Enum.AccountStatus.BANNED)) {
+			origin.kickToLogin(MBServerStatics.LOGINERROR_NO_MORE_PLAYTIME_ON_ACCOUNT, "Account banned.");
+			return true;
+		}
+
+		ClientConnection old = SessionManager.getClientConnection(acc);
+
+		if (old != null)
+			if (old != origin) {
+				Logger.info("Disconnecting other client connection Using Same Account " + old.getRemoteAddressAndPortAsString());
+				old.disconnect();
+			}
+
+		// Set machine ID here from CSS info
+		origin.machineID = sessionInfo.getMachineID();
+
+		// Send response
+		msg.setSecKey("");
+
+		if (!origin.sendMsg(msg)) {
+			Logger.error("Failed to send message");
+			origin.kickToLogin(MBServerStatics.LOGINERROR_UNABLE_TO_LOGIN, "Unable to send ValidateGameServer to client.");
+			return true;
+		}
+
+		//# Why was this all changed?
+		// CLEAN UP OTHER INSTANCES OF THIS CHARACTER
+
+		Session toKill = SessionManager.getSession(sessionInfo.getPlayerCharacter());
+
+		if (toKill != null) {
+			if (toKill.getConn() != null) {
+				LoginErrorMsg lom = new LoginErrorMsg(MBServerStatics.LOGINERROR_UNABLE_TO_LOGIN, "You may not login the same character twice!");
+				ClientConnection conn = toKill.getConn();
+				if (conn != null && !conn.sendMsg(lom))
+					Logger.error("Failed to send message"); // TODO Do we just accept this failure to send Msg?
+			}
+			SessionManager.remSession(toKill);
+			WorldGrid.RemoveWorldObject(sessionInfo.getPlayerCharacter());
+		}
+		Session s = SessionManager.getNewSession(sessionInfo.getAccount(), origin);
+		SessionManager.setPlayerCharacter(s, sessionInfo.getPlayerCharacter());
+
+		Logger.info("Login from Account: " + sessionInfo.getAccount().getUname() + " Character: " +
+				     sessionInfo.getPlayerCharacter().getName() + " machineID: " + sessionInfo.getMachineID());
+
+		DbManager.AccountQueries.SET_ACCOUNT_LOGIN(sessionInfo.getAccount(), sessionInfo.getPlayerCharacter().getName(), origin.getClientIpAddress(), sessionInfo.getMachineID());
+		return true;
+	}
+
+}
\ No newline at end of file
diff --git a/src/engine/net/client/handlers/MOTDCommitHandler.java b/src/engine/net/client/handlers/MOTDCommitHandler.java
new file mode 100644
index 00000000..b5b0c084
--- /dev/null
+++ b/src/engine/net/client/handlers/MOTDCommitHandler.java
@@ -0,0 +1,82 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.handlers;
+
+import engine.exception.MsgSendException;
+import engine.gameManager.ChatManager;
+import engine.gameManager.SessionManager;
+import engine.net.Dispatch;
+import engine.net.DispatchMessage;
+import engine.net.client.ClientConnection;
+import engine.net.client.msg.ClientNetMsg;
+import engine.net.client.msg.guild.MOTDCommitMsg;
+import engine.objects.Guild;
+import engine.objects.GuildStatusController;
+import engine.objects.PlayerCharacter;
+
+public class MOTDCommitHandler extends AbstractClientMsgHandler {
+
+	public MOTDCommitHandler() {
+		super(MOTDCommitMsg.class);
+	}
+
+	@Override
+	protected boolean _handleNetMsg(ClientNetMsg baseMsg, ClientConnection origin) throws MsgSendException {
+		MOTDCommitMsg msg = (MOTDCommitMsg) baseMsg;
+		Dispatch dispatch;
+
+		// get source player
+		PlayerCharacter sourcePlayer = SessionManager.getPlayerCharacter(
+				origin);
+
+		if (sourcePlayer == null)
+			return true;
+
+		int type = msg.getType();
+
+		if (type == 0 || type == 1 || type == 3) {
+
+			if (GuildStatusController.isInnerCouncil(sourcePlayer.getGuildStatus()) == false)
+				return true;
+
+			Guild guild = sourcePlayer.getGuild();
+
+			if (guild == null)
+				return true;
+
+			if (type == 1) { // Guild MOTD
+				guild.setMOTD(msg.getMessage());
+				ChatManager.chatGuildMOTD(sourcePlayer, msg.getMessage(),
+						true);
+			} else if (type == 3) { // IC MOTD
+				guild.setICMOTD(msg.getMessage());
+				ChatManager
+						.chatICMOTD(sourcePlayer, msg.getMessage(), true);
+			} else if (type == 0) { // Nation MOTD
+				Guild nation = guild.getNation();
+				if (nation == null)
+					return true;
+				if (nation.isNation()) { // only
+																		// nation's
+					// primary guild can
+					// set nation motd
+					nation.setMOTD(msg.getMessage());
+					ChatManager.chatNationMOTD(sourcePlayer,
+							msg.getMessage(), true);
+				}
+			}
+            dispatch = Dispatch.borrow(sourcePlayer, msg);
+            DispatchMessage.dispatchMsgDispatch(dispatch, engine.Enum.DispatchChannel.SECONDARY);
+		}
+		
+		return true;
+	}
+
+}
diff --git a/src/engine/net/client/handlers/MOTDEditHandler.java b/src/engine/net/client/handlers/MOTDEditHandler.java
new file mode 100644
index 00000000..f5d1436a
--- /dev/null
+++ b/src/engine/net/client/handlers/MOTDEditHandler.java
@@ -0,0 +1,83 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.handlers;
+
+import engine.Enum;
+import engine.exception.MsgSendException;
+import engine.gameManager.SessionManager;
+import engine.net.Dispatch;
+import engine.net.DispatchMessage;
+import engine.net.client.ClientConnection;
+import engine.net.client.msg.ClientNetMsg;
+import engine.net.client.msg.ErrorPopupMsg;
+import engine.net.client.msg.guild.LeaveGuildMsg;
+import engine.net.client.msg.guild.MOTDMsg;
+import engine.objects.Guild;
+import engine.objects.GuildStatusController;
+import engine.objects.PlayerCharacter;
+
+public class MOTDEditHandler extends AbstractClientMsgHandler {
+
+	public MOTDEditHandler() {
+		super(MOTDMsg.class);
+	}
+
+	@Override
+	protected boolean _handleNetMsg(ClientNetMsg baseMsg, ClientConnection origin) throws MsgSendException {
+		MOTDMsg msg = (MOTDMsg) baseMsg;
+        Dispatch dispatch;
+
+		// get source player
+		PlayerCharacter playerCharacter = SessionManager.getPlayerCharacter(
+				origin);
+
+		if (playerCharacter == null)
+			return true;
+
+		int type = msg.getType();
+
+		msg.setResponse((byte) 1);
+		if (type == 0 || type == 1 || type == 3) {
+			if (GuildStatusController.isInnerCouncil(playerCharacter.getGuildStatus()) == false) {
+				 ErrorPopupMsg.sendErrorMsg(playerCharacter, "You do not have such authority!");
+				return true;
+			}
+
+			Guild guild = playerCharacter.getGuild();
+
+			if (guild == null || guild.getObjectUUID() == 0) {
+
+                LeaveGuildMsg leaveGuildMsg = new LeaveGuildMsg();
+                leaveGuildMsg.setMessage("You do not belong to a guild!");
+                dispatch = Dispatch.borrow(playerCharacter, leaveGuildMsg);
+                DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+
+				return true;
+			}
+
+			if (type == 1) // Guild MOTD
+				msg.setMessage(guild.getMOTD());
+			else if (type == 3) // IC MOTD
+				msg.setMessage(guild.getICMOTD());
+			else if (type == 0) { // Nation MOTD
+				Guild nation = guild.getNation();
+				if (nation == null || !nation.isNation()) {
+					ErrorPopupMsg.sendErrorMsg(playerCharacter, "You do not have such authority!");
+					return true;
+				}
+				msg.setMessage(nation.getMOTD());
+			}
+            dispatch = Dispatch.borrow(playerCharacter, msg);
+            DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+		}
+		
+		return true;
+	}
+}
diff --git a/src/engine/net/client/handlers/ManageCityAssetMsgHandler.java b/src/engine/net/client/handlers/ManageCityAssetMsgHandler.java
new file mode 100644
index 00000000..6928b207
--- /dev/null
+++ b/src/engine/net/client/handlers/ManageCityAssetMsgHandler.java
@@ -0,0 +1,364 @@
+package engine.net.client.handlers;
+
+import engine.Enum;
+import engine.Enum.DispatchChannel;
+import engine.Enum.GameObjectType;
+import engine.exception.MsgSendException;
+import engine.gameManager.BuildingManager;
+import engine.gameManager.SessionManager;
+import engine.gameManager.ZoneManager;
+import engine.math.Bounds;
+import engine.net.Dispatch;
+import engine.net.DispatchMessage;
+import engine.net.client.ClientConnection;
+import engine.net.client.msg.ClientNetMsg;
+import engine.net.client.msg.ErrorPopupMsg;
+import engine.net.client.msg.ManageCityAssetsMsg;
+import engine.net.client.msg.PlaceAssetMsg;
+import engine.objects.*;
+import org.joda.time.DateTime;
+
+/*
+ * @Author:
+ * @Summary: Processes application protocol message which opens
+ * and processes the various building asset management windows.
+ */
+public class ManageCityAssetMsgHandler extends AbstractClientMsgHandler {
+
+	public ManageCityAssetMsgHandler() {
+		super(ManageCityAssetsMsg.class);
+	}
+
+	public static boolean playerCanManageNotFriends(PlayerCharacter player, Building building){
+
+		//Player Can only Control Building if player is in Same Guild as Building and is higher rank than IC.
+
+		if (player == null)
+			return false;
+
+		if (building.getRank() == -1)
+			return false;
+
+		if (BuildingManager.IsOwner(building, player))
+			return true;
+
+		if (GuildStatusController.isGuildLeader(player.getGuildStatus()) == false && GuildStatusController.isInnerCouncil(player.getGuildStatus()) == false)
+			return false;
+
+		//Somehow guild leader check fails above? lets check if Player is true Guild GL.
+		if (building.getGuild() != null && building.getGuild().isGuildLeader(player.getObjectUUID()))
+			return true;
+
+		return Guild.sameGuild(building.getGuild(), player.getGuild());
+
+	}
+
+	@Override
+	protected boolean _handleNetMsg(ClientNetMsg baseMsg, ClientConnection origin) throws MsgSendException {
+
+		ManageCityAssetsMsg msg;
+		PlayerCharacter player;
+		ManageCityAssetsMsg outMsg;
+		Building building;
+
+		msg = (ManageCityAssetsMsg) baseMsg;
+
+		player = SessionManager.getPlayerCharacter(origin);
+
+		if (player == null)
+			return true;
+
+		building = BuildingManager.getBuildingFromCache(msg.getTargetID());
+
+		if (building == null){
+			if (msg.actionType == 14) {
+			
+				Zone zone = ZoneManager.findSmallestZone(player.getLoc());
+				
+				if (!zone.isPlayerCity()){
+					ErrorPopupMsg.sendErrorMsg(player, "Unable to find city to command.");
+					return true;
+				}
+				
+				City city = City.GetCityFromCache(zone.getPlayerCityUUID());
+				
+				if (city == null){
+					ErrorPopupMsg.sendErrorMsg(player, "Unable to find city to command.");
+					return true;
+				}
+				
+				if (!city.getGuild().equals(player.getGuild())){
+					ErrorPopupMsg.sendErrorMsg(player, "You are not in the correct guild to command this city.");
+					return true;
+				}
+				
+				if (!GuildStatusController.isInnerCouncil(player.getGuildStatus()) && !GuildStatusController.isGuildLeader(player.getGuildStatus())){
+					ErrorPopupMsg.sendErrorMsg(player, "You must be an Inner Council or Guild leader to access city commands.");
+					return true;
+				}
+				ManageCityAssetsMsg mca = new ManageCityAssetsMsg(player, building);
+				mca.actionType = 15;
+				Dispatch dispatch = Dispatch.borrow(player, mca);
+				DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
+			}
+			return true;
+		}
+
+		outMsg = new ManageCityAssetsMsg(player, building);
+
+		if (player.isSafeMode()){
+			outMsg.actionType = 4;
+			outMsg.setTargetType(building.getObjectType().ordinal());
+			outMsg.setTargetID(building.getObjectUUID());
+			outMsg.setAssetName(building.getName());
+			 Dispatch dispatch = Dispatch.borrow(player, outMsg);
+	            DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
+			return true;
+		}
+
+		if (msg.actionType == 2 || msg.actionType == 22) {
+
+			if (building.getBlueprint() != null && building.getBlueprint().getBuildingGroup() == engine.Enum.BuildingGroup.BANESTONE) {
+
+				outMsg.actionType = 18;
+				outMsg.setTargetType(building.getObjectType().ordinal());
+				outMsg.setTargetID(building.getObjectUUID());
+
+			} else if (BuildingManager.playerCanManage(player, building)) { //TODO allow Friends list.
+				configWindowState(player, building, outMsg);
+				outMsg.actionType = 3;
+				outMsg.setTargetType(building.getObjectType().ordinal());
+				outMsg.setTargetID(building.getObjectUUID());
+				outMsg.setTargetType3(building.getObjectType().ordinal());
+				outMsg.setTargetID3(building.getObjectUUID());
+				outMsg.setUnknown54(1);
+
+			} else {
+				
+				if (building.getBlueprintUUID() != 0)
+					switch (building.getBlueprint().getBuildingGroup()) {
+					case SHRINE:
+						if (building.getRank() == -1) {
+							if (!Bounds.collide(player.getLoc(), building)) {
+								ErrorPopupMsg.sendErrorPopup(player, 64);
+								return true;
+							}
+
+							Shrine shrine = Shrine.shrinesByBuildingUUID.get(building.getObjectUUID());
+
+							if (shrine == null)
+								return true;
+
+							if (shrine.getFavors() == 0) {
+								ErrorPopupMsg.sendErrorPopup(player, 166); // There is no more favor in this shrine to loot
+								return true;
+							}
+
+							BuildingManager.lootBuilding(player, building);
+							return true;
+						}
+						break;
+					case WAREHOUSE:
+						//TODO check
+						if (building.getRank() == -1) {
+							if (!Bounds.collide(player.getLoc(), building)) {
+								ErrorPopupMsg.sendErrorPopup(player, 64);
+								return true;
+							}
+
+							Warehouse warehouse = Warehouse.warehouseByBuildingUUID.get(building.getObjectUUID());
+
+							if (warehouse == null)
+								return true;
+
+							if (warehouse.isEmpty()) {
+								ErrorPopupMsg.sendErrorPopup(player, 167); // no more resources.
+								return true;
+							}
+
+							BuildingManager.lootBuilding(player, building);
+							return true;
+						}
+					}
+
+				if (building.getRank() == -1)
+					return true;
+
+				AbstractCharacter owner = building.getOwner();
+
+				//no owner, send building info
+				if (owner == null) {
+					msg.actionType = 4;
+
+					Dispatch dispatch = Dispatch.borrow(player, msg);
+		            DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
+					return true;
+				}
+				outMsg.actionType = 4;
+				outMsg.setTargetType(building.getObjectType().ordinal());
+				outMsg.setTargetID(building.getObjectUUID());
+				outMsg.setAssetName(building.getName());
+
+			}
+			 Dispatch dispatch = Dispatch.borrow(player, outMsg);
+	            DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
+	            return true;
+		}
+
+		if (msg.actionType == 13) {
+			outMsg.actionType = 13;
+			Dispatch dispatch = Dispatch.borrow(player, outMsg);
+            DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
+            return true;
+		}
+
+		
+
+		//Rename Building.
+
+		if (msg.actionType == 5) {
+
+			//TODO we need to check names before allowing
+			building.setName(msg.getAssetName());
+			configWindowState(player, building, outMsg);
+
+			outMsg.actionType = 3;
+			outMsg.setTargetType(building.getObjectType().ordinal());
+			outMsg.setTargetID(building.getObjectUUID());
+			outMsg.setTargetType3(GameObjectType.Building.ordinal());
+			outMsg.setTargetID3(building.getObjectUUID());
+			outMsg.setAssetName1(building.getName());
+			outMsg.setUnknown54(1);
+
+            Dispatch dispatch = Dispatch.borrow(player, outMsg);
+            DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
+            
+            return true;
+
+			//TOL, update city name also
+			//TODO update city and zone in database
+			//TODO update city map data in game server
+		}
+
+		if (msg.actionType == 14) {
+			ManageCityAssetsMsg mca = new ManageCityAssetsMsg(player, building);
+			mca.actionType = 15;
+			Dispatch dispatch = Dispatch.borrow(player, mca);
+	            DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
+	            return true;
+		}
+
+		if (msg.actionType == 20) {
+
+			Zone baneZone = building.getParentZone();
+
+			if (baneZone == null)
+				return true;
+
+			City banedCity = City.getCity(baneZone.getPlayerCityUUID());
+
+			if (banedCity == null)
+				return true;
+
+			Bane bane = banedCity.getBane();
+
+			if (bane == null || bane.getLiveDate() != null || player.getGuild() != banedCity.getGuild() || GuildStatusController.isInnerCouncil(player.getGuildStatus()) == false)
+				return true;
+
+			int baneHour = msg.getBaneHour();
+
+			if (baneHour < 16 || baneHour > 24) {
+				PlaceAssetMsg.sendPlaceAssetError(origin, 1, "A Serious error has occurred. Please post details for to ensure transaction integrity");
+				return true;
+			}
+
+			DateTime baneLive = new DateTime(bane.getPlacementDate());
+			baneLive = baneHour == 24 ? baneLive.plusDays(3) : baneLive.plusDays(2);
+			baneLive = baneHour == 24 ? baneLive.hourOfDay().setCopy(0) : baneLive.hourOfDay().setCopy(baneHour);
+			baneLive = baneLive.minuteOfHour().setCopy(0);
+			baneLive = baneLive.secondOfMinute().setCopy(1);
+			bane.setLiveDate(baneLive);
+			outMsg.actionType = 18;
+
+			Dispatch dispatch = Dispatch.borrow(player, outMsg);
+			DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
+			return true;
+		}
+		return true;
+	}
+
+	public void configWindowState(PlayerCharacter player, Building building, ManageCityAssetsMsg manageCityAssetsMsg) {
+
+		// Tests to turn on upgrade button if a building is not
+		// at it's maximum allowed rank or currently ranking
+
+
+		// Owner is obviously allowed to upgrade his own buildings
+
+		if (building.getOwner().equals(player)) {
+
+			// Players cannot destroy or transfer a TOL.
+
+			if (building.getBlueprint() == null){
+				manageCityAssetsMsg.buttonDestroy = 0;
+				manageCityAssetsMsg.buttonTransfer = 0;
+				manageCityAssetsMsg.buttonAbandon = 0;
+				manageCityAssetsMsg.buttonUpgrade = 0;
+			}
+			else
+			if (building.getBlueprint().getBuildingGroup() == Enum.BuildingGroup.TOL) {
+				manageCityAssetsMsg.buttonDestroy = 0;
+				manageCityAssetsMsg.buttonTransfer = 0;
+				manageCityAssetsMsg.buttonAbandon = 1;
+				manageCityAssetsMsg.buttonUpgrade = 1;
+			}
+			else if (building.getBlueprint().getBuildingGroup() == Enum.BuildingGroup.MINE) {
+				manageCityAssetsMsg.buttonDestroy = 0;
+				manageCityAssetsMsg.buttonTransfer = 0;
+				manageCityAssetsMsg.buttonAbandon = 0;
+				manageCityAssetsMsg.buttonUpgrade = 0;  // Cannot upgrade a mine
+			}
+			else{
+				manageCityAssetsMsg.buttonDestroy = 1;
+				manageCityAssetsMsg.buttonTransfer = 1;
+				manageCityAssetsMsg.buttonAbandon = 1;
+				manageCityAssetsMsg.buttonUpgrade = 1;
+			}
+		}
+
+		// Inner Council of the same guild can also upgrade
+
+		if ((player.getGuild().equals(building.getGuild())) &&
+				GuildStatusController.isInnerCouncil(player.getGuildStatus()))
+			manageCityAssetsMsg.buttonUpgrade = 1;
+
+		// Disable upgrade button if at max rank.
+
+		if (building.getBlueprint() == null)
+			manageCityAssetsMsg.buttonUpgrade = 0;
+		else
+		if (building.getRank() >= building.getBlueprint().getMaxRank())
+			manageCityAssetsMsg.buttonUpgrade = 0;;
+
+		// If a building is not protected we can exit here
+
+		if (building.assetIsProtected() == false)
+			return;
+
+		// Protection is displayed as "UNDER SIEGE" if
+		// an active bane is invalidating the protection
+		// contracts of the city.
+
+		if ((building.getCity() != null) &&
+				(building.getCity().protectionEnforced == false)) {
+			manageCityAssetsMsg.labelProtected = 0;
+			manageCityAssetsMsg.labelSiege = 1;
+			manageCityAssetsMsg.labelCeaseFire = 0;
+			return;
+		}
+
+		// Building is currently protected by a TOL
+
+		manageCityAssetsMsg.labelProtected = 1;
+	}
+}
diff --git a/src/engine/net/client/handlers/MerchantMsgHandler.java b/src/engine/net/client/handlers/MerchantMsgHandler.java
new file mode 100644
index 00000000..687a29f9
--- /dev/null
+++ b/src/engine/net/client/handlers/MerchantMsgHandler.java
@@ -0,0 +1,465 @@
+package engine.net.client.handlers;
+
+import engine.Enum;
+import engine.Enum.GuildHistoryType;
+import engine.InterestManagement.RealmMap;
+import engine.exception.MsgSendException;
+import engine.gameManager.*;
+import engine.job.JobScheduler;
+import engine.jobs.TeleportJob;
+import engine.math.Vector3fImmutable;
+import engine.net.Dispatch;
+import engine.net.DispatchMessage;
+import engine.net.client.ClientConnection;
+import engine.net.client.msg.*;
+import engine.objects.*;
+import engine.powers.PowersBase;
+import engine.server.MBServerStatics;
+
+import java.util.ArrayList;
+
+/*
+ * @Author:
+ * @Summary: Processes a variety of windows the client can open
+ * such as realm blessings and warehouse deposits.
+ */
+
+public class MerchantMsgHandler extends AbstractClientMsgHandler {
+
+	public MerchantMsgHandler() {
+		super(MerchantMsg.class);
+	}
+
+	@Override
+	protected boolean _handleNetMsg(ClientNetMsg baseMsg, ClientConnection origin) throws MsgSendException {
+
+		// Member variable declaration
+
+		MerchantMsg msg;
+		PlayerCharacter player;
+		NPC npc;
+		int msgType;
+		Building warehouse;
+		Dispatch dispatch;
+
+		// Member variable assignment
+
+		player = SessionManager.getPlayerCharacter(origin);
+		msg = (MerchantMsg) baseMsg;
+		npc = NPC.getNPC(msg.getNPCID());
+
+		// Early exit if something goes awry
+
+		if ((player == null) || (npc == null))
+			return true;
+
+		// Player must be within talking range
+
+		if (player.getLoc().distanceSquared2D(npc.getLoc()) > MBServerStatics.NPC_TALK_RANGE * MBServerStatics.NPC_TALK_RANGE) {
+			ErrorPopupMsg.sendErrorPopup(player, 14);
+			return true;
+		}
+
+		// Process application protocol message
+
+		msgType = msg.getType();
+
+		switch (msgType) {
+		case 3:
+			break;
+		case 5:
+
+			dispatch = Dispatch.borrow(player, msg);
+			DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+			requestSwearAsSubGuild(msg, origin, player, npc);
+			break;
+		case 10:
+			teleportRepledgeScreen(msg, origin, player, false, npc);
+			break;
+		case 11:
+			teleportRepledge(msg, origin, player, false, npc);
+			break;
+		case 12:
+			teleportRepledgeScreen(msg, origin, player, true, npc);
+			break;
+		case 13:
+			teleportRepledge(msg, origin, player, true, npc);
+			break;
+		case 14:
+			if (isHermit(npc))
+				requestHermitBlessing(msg, origin, player, npc);
+			else
+				requestBoon(msg, origin, player, npc);
+			break;
+		case 15:
+			LeaderboardMessage lbm = new LeaderboardMessage();
+			dispatch = Dispatch.borrow(player, lbm);
+			DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+			break;
+		case 16:
+			ViewResourcesMessage vrm = new ViewResourcesMessage(player);
+			warehouse = npc.getBuilding();
+			vrm.setGuild(player.getGuild());
+			vrm.setWarehouseBuilding(warehouse);
+			vrm.configure();
+			dispatch = Dispatch.borrow(player, vrm);
+			DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+			break;
+		case 17:
+			Warehouse.warehouseWithdraw(msg, player, npc, origin);
+			break;
+		case 18:
+			Warehouse.warehouseDeposit(msg, player, npc, origin);
+			break;
+		case 19:
+			Warehouse.warehouseLock(msg, player, npc, origin);
+			break;
+		}
+
+		return true;
+
+	}
+
+	private static void requestSwearAsSubGuild(MerchantMsg msg, ClientConnection origin, PlayerCharacter player, NPC npc) {
+
+		boolean Disabled = true;
+
+		if (Disabled){
+			ErrorPopupMsg.sendErrorMsg(player, "Swearing to Safeholds have been temporary disabled."); //Cannot sub as errant guild.
+			return;
+		}
+		
+		if (player.getGuild().isErrant()){
+			ErrorPopupMsg.sendErrorMsg(player, "You do not belong to a guild!"); //Cannot sub as errant guild.
+			return;
+		}
+
+		if (player.getGuild().getNation() != null && !player.getGuild().getNation().isErrant()){
+			ErrorPopupMsg.sendErrorMsg(player, "You already belong to a nation!"); //Cannot sub as errant guild.
+			return;
+		}
+
+		if (player.getGuild().getGuildLeaderUUID() != player.getObjectUUID()){
+			ErrorPopupMsg.sendErrorMsg(player, "You must be a Guild Leader to Swear your guild as a Sub Guild!"); //Cannot sub as errant guild.
+			return;
+		}
+
+		if (!GuildStatusController.isGuildLeader(player.getGuildStatus())){
+			ErrorPopupMsg.sendErrorMsg(player, "You must be a Guild Leader to Swear your guild as a Sub Guild!"); //Cannot sub as errant guild.
+			return;
+		}
+
+		
+		if (!npc.getGuild().isNPCGuild()){
+			ErrorPopupMsg.sendErrorMsg(player, "Runemaster does not belong to a safehold!"); //Cannot sub as errant guild.
+			return;
+		}
+
+		if (npc.getGuild().getRepledgeMin() > player.getLevel()){
+			ErrorPopupMsg.sendErrorMsg(player, "You are too low of a level to sub to this guild!"); //Cannot sub as errant guild.
+			return;
+		}
+
+		if (npc.getGuild().getRepledgeMax() < 75){
+			ErrorPopupMsg.sendErrorMsg(player, "Runemaster Guild Cannot Swear in your guild!"); //Cannot sub as errant guild.
+			return;
+		}
+
+
+
+
+
+		if (!DbManager.GuildQueries.UPDATE_PARENT(player.getGuild().getObjectUUID(), npc.getGuild().getObjectUUID())) {
+			ErrorPopupMsg.sendErrorMsg(player, "A Serious error has occured. Please post details for to ensure transaction integrity");
+			return;
+		}
+
+
+		GuildManager.updateAllGuildBinds(player.getGuild(), npc.getGuild().getOwnedCity());
+
+
+
+		//update Guild state.
+		player.getGuild().setNation(npc.getGuild());
+		GuildManager.updateAllGuildTags(player.getGuild());
+
+		//update state twice, errant to petitioner, to sworn.
+		player.getGuild().upgradeGuildState(false);//to petitioner
+		player.getGuild().upgradeGuildState(false);//to sworn
+
+
+
+
+
+	}
+
+
+
+	private static void requestHermitBlessing(MerchantMsg msg, ClientConnection origin, PlayerCharacter player, NPC npc) {
+
+		Guild guild;
+		Realm realm;
+		City city;
+		Building tol;
+
+		// Validate player can obtain blessing
+
+		if (GuildStatusController.isGuildLeader(player.getGuildStatus()) == false) {
+			ErrorPopupMsg.sendErrorPopup(player, 173); // You must be the leader of a guild to receive a blessing
+			return;
+		}
+
+		guild = player.getGuild();
+		city = guild.getOwnedCity();
+
+		if (city == null) {
+			ErrorPopupMsg.sendErrorPopup(player, 179); // Only landed guilds may claim a territory
+			return;
+		}
+		tol = city.getTOL();
+
+		if (tol.getRank() != 7) {
+			ErrorPopupMsg.sendErrorPopup(player, 181); // Your tree must be rank 7 before claiming a territory
+			return;
+		}
+
+		realm = RealmMap.getRealmForCity(city);
+
+		if (realm.getCanBeClaimed() == false) {
+			ErrorPopupMsg.sendErrorPopup(player, 180); // This territory cannot be ruled by anyone
+			return;
+		}
+
+		if (realm.isRuled() == true) {
+			ErrorPopupMsg.sendErrorPopup(player, 178); // This territory is already claimed
+			return;
+		}
+
+		// Everything should be good, apply boon for this hermit
+
+		PowersManager.applyPower(player, player, player.getLoc(), getPowerforHermit(npc).getToken(), 40, false);
+
+	}
+
+	private static void requestBoon(MerchantMsg msg, ClientConnection origin, PlayerCharacter player, NPC npc) {
+
+		Building shrineBuilding;
+		Shrine shrine;
+
+		if (npc.getGuild() != player.getGuild())
+			return;
+
+		shrineBuilding = npc.getBuilding();
+
+		if (shrineBuilding == null)
+			return;
+
+		if (shrineBuilding.getBlueprint() != null && shrineBuilding.getBlueprint().getBuildingGroup() != engine.Enum.BuildingGroup.SHRINE)
+			return;
+
+		if (shrineBuilding.getRank() == -1)
+			return;
+
+		shrine = Shrine.shrinesByBuildingUUID.get(shrineBuilding.getObjectUUID());
+
+		if (shrine == null)
+			return;
+
+		if (shrine.getFavors() == 0) {
+			ErrorPopupMsg.sendErrorPopup(player, 172);
+			return;
+		}
+
+		//already haz boon.
+
+		if (player.containsEffect(shrine.getShrineType().getPowerToken())) {
+			ErrorPopupMsg.sendErrorPopup(player, 199);
+			return;
+		}
+
+		if (!Shrine.canTakeFavor(player, shrine))
+			return;
+
+		if (!shrine.takeFavor(player))
+			return;
+
+		PowersBase shrinePower = PowersManager.getPowerByToken(shrine.getShrineType().getPowerToken());
+
+		if (shrinePower == null) {
+			ChatManager.chatSystemError(player, "FAILED TO APPLY POWER!");
+			return;
+		}
+
+		int rank = shrine.getRank();
+		//R8 trees always get atleast rank 2 boons. rank uses index, where 0 is first place, 1 is second, etc...
+		if (shrineBuilding.getCity() != null && shrineBuilding.getCity().getTOL() != null && shrineBuilding.getCity().getTOL().getRank() == 8)
+			if (rank != 0)
+				rank = 1;
+		int trains = 40 - (rank * 10);
+		if (trains < 0)
+			trains = 0;
+
+		//System.out.println(trains);
+		PowersManager.applyPower(player, player, player.getLoc(), shrinePower.getToken(), trains, false);
+		ChatManager.chatGuildInfo(player.getGuild(), player.getName() + " has recieved a boon costing " + 1 + " point of favor.");
+		shrineBuilding.addEffectBit(1000000 << 2);
+		shrineBuilding.updateEffects();
+
+		//remove the effect so players loading shrines dont see the effect go off.
+		shrineBuilding.removeEffectBit(1000000 << 2);
+	}
+
+	private static void teleportRepledgeScreen(MerchantMsg msg, ClientConnection origin, PlayerCharacter pc, boolean isTeleport, NPC npc) {
+
+		Dispatch dispatch;
+		TeleportRepledgeListMsg trlm;
+
+		//verify npc is runemaster
+
+		Contract contract = npc.getContract();
+
+		if (contract == null || !contract.isRuneMaster())
+			return;
+
+		if (!isTeleport)
+			trlm = new TeleportRepledgeListMsg(pc, false);
+		else
+			trlm = new TeleportRepledgeListMsg(pc, true);
+
+		trlm.configure();
+
+		dispatch = Dispatch.borrow(pc, trlm);
+		DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+	}
+
+	private static void teleportRepledge(MerchantMsg msg, ClientConnection origin, PlayerCharacter player, boolean isTeleport, NPC npc) {
+
+		//verify npc is runemaster
+
+		Contract contract = npc.getContract();
+		Dispatch dispatch;
+
+		if (contract == null || !contract.isRuneMaster())
+			return;
+
+		//get city to teleport/repledge to and verify valid
+
+		ArrayList<City> cities;
+
+		City targetCity = null;
+
+		if (isTeleport)
+			cities = City.getCitiesToTeleportTo(player);
+		else
+			cities = City.getCitiesToRepledgeTo(player);
+		for (City city : cities) {
+			if (city.getObjectUUID() == msg.getCityID()) {
+				targetCity = city;
+				break;
+			}
+		}
+
+		if (targetCity == null)
+			return;
+
+		//verify level required to teleport or repledge
+
+		Guild toGuild = targetCity.getGuild();
+
+		if (toGuild != null)
+			if (isTeleport) {
+				if (player.getLevel() < toGuild.getTeleportMin() || player.getLevel() > toGuild.getTeleportMax())
+					return;
+			}
+			else if (player.getLevel() < toGuild.getRepledgeMin() || player.getLevel() > toGuild.getRepledgeMax())
+				return;
+
+		boolean joinedGuild = false;
+
+		//if repledge, reguild the player
+
+		if (!isTeleport)
+			joinedGuild = GuildManager.joinGuild(player, targetCity.getGuild(), targetCity.getObjectUUID(), GuildHistoryType.JOIN);
+
+		int time;
+
+		if (!isTeleport) //repledge
+			time = MBServerStatics.REPLEDGE_TIME_IN_SECONDS;
+		else
+			time = MBServerStatics.TELEPORT_TIME_IN_SECONDS;
+
+		//resend message
+		msg.setTeleportTime(time);
+
+		if ((!isTeleport && joinedGuild) || (isTeleport)) {
+
+			dispatch = Dispatch.borrow(player, msg);
+			DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+		}
+
+		//teleport player to city
+
+		Vector3fImmutable teleportLoc;
+
+		if (targetCity.getTOL().getRank() == 8)
+			teleportLoc = targetCity.getTOL().getStuckLocation();
+		else
+			teleportLoc = Vector3fImmutable.getRandomPointOnCircle(targetCity.getTOL().getLoc(), MBServerStatics.TREE_TELEPORT_RADIUS);
+
+		if (time > 0) {
+			//TODO add timer to teleport
+			TeleportJob tj = new TeleportJob(player, npc, teleportLoc, origin, true);
+			JobScheduler.getInstance().scheduleJob(tj, time * 1000);
+		}
+		else if (joinedGuild) {
+			player.teleport(teleportLoc);
+			player.setSafeMode();
+		}
+	}
+
+	private static PowersBase getPowerforHermit(NPC npc) {
+
+		int contractID;
+		PowersBase power;
+		Contract contract;
+
+		contract = npc.getContract();
+		contractID = contract.getContractID();
+		power = null;
+
+		switch (contractID) {
+		case 435579:
+			power = PowersManager.getPowerByIDString("BLS-POWER");
+			break;
+		case 435580:
+			power = PowersManager.getPowerByIDString("BLS-FORTUNE");
+			break;
+		case 435581:
+			power = PowersManager.getPowerByIDString("BLS-WISDOM");
+			break;
+
+		}
+		return power;
+	}
+
+	private static boolean isHermit(NPC npc) {
+
+		int contractID;
+		boolean retValue = false;
+
+		contractID = npc.getContractID();
+
+		switch (contractID) {
+		case 435579:
+		case 435580:
+		case 435581:
+			retValue = true;
+			break;
+		default:
+			break;
+		}
+
+		return retValue;
+	}
+
+}
\ No newline at end of file
diff --git a/src/engine/net/client/handlers/MinionTrainingMsgHandler.java b/src/engine/net/client/handlers/MinionTrainingMsgHandler.java
new file mode 100644
index 00000000..c0bd4819
--- /dev/null
+++ b/src/engine/net/client/handlers/MinionTrainingMsgHandler.java
@@ -0,0 +1,316 @@
+package engine.net.client.handlers;
+
+import engine.Enum;
+import engine.Enum.DispatchChannel;
+import engine.InterestManagement.WorldGrid;
+import engine.ai.MobileFSM;
+import engine.exception.MsgSendException;
+import engine.gameManager.BuildingManager;
+import engine.gameManager.DbManager;
+import engine.gameManager.SessionManager;
+import engine.net.Dispatch;
+import engine.net.DispatchMessage;
+import engine.net.client.ClientConnection;
+import engine.net.client.msg.*;
+import engine.objects.*;
+import engine.server.MBServerStatics;
+import org.pmw.tinylog.Logger;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+/*
+ * @Author:
+ * @Summary: Processes application protocol message which
+ * processes training of minions in guard barracks
+ */
+
+public class MinionTrainingMsgHandler extends AbstractClientMsgHandler {
+
+	public static HashMap<Integer, ArrayList<Integer>> _minionsByCaptain = null;
+
+	public MinionTrainingMsgHandler() {
+		super(MinionTrainingMessage.class);
+	}
+
+	@Override
+	protected boolean _handleNetMsg(ClientNetMsg baseMsg, ClientConnection origin) throws MsgSendException {
+
+		MinionTrainingMessage minionMsg = (MinionTrainingMessage) baseMsg;
+
+		PlayerCharacter player = SessionManager.getPlayerCharacter(origin);
+
+		if (player == null)
+			return true;
+
+		if (minionMsg.getNpcType() == Enum.GameObjectType.NPC.ordinal()){
+
+			NPC npc = NPC.getFromCache(minionMsg.getNpcID());
+
+			if (npc == null)
+				return true;
+
+			Building b = BuildingManager.getBuildingFromCache(minionMsg.getBuildingID());
+
+			if (b == null)
+				return true;
+
+			//clear minion
+
+			if (npc.minionLock.writeLock().tryLock()) {
+				try {
+					if (minionMsg.getType() == 2) {
+
+						Mob toRemove = Mob.getFromCache(minionMsg.getUUID());
+
+						if (!npc.getSiegeMinionMap().containsKey(toRemove))
+							return true;
+
+						toRemove.setState(MobileFSM.STATE.Disabled);
+						npc.getSiegeMinionMap().remove(toRemove);
+
+						//toRemove.disableIntelligence();
+						WorldGrid.RemoveWorldObject(toRemove);
+
+						if (toRemove.getParentZone() != null)
+							toRemove.getParentZone().zoneMobSet.remove(toRemove);
+
+						DbManager.removeFromCache(toRemove);
+						PlayerCharacter petOwner = toRemove.getOwner();
+
+						if (petOwner != null) {
+							petOwner.setPet(null);
+							toRemove.setOwner(null);
+							PetMsg petMsg = new PetMsg(5, null);
+							Dispatch dispatch = Dispatch.borrow(petOwner, petMsg);
+							DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
+						}
+
+						// we Found the move to remove, lets break the for loop so it doesnt look for more.
+
+						ManageCityAssetsMsg mca1 = new ManageCityAssetsMsg(player, b);
+						mca1.actionType = 3;
+						mca1.setTargetType(b.getObjectType().ordinal());
+						mca1.setTargetID(b.getObjectUUID());
+
+						mca1.setTargetType3(npc.getObjectType().ordinal());
+						mca1.setTargetID3(npc.getObjectUUID());
+						mca1.setAssetName1(b.getName());
+						mca1.setUnknown54(1);
+
+						Dispatch dispatch = Dispatch.borrow(player, mca1);
+						DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
+
+						ManageNPCMsg mnm = new ManageNPCMsg(npc);
+						dispatch = Dispatch.borrow(player, mnm);
+						DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
+
+						//Add Minion
+					}
+					else {
+						Zone zone = npc.getParentZone();
+
+						if (zone == null)
+							return true;
+
+						int maxSlots = 3;
+
+						if (npc.getContractID() == 842)
+							maxSlots = 1;
+
+						if (npc.getSiegeMinionMap().size() == maxSlots)
+							return true;
+
+						int mobBase;
+
+						switch (minionMsg.getMinion()){
+							case 9:
+								mobBase = 13171;
+								break;
+							case 2:
+								mobBase = 13758;
+								break;
+							case 3:
+								mobBase = 13757;
+								break;
+							case 4:
+								mobBase = 2111;
+								break;
+							case 5:
+								mobBase = 12402;
+								break;
+							case 6:
+								mobBase = 2113;
+								break;
+							default:
+								mobBase = minionMsg.getMinion();
+						}
+
+						if (mobBase == 0)
+							return true;
+
+						Mob toCreate = npc.createSiegeMob(mobBase, npc.getGuild(), zone, b.getLoc(), (short) 1);
+
+						if (toCreate == null)
+							return true;
+
+						//   toCreate.despawn();
+						if (toCreate != null) {
+							toCreate.setSpawnTime(60 * 15);
+							toCreate.setTimeToSpawnSiege(System.currentTimeMillis() + (60 * 15 * 1000));
+							toCreate.setDeathTime(System.currentTimeMillis());
+							toCreate.setState(MobileFSM.STATE.Respawn);
+						}
+					}
+
+					ManageNPCMsg mnm = new ManageNPCMsg(npc);
+					mnm.setMessageType(1);
+					Dispatch dispatch = Dispatch.borrow(player, mnm);
+					DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
+
+				} finally {
+					npc.minionLock.writeLock().unlock();
+				}
+			}
+
+		}else if (minionMsg.getNpcType() == Enum.GameObjectType.Mob.ordinal()){
+
+			Mob npc = Mob.getFromCache(minionMsg.getNpcID());
+
+			if (npc == null)
+				return true;
+
+			Building b = BuildingManager.getBuildingFromCache(minionMsg.getBuildingID());
+
+			if (b == null)
+				return true;
+
+			//clear minion
+
+			if (npc.minionLock.writeLock().tryLock()) {
+				try {
+					if (minionMsg.getType() == 2) {
+
+						Mob toRemove = Mob.getFromCache(minionMsg.getUUID());
+						if (!npc.getSiegeMinionMap().containsKey(toRemove))
+							return true;
+
+						if (!DbManager.MobQueries.REMOVE_FROM_GUARDS(npc.getObjectUUID(), toRemove.getMobBaseID(), npc.getSiegeMinionMap().get(toRemove)))
+							return true;
+
+						toRemove.setState(MobileFSM.STATE.Disabled);
+						npc.getSiegeMinionMap().remove(toRemove);
+
+						//toRemove.disableIntelligence();
+						WorldGrid.RemoveWorldObject(toRemove);
+
+						if (toRemove.getParentZone() != null)
+							toRemove.getParentZone().zoneMobSet.remove(toRemove);
+
+						DbManager.removeFromCache(toRemove);
+						PlayerCharacter petOwner = toRemove.getOwner();
+
+						if (petOwner != null) {
+							petOwner.setPet(null);
+							toRemove.setOwner(null);
+							PetMsg petMsg = new PetMsg(5, null);
+							Dispatch dispatch = Dispatch.borrow(petOwner, petMsg);
+							DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
+						}
+
+						// we Found the move to remove, lets break the for loop so it doesnt look for more.
+
+						ManageCityAssetsMsg mca1 = new ManageCityAssetsMsg(player, b);
+						mca1.actionType = 3;
+						mca1.setTargetType(b.getObjectType().ordinal());
+						mca1.setTargetID(b.getObjectUUID());
+
+						mca1.setTargetType3(npc.getObjectType().ordinal());
+						mca1.setTargetID3(npc.getObjectUUID());
+						mca1.setAssetName1(b.getName());
+						mca1.setUnknown54(1);
+
+						Dispatch dispatch = Dispatch.borrow(player, mca1);
+						DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);;
+
+						ManageNPCMsg mnm = new ManageNPCMsg(npc);
+						dispatch = Dispatch.borrow(player, mnm);
+						DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
+
+						//Add Minion
+					}
+					else {
+						Zone zone = npc.getParentZone();
+
+						if (zone == null)
+							return true;
+
+						int maxSlots = 5;
+
+						if (npc.getContract().getContractID() == 842)
+							maxSlots = 1;
+
+						switch (npc.getRank()){
+							case 1:
+							case 2:
+								maxSlots = 1;
+								break;
+							case 3:
+								maxSlots = 2;
+								break;
+							case 4:
+							case 5:
+								maxSlots = 3;
+								break;
+							case 6:
+								maxSlots = 4;
+								break;
+							case 7:
+								maxSlots = 5;
+								break;
+						}
+
+						if (npc.getSiegeMinionMap().size() == maxSlots)
+							return true;
+
+						int mobBase = npc.getMobBaseID();
+						
+						if (mobBase == 0)
+							return true;
+
+						String pirateName = NPC.getPirateName(mobBase);
+
+						if (!DbManager.MobQueries.ADD_TO_GUARDS(npc.getObjectUUID(), mobBase, pirateName, npc.getSiegeMinionMap().size() + 1))
+							return true;
+
+						Mob toCreate = npc.createGuardMob(mobBase, npc.getGuild(), zone, b.getLoc(), npc.getLevel(),pirateName);
+
+						if (toCreate == null)
+							return true;
+
+						//   toCreate.despawn();
+						if (toCreate != null) {
+							toCreate.setTimeToSpawnSiege(System.currentTimeMillis() + MBServerStatics.FIFTEEN_MINUTES);
+							toCreate.setDeathTime(System.currentTimeMillis());
+							toCreate.setState(MobileFSM.STATE.Respawn);
+						}
+					}
+
+					ManageNPCMsg mnm = new ManageNPCMsg(npc);
+					mnm.setMessageType(1);
+					Dispatch dispatch = Dispatch.borrow(player, mnm);
+					DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
+
+				}catch (Exception e){
+					Logger.error(e);
+				}finally {
+				
+					npc.minionLock.writeLock().unlock();
+				}
+			}
+
+		}
+		return true;
+	}
+
+}
\ No newline at end of file
diff --git a/src/engine/net/client/handlers/MoveToPointHandler.java b/src/engine/net/client/handlers/MoveToPointHandler.java
new file mode 100644
index 00000000..f699b7b0
--- /dev/null
+++ b/src/engine/net/client/handlers/MoveToPointHandler.java
@@ -0,0 +1,38 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.handlers;
+
+import engine.exception.MsgSendException;
+import engine.gameManager.MovementManager;
+import engine.net.client.ClientConnection;
+import engine.net.client.msg.ClientNetMsg;
+import engine.net.client.msg.MoveToPointMsg;
+import engine.objects.PlayerCharacter;
+
+public class MoveToPointHandler extends AbstractClientMsgHandler {
+
+    public MoveToPointHandler() {
+        super(MoveToPointMsg.class);
+    }
+
+    @Override
+    protected boolean _handleNetMsg(ClientNetMsg baseMsg,
+            ClientConnection origin) throws MsgSendException {
+        MoveToPointMsg msg = (MoveToPointMsg) baseMsg;
+
+        PlayerCharacter pc = (origin != null) ? (origin.getPlayerCharacter()) : null;
+        if (pc == null)
+            return false;
+
+        MovementManager.movement(msg, pc);
+        return true;
+    }
+
+}
diff --git a/src/engine/net/client/handlers/ObjectActionMsgHandler.java b/src/engine/net/client/handlers/ObjectActionMsgHandler.java
new file mode 100644
index 00000000..10de0018
--- /dev/null
+++ b/src/engine/net/client/handlers/ObjectActionMsgHandler.java
@@ -0,0 +1,569 @@
+package engine.net.client.handlers;
+
+import engine.Enum;
+import engine.Enum.BuildingGroup;
+import engine.Enum.DispatchChannel;
+import engine.Enum.ItemType;
+import engine.InterestManagement.RealmMap;
+import engine.InterestManagement.WorldGrid;
+import engine.exception.MsgSendException;
+import engine.gameManager.*;
+import engine.math.Bounds;
+import engine.math.Vector3fImmutable;
+import engine.net.Dispatch;
+import engine.net.DispatchMessage;
+import engine.net.client.ClientConnection;
+import engine.net.client.msg.*;
+import engine.objects.*;
+import engine.powers.PowersBase;
+import org.pmw.tinylog.Logger;
+
+import java.util.ArrayList;
+import java.util.concurrent.ThreadLocalRandom;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+/*
+ * @Author:
+ * @Summary: Processes application protocol message which actives
+ * items such as charters and deeds in the character's inventory
+ */
+public class ObjectActionMsgHandler extends AbstractClientMsgHandler {
+
+	// Reentrant lock for dropping banes
+
+	private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
+
+	public ObjectActionMsgHandler() {
+		super(ObjectActionMsg.class);
+	}
+
+	@Override
+	protected boolean _handleNetMsg(ClientNetMsg baseMsg, ClientConnection origin) throws MsgSendException {
+
+		// Member variable declaration
+		ObjectActionMsg msg;
+		PlayerCharacter player;
+		CharacterItemManager itemMan;
+		ArrayList<Long> comps;
+		Dispatch dispatch;
+		boolean waterbucketBypass = false;
+
+		// Member variable assignment
+		msg = (ObjectActionMsg) baseMsg;
+		player = SessionManager.getPlayerCharacter(origin);
+
+		if (player == null) {
+			return true;
+		}
+
+		itemMan = player.getCharItemManager();
+
+		if (itemMan == null) {
+			return true;
+		}
+
+		comps = msg.getTargetCompID();
+
+		if (comps.isEmpty()) {
+			return true;
+		}
+
+		long comp = comps.get(0);
+
+		if (((int) comp) != 0) {
+			Item item = Item.getFromCache((int) comp);
+
+			if (item == null) {
+				return true;
+			}
+
+			//dupe check
+			if (!item.validForInventory(origin, player, itemMan)) {
+				return true;
+			}
+
+			ItemBase ib = item.getItemBase();
+
+			if (ib == null) {
+				return true;
+			}
+
+			if (itemMan.doesCharOwnThisItem(item.getObjectUUID())) {
+
+				if (ib.isConsumable() || ib.getType() == ItemType.FARMABLE) {
+
+					int uuid = ib.getUUID();
+					int type = ib.getType().getValue();
+
+					switch (type) {
+					case 27: //Mithril repair
+						break;
+					case 10: //charters
+						//don't think they're handled here?
+						break;
+					case 19: //buildings
+						//Call add building screen here, ib.getUseID() get's building ID
+
+						//if inside player city, center loc on tol. otherwise center on player.
+						Vector3fImmutable loc = player.getLoc();
+						Zone zone = ZoneManager.findSmallestZone(player.getLoc());
+
+						if (zone != null) {
+							if (zone.isPlayerCity()) {
+								loc = zone.getLoc();
+							}
+						}
+
+						PlaceAssetMsg pam = new PlaceAssetMsg();
+						pam.setActionType(2);
+						pam.setContractID(item.getObjectUUID());
+						pam.setX(loc.getX() + 64); //offset grid from tol
+						pam.setY(loc.getY());
+						pam.setZ(loc.getZ() + 64); //offset grid from tol
+						pam.addPlacementInfo(ib.getUseID());
+
+						dispatch = Dispatch.borrow(player, pam);
+						DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+
+						//itemMan.consume(item); //temporary fix for dupe.. TODO Make Item Unusable after This message is sent.
+						break;
+					case 25: //furniture
+						//Call add furniture screen here. ib.getUseID() get's furniture ID
+						break;
+					case 33:
+						long shrineCompID = comps.get(1);
+						Building shrineBuilding = BuildingManager.getBuilding((int)shrineCompID);
+						if (shrineBuilding == null) {
+							return true;
+						}
+						if (shrineBuilding.getBlueprint() != null && shrineBuilding.getBlueprint().getBuildingGroup() != engine.Enum.BuildingGroup.SHRINE) {
+							return true;
+						}
+
+						if (shrineBuilding.getRank() == -1) {
+							return true;
+						}
+						Shrine shrine = Shrine.shrinesByBuildingUUID.get(shrineBuilding.getObjectUUID());
+
+						if (shrine == null) {
+							return true;
+						}
+
+						if (shrine.addFavor(player, item)) {
+							shrineBuilding.addEffectBit(1000000 << 2);
+							shrineBuilding.updateEffects();
+							shrineBuilding.removeEffectBit(1000000 << 2);
+						}
+						break;
+
+					case 35:
+						int charterType = 0;
+						switch (uuid) {
+						case 910020:
+							charterType = 762228431;
+							break;
+						case 910021:
+							charterType = -15978914;
+							break;
+						case 910022:
+							charterType = -600065291;
+							break;
+						}
+						if (claimRealm(player, charterType) == true) {
+							itemMan.consume(item);
+						}
+						break;
+					case 7: //rod of command
+						long compID = comps.get(1);
+
+						int objectType = AbstractWorldObject.extractTypeID(compID).ordinal();
+						Mob toCommand;
+						if (objectType == engine.Enum.GameObjectType.Mob.ordinal()) {
+							toCommand = Mob.getFromCache((int)compID);
+						} //Only Command Mob Types.
+						else {
+							return true;
+						}
+
+						if (toCommand == null) {
+							return true;
+						}
+
+						if (!toCommand.isSiege())
+							return true;
+
+						if (player.commandSiegeMinion(toCommand)) {
+							itemMan.consume(item);
+						}
+						break;
+						//ANNIVERSERY GIFT
+					case 31:
+
+
+						if (ib.getUUID() == 971012){
+							int random = ThreadLocalRandom.current().nextInt(ItemBase.AnniverseryGifts.size());
+							int annyID = ItemBase.AnniverseryGifts.get(random);
+
+							ItemBase annyIB = ItemBase.getItemBase(annyID);
+							if (annyIB != null){
+								Item gift = MobLoot.createItemForPlayer(player, annyIB);
+								if (gift != null){
+									itemMan.addItemToInventory(gift);
+									itemMan.consume(item);
+								}
+							}
+							break;
+						}
+
+						LootTable.CreateGamblerItem(item, player);
+
+
+						break;
+
+					case 30: //water bucket
+					case 8: //potions, tears of saedron
+
+					case 5: //runes, petition, warrant, scrolls
+						if (uuid > 3000 && uuid < 3050) { //Discipline Runes
+							if (ApplyRuneMsg.applyRune(uuid, origin, player)) {
+								itemMan.consume(item);
+							}
+							break;
+						} else if (uuid > 249999 && uuid < 250123) { //stat and mastery runes
+							if (ApplyRuneMsg.applyRune(uuid, origin, player)) {
+								itemMan.consume(item);
+							}
+							break;
+						} else if (uuid > 250114 && uuid < 250123) { //mastery runes
+							if (ApplyRuneMsg.applyRune(uuid, origin, player)) {
+								itemMan.consume(item);
+							}
+							break;
+						} else if (uuid > 252122 && uuid < 252128) { //mastery runes
+							if (ApplyRuneMsg.applyRune(uuid, origin, player)) {
+								itemMan.consume(item);
+							}
+							break;
+						} else if (uuid > 680069 && uuid < 680074) //Handle Charter, Deed, Petition, Warrant here
+						{
+							break;
+						} else if (uuid > 910010 && uuid < 910019) {
+
+							int rank = uuid - 910010;
+
+							if (rank < 1 || rank > 8) {
+								ChatManager.chatSystemError(player, "Invalid Rank for bane scroll!");
+								return true;
+							}
+							// Only one banestone at a time
+							lock.writeLock().lock();
+
+							try {
+								if (Bane.summonBanestone(player, origin, rank) == true)
+									itemMan.consume(item);
+							} finally {
+								lock.writeLock().unlock();
+							}
+							break;
+						} else if (uuid == 910010) { //tears of saedron
+							if (comps.size() > 1) {
+								removeRune(player, origin, comps.get(1).intValue());
+							}
+							break;
+						}
+
+						else if (item.getChargesRemaining() > 0) {
+							ArrayList<Long> tarList = msg.getTargetCompID();
+							AbstractWorldObject target = player;
+							if (tarList.size() > 1) {
+								long tarID = tarList.get(1);
+								if (tarID != 0) {
+									AbstractGameObject tarAgo = AbstractGameObject.getFromTypeAndID(tarID);
+									if (tarAgo != null && tarAgo instanceof AbstractWorldObject) {
+										target = (AbstractWorldObject) tarAgo;
+									}
+								}
+							}
+
+							// Bypass for waterbuckets
+
+							// test character targeted
+
+							if (ib.getUUID() == 910005) {
+
+								// test for valid target type
+								if (target.getObjectType() == Enum.GameObjectType.PlayerCharacter)
+									waterbucketBypass = true;
+								else {
+									// test distance to structure
+									Building targetBuilding = (Building) target;
+									Bounds testBounds = Bounds.borrow();
+									testBounds.setBounds(player.getLoc(), 25);
+
+									if (Bounds.collide(targetBuilding.getBounds(), testBounds, .1f) == false) {
+										ChatManager.chatSystemError(player, "Not in range of structura for to heal!");
+										return true;
+									}
+								}
+
+								// Send piss bucket animation
+
+								VisualUpdateMessage vum = new VisualUpdateMessage(player, 16323);
+								vum.configure();
+								DispatchMessage.sendToAllInRange(player, vum);
+							}
+
+							if (waterbucketBypass == false)
+								PowersManager.applyPower(player, target, Vector3fImmutable.ZERO, ib.getUseID(), ib.getUseAmount(), true);
+
+							itemMan.consume(item);
+						} else //just remove the item at this point
+							itemMan.consume(item);
+
+						dispatch = Dispatch.borrow(player, msg);
+						DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+						player.cancelOnSpell();
+						break;
+					default: //shouldn't be here, consume item
+						dispatch = Dispatch.borrow(player, msg);
+						DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+						// itemMan.consume(item);
+					}
+				}
+			} else {
+				// TODO log item does not belong to player
+				// System.out.println("Item does not belong to player");
+				// Cleanup duped item here
+			}
+		}
+
+		return true;
+	}
+
+	private static boolean claimRealm(PlayerCharacter player, int charterUUID) {
+
+		Guild guild;
+		Realm realm;
+		City city;
+		Building tol;
+		float hPMod;
+		Warehouse warehouse;
+		boolean hasResources = true;
+		int resourceValue;
+
+		if (GuildStatusController.isGuildLeader(player.getGuildStatus()) == false) {
+			ErrorPopupMsg.sendErrorPopup(player, 176); // Only guild leaders can claim a territory
+			return false;
+		}
+
+		guild = player.getGuild();
+		city = guild.getOwnedCity();
+
+		if (city == null) {
+			ErrorPopupMsg.sendErrorPopup(player, 179); // Only landed guilds may claim a territory
+			return false;
+		}
+
+		if (city.isLocationOnCityGrid(player.getLoc()) == false) {
+			ErrorPopupMsg.sendErrorPopup(player, 186); // Your tree is not inside a territory!
+			return false;
+		}
+
+		tol = city.getTOL();
+
+		if (tol.getRank() != 7) {
+			ErrorPopupMsg.sendErrorPopup(player, 181); // Your tree must be rank 7 before claiming a territory
+			return false;
+		}
+
+		realm = RealmMap.getRealmForCity(city);
+
+		if (realm.getCanBeClaimed() == false) {
+			ErrorPopupMsg.sendErrorPopup(player, 180); // This territory cannot be ruled by anyone
+			return false;
+		}
+
+		if (realm.isRuled() == true) {
+			ErrorPopupMsg.sendErrorPopup(player, 178); // This territory is already claimed
+			return false;
+		}
+
+		if (!Realm.HasAllBlessings(player)) {
+			ErrorPopupMsg.sendErrorPopup(player, 185); // You must seek the blessing of the three sages before you can rule
+			return false;
+		}
+
+		// Must have the required resources in warehouse to claim realm
+
+		warehouse = city.getWarehouse();
+
+		if (warehouse == null) {
+			ErrorPopupMsg.sendErrorPopup(player, 188);  // You must have a warehouse to become a capital
+			return false;
+		}
+
+		resourceValue = warehouse.getResources().get(Warehouse.goldIB);
+
+		if (resourceValue < 5000000)
+			hasResources = false;
+
+		resourceValue = warehouse.getResources().get(Warehouse.stoneIB);
+
+		if (resourceValue < 8000)
+			hasResources = false;
+
+		resourceValue = warehouse.getResources().get(Warehouse.lumberIB);
+
+		if (resourceValue < 8000)
+			hasResources = false;
+
+		resourceValue = warehouse.getResources().get(Warehouse.galvorIB);
+
+		if (resourceValue < 15)
+			hasResources = false;
+
+		resourceValue = warehouse.getResources().get(Warehouse.wormwoodIB);
+
+		if (resourceValue < 15)
+			hasResources = false;
+
+		if (hasResources == false) {
+			ErrorPopupMsg.sendErrorPopup(player, 184);  // Insufficient gold or resources to upgrade to capital
+			return false;
+		}
+
+		// Remove resources from warehouse before claiming realm
+
+		resourceValue = warehouse.getResources().get(Warehouse.goldIB);
+
+		if (DbManager.WarehouseQueries.updateGold(warehouse, resourceValue - 5000000) == true) {
+			warehouse.getResources().put(Warehouse.goldIB, resourceValue - 5000000);
+			warehouse.AddTransactionToWarehouse(engine.Enum.GameObjectType.Building, tol.getObjectUUID(), Enum.TransactionType.WITHDRAWL, Resource.GOLD, 5000000);
+		} else {
+			Logger.error("gold update failed for warehouse of UUID:" + warehouse.getObjectUUID());
+			return false;
+		}
+
+		resourceValue = warehouse.getResources().get(Warehouse.stoneIB);
+
+		if (DbManager.WarehouseQueries.updateStone(warehouse, resourceValue - 8000) == true) {
+			warehouse.getResources().put(Warehouse.stoneIB, resourceValue - 8000);
+			warehouse.AddTransactionToWarehouse(engine.Enum.GameObjectType.Building, tol.getObjectUUID(), Enum.TransactionType.WITHDRAWL, Resource.STONE, 8000);
+		} else {
+			Logger.error( "stone update failed for warehouse of UUID:" + warehouse.getObjectUUID());
+			return false;
+		}
+
+		resourceValue = warehouse.getResources().get(Warehouse.lumberIB);
+
+		if (DbManager.WarehouseQueries.updateLumber(warehouse, resourceValue - 8000) == true) {
+			warehouse.getResources().put(Warehouse.lumberIB, resourceValue - 8000);
+			warehouse.AddTransactionToWarehouse(engine.Enum.GameObjectType.Building, tol.getObjectUUID(), Enum.TransactionType.WITHDRAWL, Resource.LUMBER, 8000);
+		} else {
+			Logger.error("lumber update failed for warehouse of UUID:" + warehouse.getObjectUUID());
+			return false;
+		}
+
+		resourceValue = warehouse.getResources().get(Warehouse.galvorIB);
+
+		if (DbManager.WarehouseQueries.updateGalvor(warehouse, resourceValue - 15) == true) {
+			warehouse.getResources().put(Warehouse.galvorIB, resourceValue - 15);
+			warehouse.AddTransactionToWarehouse(engine.Enum.GameObjectType.Building, tol.getObjectUUID(), Enum.TransactionType.WITHDRAWL, Resource.GALVOR, 15);
+		} else {
+			Logger.error("galvor update failed for warehouse of UUID:" + warehouse.getObjectUUID());
+			return false;
+		}
+
+		resourceValue = warehouse.getResources().get(Warehouse.wormwoodIB);
+
+		if (DbManager.WarehouseQueries.updateWormwood(warehouse, resourceValue - 15) == true) {
+			warehouse.getResources().put(Warehouse.wormwoodIB, resourceValue - 15);
+			warehouse.AddTransactionToWarehouse(engine.Enum.GameObjectType.Building, tol.getObjectUUID(), Enum.TransactionType.WITHDRAWL, Resource.WORMWOOD, 15);
+		} else {
+			Logger.error("wormwood update failed for warehouse of UUID:" + warehouse.getObjectUUID());
+			return false;
+		}
+
+		realm.claimRealmForCity(city, charterUUID);
+
+		tol.setRank(8);
+		WorldGrid.updateObject(tol);
+
+		for (Building building : city.getParent().zoneBuildingSet) {
+
+			if (building.getBlueprintUUID() != 0) {
+
+				// TOL Health set through regular linear equation
+				if (building.getBlueprint().getBuildingGroup() == BuildingGroup.TOL) {
+					continue;
+				}
+
+				hPMod = (building.getMaxHitPoints() * Realm.getRealmHealthMod(city));
+				building.setMaxHitPoints(building.getMaxHitPoints() + hPMod);
+			}
+		}
+
+		if (!guild.getNation().equals(guild)) {
+			guild.getNation().setRealmsOwned(guild.getNation().getRealmsOwned() + 1);
+			GuildManager.updateAllGuildTags(guild.getNation());
+		}
+
+		guild.setRealmsOwned(guild.getRealmsOwned() + 1);
+		GuildManager.updateAllGuildTags(guild);
+
+		removeAllBlessings(player);
+
+		return true;
+
+	}
+
+	private static void removeAllBlessings(PlayerCharacter player) {
+
+		PowersBase[] powers = new PowersBase[3];
+
+		powers[0] = PowersManager.getPowerByIDString("BLS-POWER");
+		powers[1] = PowersManager.getPowerByIDString("BLS-FORTUNE");
+		powers[2] = PowersManager.getPowerByIDString("BLS-WISDOM");
+
+		for (PowersBase power : powers) {
+			PowersManager.removeEffect(player, power.getActions().get(0), true, false);
+		}
+
+	}
+	// Handle activation of tears of seadron: Removes rune from player.
+
+	private static void removeRune(PlayerCharacter pc, ClientConnection origin, int runeID) {
+
+		if (pc == null || origin == null) {
+			return;
+		}
+
+		//remove only if rune is discipline
+		if (runeID < 3001 || runeID > 3048) {
+			return;
+		}
+
+		//see if pc has rune
+		ArrayList<CharacterRune> runes = pc.getRunes();
+
+		if (runes == null)
+			return;
+
+		CharacterRune found = pc.getRune(runeID);
+
+		if (found == null)
+			return;
+
+		//TODO see if player needs to refine skills or powers first
+		//attempt remove rune from player
+
+		if (!CharacterRune.removeRune(pc, runeID))
+			return;
+
+		//update client with removed rune.
+		ApplyRuneMsg arm = new ApplyRuneMsg(pc.getObjectType().ordinal(), pc.getObjectUUID(), runeID);
+		Dispatch dispatch = Dispatch.borrow(pc, arm);
+		DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
+	}
+
+}
diff --git a/src/engine/net/client/handlers/OpenFriendsCondemnListMsgHandler.java b/src/engine/net/client/handlers/OpenFriendsCondemnListMsgHandler.java
new file mode 100644
index 00000000..27fc4083
--- /dev/null
+++ b/src/engine/net/client/handlers/OpenFriendsCondemnListMsgHandler.java
@@ -0,0 +1,397 @@
+package engine.net.client.handlers;
+
+import engine.Enum;
+import engine.Enum.DispatchChannel;
+import engine.Enum.GameObjectType;
+import engine.exception.MsgSendException;
+import engine.gameManager.BuildingManager;
+import engine.gameManager.DbManager;
+import engine.gameManager.SessionManager;
+import engine.net.Dispatch;
+import engine.net.DispatchMessage;
+import engine.net.client.ClientConnection;
+import engine.net.client.msg.ClientNetMsg;
+import engine.net.client.msg.ErrorPopupMsg;
+import engine.net.client.msg.OpenFriendsCondemnListMsg;
+import engine.objects.*;
+import org.pmw.tinylog.Logger;
+
+import java.util.ArrayList;
+
+/*
+ * @Author:
+ * @Summary: Processes application protocol message which handles
+ * client requests for various lists
+ */
+
+public class OpenFriendsCondemnListMsgHandler extends AbstractClientMsgHandler {
+
+	public OpenFriendsCondemnListMsgHandler() {
+		super(OpenFriendsCondemnListMsg.class);
+	}
+
+	@Override
+	protected boolean _handleNetMsg(ClientNetMsg baseMsg, ClientConnection origin) throws MsgSendException {
+
+		PlayerCharacter player = SessionManager.getPlayerCharacter(origin);
+		Building sourceBuilding;
+		OpenFriendsCondemnListMsg msg;
+		OpenFriendsCondemnListMsg openFriendsCondemnListMsg;
+		Enum.FriendListType friendListType;
+		Dispatch dispatch;
+
+		if (player == null)
+			return true;
+
+		msg = (OpenFriendsCondemnListMsg) baseMsg;
+		openFriendsCondemnListMsg = new OpenFriendsCondemnListMsg(msg);
+		friendListType = Enum.FriendListType.getListTypeByID(msg.getMessageType());
+		
+		if (friendListType == null){
+			Logger.error("Invalid FriendListType for messageType " + msg.getMessageType());
+			return true;
+		}
+
+		switch (friendListType) {
+		case VIEWHERALDRY: // Heraldry
+			
+			Heraldry.ValidateHeraldry(player.getObjectUUID());
+			OpenFriendsCondemnListMsg outMsg = new OpenFriendsCondemnListMsg(msg);
+			outMsg.setOrigin(origin);
+			outMsg.setMessageType(2);
+			dispatch = Dispatch.borrow(player, outMsg);
+			DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
+		break;
+		
+		case ADDHERALDRY:
+			Heraldry.ValidateHeraldry(player.getObjectUUID());
+			if (msg.getPlayerID() <= 0){
+				//ErrorPopupMsg.sendErrorMsg(player, "Invalid Heraldry Object.");
+				return true;
+			}
+			AbstractCharacter toAdd = null;
+			if (msg.getPlayerType() == GameObjectType.PlayerCharacter.ordinal())
+				toAdd = PlayerCharacter.getFromCache(msg.getPlayerID());
+			else if (msg.getPlayerType() == GameObjectType.NPC.ordinal())
+				toAdd = NPC.getFromCache(msg.getPlayerID());
+			else if (msg.getPlayerType() == GameObjectType.Mob.ordinal())
+				toAdd = Mob.getFromCache(msg.getPlayerID());
+			else{
+				ErrorPopupMsg.sendErrorMsg(player, "Invalid Heraldry Object.");
+				return true;
+			}
+			
+			if (toAdd == null){
+				ErrorPopupMsg.sendErrorMsg(player, "Invalid Heraldry Object.");
+				return true;
+			}
+			
+			Heraldry.AddToHeraldy(player.getObjectUUID(), toAdd);
+			
+			
+			 outMsg = new OpenFriendsCondemnListMsg(msg);
+			outMsg.setOrigin(origin);
+			outMsg.setMessageType(2);
+			dispatch = Dispatch.borrow(player, outMsg);
+			DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
+
+			break;
+		case REMOVEHERALDRY:
+			Heraldry.ValidateHeraldry(player.getObjectUUID());
+			Heraldry.RemoveFromHeraldy(player.getObjectUUID(), msg.getPlayerID());
+				
+			
+			 outMsg = new OpenFriendsCondemnListMsg(msg);
+				outMsg.setOrigin(origin);
+				outMsg.setMessageType(2);
+				dispatch = Dispatch.borrow(player, outMsg);
+				DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
+				
+			break;
+
+		case DEALTHS: // Death List
+			openFriendsCondemnListMsg.updateMsg(8, new ArrayList<>(player.pvpDeaths));
+			dispatch = Dispatch.borrow(player, openFriendsCondemnListMsg);
+			DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+			break;
+
+		case KILLS: // Kill List
+			openFriendsCondemnListMsg.updateMsg(10, new ArrayList<>(player.pvpKills));
+			dispatch = Dispatch.borrow(player, openFriendsCondemnListMsg);
+			DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+			break;
+
+		case VIEWCONDEMN:
+
+			sourceBuilding = BuildingManager.getBuildingFromCache(msg.getBuildingID());
+
+			if (sourceBuilding == null)
+				return true;
+
+			openFriendsCondemnListMsg = new OpenFriendsCondemnListMsg(12, sourceBuilding.getCondemned(), sourceBuilding.reverseKOS);
+			openFriendsCondemnListMsg.configure();
+			dispatch = Dispatch.borrow(player, openFriendsCondemnListMsg);
+			DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+
+			//msg.updateMsg(12, DbManager.GuildQueries.)
+			break;
+			//REMOVE CONDEMN
+		case REMOVECONDEMN:
+
+			sourceBuilding = BuildingManager.getBuildingFromCache(msg.getBuildingID());
+
+			if (sourceBuilding == null)
+				return true;
+
+			if (!BuildingManager.PlayerCanControlNotOwner(sourceBuilding, player))
+				return true;
+
+			Condemned removeCondemn = sourceBuilding.getCondemned().get(msg.getRemoveFriendID());
+
+			if (removeCondemn == null)
+				return true;
+
+			if (!DbManager.BuildingQueries.REMOVE_FROM_CONDEMNED_LIST(removeCondemn.getParent(), removeCondemn.getPlayerUID(), removeCondemn.getGuildUID(), removeCondemn.getFriendType()))
+				return true;
+
+			sourceBuilding.getCondemned().remove(msg.getRemoveFriendID());
+			dispatch = Dispatch.borrow(player, msg);
+			DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+			break;
+
+		case TOGGLEACTIVE:
+
+			sourceBuilding = BuildingManager.getBuildingFromCache(msg.getBuildingID());
+
+			if (sourceBuilding == null)
+				return true;
+
+			if (!BuildingManager.PlayerCanControlNotOwner(sourceBuilding, player))
+				return true;
+
+			Condemned condemn = sourceBuilding.getCondemned().get(msg.getRemoveFriendID());
+
+			if (condemn == null)
+				return true;
+
+			condemn.setActive(msg.isReverseKOS());
+			openFriendsCondemnListMsg.setReverseKOS(condemn.isActive());
+			dispatch = Dispatch.borrow(player, msg);
+			DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+			break;
+		case REVERSEKOS:
+
+
+			sourceBuilding = BuildingManager.getBuildingFromCache(msg.getBuildingID());
+
+			if (sourceBuilding == null)
+				return true;
+
+			if (!BuildingManager.PlayerCanControlNotOwner(sourceBuilding, player))
+				return true;
+
+			if (!sourceBuilding.setReverseKOS(msg.isReverseKOS()))
+				return true;
+			break;
+
+			//ADD GUILD CONDEMN
+		case ADDCONDEMN:
+
+			sourceBuilding = BuildingManager.getBuildingFromCache(msg.getBuildingID());
+
+			if (sourceBuilding == null)
+				return true;
+
+			if (!BuildingManager.PlayerCanControlNotOwner(sourceBuilding, player))
+				return true;
+
+			switch (msg.getInviteType()) {
+			case 2:
+
+				if (msg.getPlayerID() == 0)
+					return true;
+				
+				if (msg.getPlayerType() != GameObjectType.PlayerCharacter.ordinal())
+					return true;
+
+				PlayerCharacter playerCharacter = PlayerCharacter.getFromCache(msg.getPlayerID());
+
+				if (playerCharacter == null)
+					return true;
+
+				if (Guild.sameNationExcludeErrant(sourceBuilding.getGuild(), playerCharacter.getGuild()))
+					return true;
+
+				if (sourceBuilding.getCondemned().containsKey(playerCharacter.getObjectUUID()))
+					return true;
+
+				if (!DbManager.BuildingQueries.ADD_TO_CONDEMNLIST(sourceBuilding.getObjectUUID(), playerCharacter.getObjectUUID(), msg.getGuildID(), msg.getInviteType())) {
+					Logger.debug( "Failed to add Condemned: " + playerCharacter.getFirstName() + " to Building With UID " + sourceBuilding.getObjectUUID());
+					return true;
+				}
+
+				sourceBuilding.getCondemned().put(playerCharacter.getObjectUUID(), new Condemned(playerCharacter.getObjectUUID(), sourceBuilding.getObjectUUID(), msg.getGuildID(), msg.getInviteType()));
+				break;
+			case 4:
+				if (msg.getGuildID() == 0)
+					return true;
+
+				if (sourceBuilding.getCondemned().containsKey(msg.getGuildID()))
+					return true;
+
+				Guild condemnedGuild = Guild.getGuild(msg.getGuildID());
+
+				if (condemnedGuild == null)
+					return true;
+
+				if (!DbManager.BuildingQueries.ADD_TO_CONDEMNLIST(sourceBuilding.getObjectUUID(), msg.getPlayerID(), condemnedGuild.getObjectUUID(), msg.getInviteType())) {
+					Logger.debug("Failed to add Condemned: " + condemnedGuild.getName() + " to Building With UID " + sourceBuilding.getObjectUUID());
+					return true;
+				}
+
+				sourceBuilding.getCondemned().put(condemnedGuild.getObjectUUID(), new Condemned(msg.getPlayerID(), sourceBuilding.getObjectUUID(), condemnedGuild.getObjectUUID(), msg.getInviteType()));
+				break;
+			case 5:
+				if (msg.getNationID() == 0)
+					return true;
+
+				if (sourceBuilding.getCondemned().containsKey(msg.getNationID()))
+					return true;
+
+				Guild condemnedNation = Guild.getGuild(msg.getNationID());
+
+				if (condemnedNation == null)
+					return true;
+
+				if (!DbManager.BuildingQueries.ADD_TO_CONDEMNLIST(sourceBuilding.getObjectUUID(), msg.getPlayerID(), condemnedNation.getObjectUUID(), msg.getInviteType())) {
+					Logger.debug( "Failed to add Condemned: " + condemnedNation.getName() + " to Building With UID " + sourceBuilding.getObjectUUID());
+					return true;
+				}
+
+				sourceBuilding.getCondemned().put(condemnedNation.getObjectUUID(), new Condemned(msg.getPlayerID(), sourceBuilding.getObjectUUID(), condemnedNation.getObjectUUID(), msg.getInviteType()));
+				break;
+
+			}
+
+			openFriendsCondemnListMsg = new OpenFriendsCondemnListMsg(12, sourceBuilding.getCondemned(), sourceBuilding.reverseKOS);
+			openFriendsCondemnListMsg.configure();
+			dispatch = Dispatch.borrow(player, openFriendsCondemnListMsg);
+			DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+			break;
+
+			//ADD FRIEND BUILDING
+		case ADDFRIEND:
+			sourceBuilding =  BuildingManager.getBuildingFromCache(msg.getBuildingID());
+
+			if (sourceBuilding == null)
+				return true;
+
+			if (msg.getGuildID() == 0)
+				return true;
+
+			if (!BuildingManager.PlayerCanControlNotOwner(sourceBuilding, player))
+				return true;
+			
+			PlayerCharacter playerCharacter = null;
+
+		
+
+			Guild guildInvited = Guild.getGuild(msg.getGuildID());
+
+			if (guildInvited == null)
+				return true;
+
+			//Check to see if the invited is already on the friends list.
+			switch (msg.getInviteType()) {
+			case 7:
+				playerCharacter = PlayerCharacter.getFromCache(msg.getPlayerID());
+				if (playerCharacter == null)
+					return true;
+				if (sourceBuilding.getFriends().containsKey(playerCharacter.getObjectUUID()))
+					return true;
+				break;
+			case 8:
+			case 9:
+				if (sourceBuilding.getFriends().containsKey(guildInvited.getObjectUUID()))
+					return true;
+				break;
+			}
+
+			if (!DbManager.BuildingQueries.ADD_TO_FRIENDS_LIST(sourceBuilding.getObjectUUID(), msg.getPlayerID(), guildInvited.getObjectUUID(), msg.getInviteType())) {
+				Logger.debug( "Failed to add Friend: " + playerCharacter.getFirstName() + " to Building With UID " + sourceBuilding.getObjectUUID());
+				return true;
+			}
+
+			switch (msg.getInviteType()) {
+			case 7:
+				sourceBuilding.getFriends().put(playerCharacter.getObjectUUID(), new BuildingFriends(playerCharacter.getObjectUUID(), sourceBuilding.getObjectUUID(), playerCharacter.getGuild().getObjectUUID(), 7));
+				break;
+			case 8:
+				sourceBuilding.getFriends().put(guildInvited.getObjectUUID(), new BuildingFriends(msg.getPlayerID(), sourceBuilding.getObjectUUID(), guildInvited.getObjectUUID(), 8));
+				break;
+			case 9:
+				sourceBuilding.getFriends().put(guildInvited.getObjectUUID(), new BuildingFriends(msg.getPlayerID(), sourceBuilding.getObjectUUID(), guildInvited.getObjectUUID(), 9));
+				break;
+			}
+
+			openFriendsCondemnListMsg = new OpenFriendsCondemnListMsg(26, sourceBuilding.getFriends());
+			openFriendsCondemnListMsg.configure();
+
+			dispatch = Dispatch.borrow(player, openFriendsCondemnListMsg);
+			DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+			break;
+			//REMOVE from friends list.
+		case REMOVEFRIEND:
+			sourceBuilding = BuildingManager.getBuildingFromCache(msg.getBuildingID());
+
+			if (sourceBuilding == null)
+				return true;
+
+			if (!BuildingManager.PlayerCanControlNotOwner(sourceBuilding, player))
+				return true;
+
+			BuildingFriends friend = sourceBuilding.getFriends().get(msg.getRemoveFriendID());
+
+			if (friend == null)
+				return true;
+
+			if (!DbManager.BuildingQueries.REMOVE_FROM_FRIENDS_LIST(sourceBuilding.getObjectUUID(), friend.getPlayerUID(), friend.getGuildUID(), friend.getFriendType())) {
+				Logger.debug( "Failed to remove Friend: " + msg.getRemoveFriendID() + " from Building With UID " + sourceBuilding.getObjectUUID());
+				return true;
+			}
+			sourceBuilding.getFriends().remove(msg.getRemoveFriendID());
+
+			openFriendsCondemnListMsg = new OpenFriendsCondemnListMsg(26, sourceBuilding.getFriends());
+			openFriendsCondemnListMsg.configure();
+			dispatch = Dispatch.borrow(player, openFriendsCondemnListMsg);
+			DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+
+			break;
+			//view Friends
+		case VIEWFRIENDS:
+
+			Building building = BuildingManager.getBuildingFromCache(msg.getBuildingID());
+
+			if (building == null)
+				return true;
+
+			if (!BuildingManager.PlayerCanControlNotOwner(building, player))
+				return true;
+
+			//this message is sent twice back?????
+
+			openFriendsCondemnListMsg = new OpenFriendsCondemnListMsg(26, building.getFriends());
+			openFriendsCondemnListMsg.configure();
+
+
+			dispatch = Dispatch.borrow(player, openFriendsCondemnListMsg);
+			DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+			break;
+
+		default:
+			break;
+		}
+		return false;
+	}
+
+}
\ No newline at end of file
diff --git a/src/engine/net/client/handlers/OrderNPCMsgHandler.java b/src/engine/net/client/handlers/OrderNPCMsgHandler.java
new file mode 100644
index 00000000..3335727c
--- /dev/null
+++ b/src/engine/net/client/handlers/OrderNPCMsgHandler.java
@@ -0,0 +1,575 @@
+package engine.net.client.handlers;
+
+import engine.Enum.DispatchChannel;
+import engine.Enum.GameObjectType;
+import engine.Enum.ProfitType;
+import engine.exception.MsgSendException;
+import engine.gameManager.BuildingManager;
+import engine.gameManager.DbManager;
+import engine.gameManager.SessionManager;
+import engine.math.FastMath;
+import engine.math.Vector3fImmutable;
+import engine.net.Dispatch;
+import engine.net.DispatchMessage;
+import engine.net.client.ClientConnection;
+import engine.net.client.msg.*;
+import engine.objects.*;
+import org.pmw.tinylog.Logger;
+
+import java.util.ArrayList;
+
+/*
+ * @Author:
+ * @Summary: Processes application protocol message which keeps
+ * client's tcp connection open.
+ */
+public class OrderNPCMsgHandler extends AbstractClientMsgHandler {
+
+    // Constants used for incoming message type
+
+    private static final int CLIENT_UPGRADE_REQUEST = 3;
+    private static final int CLIENT_REDEED_REQUEST = 6;
+    private static final int SVR_CLOSE_WINDOW = 4;
+
+    public OrderNPCMsgHandler() {
+        super(OrderNPCMsg.class);
+    }
+
+    @Override
+    protected boolean _handleNetMsg(ClientNetMsg baseMsg, ClientConnection origin) throws MsgSendException {
+
+        // Member variable declarations
+
+        PlayerCharacter player;
+        NPC npc;
+        Mob mob;
+        Building building;
+        OrderNPCMsg orderNPCMsg;
+        ManageCityAssetsMsg outMsg;
+
+        // Member variable assignment
+        orderNPCMsg = (OrderNPCMsg) baseMsg;
+
+        if (origin.ordernpcspam > System.currentTimeMillis())
+            return true;
+
+        origin.ordernpcspam = System.currentTimeMillis() + 500;
+
+        player = SessionManager.getPlayerCharacter(origin);
+
+        if (player == null)
+            return true;
+
+        if (orderNPCMsg.getActionType() == 28) {
+            OrderNPCMsgHandler.handleCityCommand(orderNPCMsg, player);
+            return true;
+        }
+
+        if (orderNPCMsg.getObjectType() == GameObjectType.NPC.ordinal()) {
+
+            npc = NPC.getFromCache(orderNPCMsg.getNpcUUID());
+
+            if (npc == null)
+                return true;
+
+            building = BuildingManager.getBuildingFromCache(orderNPCMsg.getBuildingUUID());
+
+            if (building == null)
+                return true;
+
+            if (building.getHirelings().containsKey(npc) == false)
+                return true;
+
+
+            if (player.getCharItemManager().getTradingWith() != null) {
+                ErrorPopupMsg.sendErrorMsg(player, "Cannot barter and trade with same timings.");
+                return true;
+            }
+
+            player.lastBuildingAccessed = building.getObjectUUID();
+
+            switch (orderNPCMsg.getActionType()) {
+
+                case 2:
+                    player = SessionManager.getPlayerCharacter(origin);
+
+                    if (ManageCityAssetMsgHandler.playerCanManageNotFriends(player, building) == false)
+                        return true;
+
+                    if (building.getHirelings().containsKey(npc) == false)
+                        return true;
+
+                    if (npc.remove() == false) {
+                        PlaceAssetMsg.sendPlaceAssetError(player.getClientConnection(), 1, "A Serious error has occurred. Please post details for to ensure transaction integrity");
+                        return true;
+                    }
+
+                    ManageCityAssetsMsg manageCityAssetsMsg = new ManageCityAssetsMsg();
+                    manageCityAssetsMsg.actionType = SVR_CLOSE_WINDOW;
+                    manageCityAssetsMsg.setTargetType(building.getObjectType().ordinal());
+                    manageCityAssetsMsg.setTargetID(building.getObjectUUID());
+
+                    Dispatch dispatch = Dispatch.borrow(player, manageCityAssetsMsg);
+                    DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
+
+                    return true;
+
+                case CLIENT_UPGRADE_REQUEST:
+
+                    if (BuildingManager.playerCanManage(player, building) == false)
+                        return true;
+
+                    processUpgradeNPC(player, npc);
+
+                    outMsg = new ManageCityAssetsMsg(player, building);
+
+                    // Action TYPE
+                    outMsg.actionType = 3;
+                    outMsg.setTargetType(building.getObjectType().ordinal());
+                    outMsg.setTargetID(building.getObjectUUID());
+                    outMsg.setTargetType3(building.getObjectType().ordinal());
+                    outMsg.setTargetID3(building.getObjectUUID());
+                    outMsg.setAssetName1(building.getName());
+                    outMsg.setUnknown54(1);
+
+                    dispatch = Dispatch.borrow(player, outMsg);
+                    DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
+
+                    break;
+                case CLIENT_REDEED_REQUEST:
+
+                    if (BuildingManager.PlayerCanControlNotOwner(building, player) == false)
+                        return true;
+
+                    processRedeedNPC(npc, building, origin);
+                    return true;
+                //MB TODO HANDLE all profits.
+                case 7:
+                case 8:
+                case 9:
+
+                    if (BuildingManager.PlayerCanControlNotOwner(building, player) == false)
+                        return true;
+
+                    modifySellProfit(orderNPCMsg, origin);
+                    dispatch = Dispatch.borrow(player, orderNPCMsg);
+                    DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
+                    return true;
+                case 10:
+                case 11:
+                case 12:
+
+                    if (BuildingManager.PlayerCanControlNotOwner(building, player) == false)
+                        return true;
+
+                    modifyBuyProfit(orderNPCMsg, origin);
+                    dispatch = Dispatch.borrow(player, orderNPCMsg);
+                    DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
+                    return true;
+            }
+
+            // Validation check Owner or IC or friends
+            if (BuildingManager.PlayerCanControlNotOwner(building, player) == false)
+                if (BuildingManager.playerCanManage(player, building) == false)
+                    return true;
+
+            ManageNPCMsg manageNPCMsg = new ManageNPCMsg(npc);
+            Dispatch dispatch = Dispatch.borrow(player, manageNPCMsg);
+            DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
+            return true;
+
+        } else if (orderNPCMsg.getObjectType() == GameObjectType.Mob.ordinal()) {
+
+            mob = Mob.getFromCacheDBID(orderNPCMsg.getNpcUUID());
+
+            if (mob == null)
+                return true;
+
+            building = BuildingManager.getBuildingFromCache(orderNPCMsg.getBuildingUUID());
+
+            if (building == null)
+                return true;
+
+            if (!building.getHirelings().containsKey(mob))
+                return true;
+
+            if (player.getCharItemManager().getTradingWith() != null) {
+                ErrorPopupMsg.sendErrorMsg(player, "Cannot barter and trade with same timings.");
+                return true;
+            }
+
+            player.lastBuildingAccessed = building.getObjectUUID();
+
+            switch (orderNPCMsg.getActionType()) {
+                case 2:
+
+                    if (BuildingManager.playerCanManage(player, building) == false)
+                        return true;
+
+                    if (building.getHirelings().containsKey(mob) == false)
+                        return true;
+
+                    if (mob.remove(building) == false) {
+                        PlaceAssetMsg.sendPlaceAssetError(player.getClientConnection(), 1, "A Serious error has occurred. Please post details for to ensure transaction integrity");
+                        return true;
+                    }
+
+                    ManageCityAssetsMsg manageCityAssetsMsg = new ManageCityAssetsMsg();
+                    manageCityAssetsMsg.actionType = SVR_CLOSE_WINDOW;
+                    manageCityAssetsMsg.setTargetType(building.getObjectType().ordinal());
+                    manageCityAssetsMsg.setTargetID(building.getObjectUUID());
+                    Dispatch dispatch = Dispatch.borrow(player, manageCityAssetsMsg);
+                    DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
+                    break;
+                case 3:
+
+                    if (BuildingManager.PlayerCanControlNotOwner(building, player) == false)
+                        return true;
+
+                    processUpgradeNPC(player, mob);
+
+                    outMsg = new ManageCityAssetsMsg(player, building);
+
+                    // Action TYPE
+                    outMsg.actionType = 3;
+                    outMsg.setTargetType(building.getObjectType().ordinal());
+                    outMsg.setTargetID(building.getObjectUUID());
+                    outMsg.setTargetType3(building.getObjectType().ordinal());
+                    outMsg.setTargetID3(building.getObjectUUID());
+                    outMsg.setAssetName1(building.getName());
+                    outMsg.setUnknown54(1);
+
+                    dispatch = Dispatch.borrow(player, outMsg);
+                    DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
+                    break;
+                case 6:
+
+                    if (BuildingManager.PlayerCanControlNotOwner(building, player) == false)
+                        return true;
+
+                    processRedeedNPC(mob, building, origin);
+                    return true;
+                //MB TODO HANDLE all profits.
+                case 7:
+                case 8:
+                case 9:
+                    break;
+                case 10:
+                case 11:
+                case 12:
+                    break;
+            }
+
+            // Validation check Owner or IC
+            if (BuildingManager.PlayerCanControlNotOwner(building, player) == false)
+                if (BuildingManager.playerCanManage(player, building) == false)
+                    return true;
+
+            ManageNPCMsg manageNPCMsg = new ManageNPCMsg(mob);
+            Dispatch dispatch = Dispatch.borrow(player, manageNPCMsg);
+            DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
+            return true;
+        }
+        return true;
+    }
+
+    private static void modifyBuyProfit(OrderNPCMsg msg, ClientConnection origin) {
+        NPC npc;
+        PlayerCharacter player;
+        Building building;
+        float percent;
+
+        ProfitType profitType = null;
+        player = origin.getPlayerCharacter();
+
+        if (player == null)
+            return;
+
+        npc = NPC.getFromCache(msg.getNpcUUID());
+
+        if (npc == null)
+            return;
+
+        building = npc.getBuilding();
+
+        if (building == null)
+            return;
+
+        NPCProfits profit = NPC.GetNPCProfits(npc);
+
+        if (profit == null)
+            return;
+
+        switch (msg.getActionType()) {
+            case 10:
+                profitType = ProfitType.BuyNormal;
+                break;
+            case 11:
+                profitType = ProfitType.BuyGuild;
+                break;
+            case 12:
+                profitType = ProfitType.BuyNation;
+        }
+
+        percent = msg.getBuySellPercent();
+        percent = FastMath.clamp(percent, 0.0f, 1.0f);
+
+        NPCProfits.UpdateProfits(npc, profit, profitType, percent);
+    }
+
+    private static void modifySellProfit(OrderNPCMsg orderNPCMsg, ClientConnection origin) {
+        NPC npc;
+        PlayerCharacter player;
+        Building building;
+        float percent;
+
+        ProfitType profitType = null;
+
+        player = origin.getPlayerCharacter();
+
+        if (player == null)
+            return;
+
+        npc = NPC.getFromCache(orderNPCMsg.getNpcUUID());
+
+        if (npc == null)
+            return;
+
+        building = npc.getBuilding();
+
+        if (building == null)
+            return;
+
+        NPCProfits profit = NPC.GetNPCProfits(npc);
+
+        if (profit == null)
+            return;
+
+        switch (orderNPCMsg.getActionType()) {
+            case 7:
+                profitType = ProfitType.SellNormal;
+                break;
+            case 8:
+                profitType = ProfitType.SellGuild;
+                break;
+            case 9:
+                profitType = ProfitType.SellNation;
+        }
+
+        percent = orderNPCMsg.getBuySellPercent();
+
+        percent -= 1f;
+        percent = FastMath.clamp(percent, 0.0f, 3.0f);
+
+        NPCProfits.UpdateProfits(npc, profit, profitType, percent);
+    }
+
+    private static void handleCityCommand(OrderNPCMsg orderNpcMsg, PlayerCharacter player) {
+
+        Building building = BuildingManager.getBuildingFromCache(orderNpcMsg.getBuildingUUID());
+
+        if (building == null)
+            return;
+
+        if (ManageCityAssetMsgHandler.playerCanManageNotFriends(player, building) == false)
+            return;
+
+        if (orderNpcMsg.getPatrolSize() >= 20)
+            Logger.info(player.getName() + " is attempting to add patrol points amount " + orderNpcMsg.getPatrolSize());
+
+        if (orderNpcMsg.getSentrySize() >= 20)
+            Logger.info(player.getName() + " is attempting to add patrol points amount " + orderNpcMsg.getSentryPoints());
+
+        if (orderNpcMsg.getPatrolPoints() != null) {
+
+           if ( !AddPatrolPoints(building.getObjectUUID(), orderNpcMsg.getPatrolPoints())){
+        	   ErrorPopupMsg.sendErrorMsg(player, "Patrol Points must be placed on city zone.");
+        	   return;
+           }
+
+            for (AbstractCharacter guard : building.getHirelings().keySet()) {
+                if (guard.getObjectType() == GameObjectType.Mob)
+                    ((Mob) guard).setPatrolPointIndex(0);
+            }
+        } else if (building.getPatrolPoints() != null)
+            ClearPatrolPoints(building.getObjectUUID());
+
+        if (orderNpcMsg.getSentryPoints() != null) {
+            AddSentryPoints(building.getObjectUUID(), orderNpcMsg.getSentryPoints());
+        } else if (building.getSentryPoints() != null)
+            ClearSentryPoints(building.getObjectUUID());
+
+        //		Dispatch dispatch = Dispatch.borrow(pc, msg);
+        //		DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
+
+    }
+
+    private static void processUpgradeNPC(PlayerCharacter player, AbstractCharacter abstractCharacter) {
+
+        Building building;
+
+        switch (abstractCharacter.getObjectType()) {
+
+            case NPC:
+                NPC npc = (NPC) abstractCharacter;
+                building = npc.getBuilding();
+
+                // Cannot upgrade an npc not within a building
+
+                if (building == null)
+                    return;
+
+                City buildingCity = building.getCity();
+
+                if (buildingCity == null) {
+                    npc.processUpgradeNPC(player);
+                    return;
+                }
+
+                buildingCity.transactionLock.writeLock().lock();
+
+                try {
+                    npc.processUpgradeNPC(player);
+                } catch (Exception e) {
+                    Logger.error(e);
+                } finally {
+                    buildingCity.transactionLock.writeLock().unlock();
+                }
+                break;
+            case Mob:
+
+                Mob mob = (Mob) abstractCharacter;
+                building = mob.getBuilding();
+
+                if (mob.getBuilding() == null)
+                    return;
+
+                City mobCity = building.getCity();
+
+                if (mobCity == null) {
+                    mob.processUpgradeMob(player);
+                    return;
+                }
+
+                mobCity.transactionLock.writeLock().lock();
+
+                try {
+                    mob.processUpgradeMob(player);
+                } catch (Exception e) {
+                    // TODO Auto-generated catch block
+                    Logger.error(e);
+                } finally {
+                    mobCity.transactionLock.writeLock().unlock();
+                }
+                break;
+        }
+    }
+
+    private synchronized void processRedeedNPC(AbstractCharacter abstractCharacter, Building building, ClientConnection origin) {
+
+        // Member variable declaration
+
+        switch (abstractCharacter.getObjectType()) {
+            case NPC:
+                NPC npc = (NPC) abstractCharacter;
+
+                Building cityBuilding = npc.getBuilding();
+
+                if (cityBuilding == null)
+                    return;
+
+                npc.processRedeedNPC(origin);
+                break;
+            case Mob:
+                Mob mob = (Mob) abstractCharacter;
+                mob.processRedeedMob(origin);
+                break;
+        }
+    }
+
+    private static boolean AddPatrolPoints(int buildingID, ArrayList<Vector3fImmutable> patrolPoints) {
+
+        Building building = BuildingManager.getBuildingFromCache(buildingID);
+
+        if (building == null)
+            return false;
+        
+        Zone zone = building.getParentZone();
+        
+        if (zone == null)
+        	return false;
+        
+        if (zone.getPlayerCityUUID() == 0)
+        	return false;
+        
+        City city = building.getCity();
+        
+        if (city == null)
+        	return false;
+        
+        
+
+        //clear first.
+        
+        for (Vector3fImmutable point : patrolPoints) {
+        	
+        	if (city.isLocationOnCityZone(point) == false){
+      		return false;
+        	}
+
+        }
+
+        DbManager.BuildingQueries.CLEAR_PATROL(buildingID);
+
+        for (Vector3fImmutable point : patrolPoints) {
+        	
+            if (!DbManager.BuildingQueries.ADD_TO_PATROL(buildingID, point))
+                return false;
+        }
+        building.patrolPoints = patrolPoints;
+        return true;
+    }
+
+    private static boolean AddSentryPoints(int buildingID, ArrayList<Vector3fImmutable> sentryPoints) {
+
+        Building building = BuildingManager.getBuildingFromCache(buildingID);
+
+        if (building == null)
+            return false;
+
+        building.sentryPoints = sentryPoints;
+        return true;
+    }
+
+    private static boolean ClearPatrolPoints(int buildingID) {
+
+        Building building = BuildingManager.getBuildingFromCache(buildingID);
+
+        if (building == null)
+            return false;
+
+        if (building.patrolPoints == null)
+            return true;
+
+        if (DbManager.BuildingQueries.CLEAR_PATROL(buildingID) == false)
+            return false;
+
+        building.patrolPoints.clear();
+        return true;
+    }
+
+    private static boolean ClearSentryPoints(int buildingID) {
+
+        Building building = BuildingManager.getBuildingFromCache(buildingID);
+
+        if (building == null)
+            return false;
+
+        if (building.sentryPoints == null)
+            return true;
+
+        building.sentryPoints.clear();
+        return true;
+    }
+
+}
\ No newline at end of file
diff --git a/src/engine/net/client/handlers/PlaceAssetMsgHandler.java b/src/engine/net/client/handlers/PlaceAssetMsgHandler.java
new file mode 100644
index 00000000..6b4c1c84
--- /dev/null
+++ b/src/engine/net/client/handlers/PlaceAssetMsgHandler.java
@@ -0,0 +1,1380 @@
+package engine.net.client.handlers;
+
+import engine.Enum;
+import engine.Enum.*;
+import engine.InterestManagement.HeightMap;
+import engine.InterestManagement.RealmMap;
+import engine.InterestManagement.WorldGrid;
+import engine.db.archive.CityRecord;
+import engine.db.archive.DataWarehouse;
+import engine.exception.MsgSendException;
+import engine.gameManager.*;
+import engine.math.Bounds;
+import engine.math.Vector3fImmutable;
+import engine.net.Dispatch;
+import engine.net.DispatchMessage;
+import engine.net.client.ClientConnection;
+import engine.net.client.msg.CityZoneMsg;
+import engine.net.client.msg.ClientNetMsg;
+import engine.net.client.msg.ErrorPopupMsg;
+import engine.net.client.msg.PlaceAssetMsg;
+import engine.net.client.msg.PlaceAssetMsg.PlacementInfo;
+import engine.objects.*;
+import engine.server.MBServerStatics;
+import org.joda.time.DateTime;
+import org.pmw.tinylog.Logger;
+
+import java.time.LocalDateTime;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+/*
+ * @Summary: Processes application protocol message which requests
+ *  creation of new city / buildings from seeds/deeds in inventory.
+ */
+public class PlaceAssetMsgHandler extends AbstractClientMsgHandler {
+
+	// Useful constants
+	// ActionType 1 = client request
+	//            2 = Server confirms open window
+	//            3 = Request to place asset
+	//            4 = Server confirms/close window
+	private static final int CLIENTREQ_UNKNOWN = 1;
+	private static final int SERVER_OPENWINDOW = 2;
+	private static final int CLIENTREQ_NEWBUILDING = 3;  // Request to place asset
+	private static final int SERVER_CLOSEWINDOW = 4;
+
+	private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
+
+	public PlaceAssetMsgHandler() {
+
+		super(PlaceAssetMsg.class);
+
+	}
+
+	@Override
+	protected boolean _handleNetMsg(ClientNetMsg baseMsg, ClientConnection origin) throws MsgSendException {
+
+		// Member variable declaration
+
+		PlaceAssetMsg msg;
+		Boolean buildingCreated;
+
+		// Character location and session
+
+		PlayerCharacter playerCharacter;
+		PlacementInfo buildingList;
+		Blueprint buildingBlueprint;
+
+		// Tell compiler it's ok to trust us and parse
+		// what we need from the message structure
+
+		msg = (PlaceAssetMsg) baseMsg;
+
+		// Action type 3 is a client requesting to place an object
+		// For all other action types let's just early exit
+
+		if (msg.getActionType() != CLIENTREQ_NEWBUILDING)
+			return true;
+
+		// assign our character
+
+		playerCharacter = SessionManager.getPlayerCharacter(origin);
+
+		// We need to figure out what exactly the player is attempting
+		// to place, as some objects like tol/bane/walls are edge cases.
+		// So let's get the first item in their list.
+
+		buildingList = msg.getFirstPlacementInfo();
+
+		// Early exit if null building list.
+
+		if (buildingList == null) {
+			Logger.error("Player " + playerCharacter.getCombinedName()
+			+ " null building list on deed use");
+			PlaceAssetMsg.sendPlaceAssetError(origin, 1, "A Serious error has occurred. Please post details for to ensure transaction integrity");
+			closePlaceAssetWindow(origin);
+			return true;
+		}
+
+		Item contract = null;
+
+		for (Item inventoryItem : playerCharacter.getInventory()) {
+			if (inventoryItem.getItemBase().getUseID() == buildingList.getBlueprintUUID()) {
+				contract = inventoryItem;
+				break;
+			}
+		}
+
+		// Grab the blueprint from the uuid in the message
+
+		buildingBlueprint = Blueprint.getBlueprint(buildingList.getBlueprintUUID());
+
+		// Early exit if blueprint can't be retrieved for the object.
+
+		if (buildingBlueprint == null) {
+			Logger.error("Player " + playerCharacter.getCombinedName()
+			+ " null blueprint UUID: " + buildingList.getBlueprintUUID() + " on deed use");
+			PlaceAssetMsg.sendPlaceAssetError(origin, 1, "A Serious error has occurred. Please post details for to ensure transaction integrity");
+			closePlaceAssetWindow(origin);
+			return true;
+		}
+
+		// Let's now attempt to place the building
+
+		buildingCreated = false;
+
+		// Many buildings have particular validation and
+		// post-creation cleanup requirements.
+
+		boolean close = true;
+		lock.writeLock().lock();
+
+		try {
+			switch (buildingBlueprint.getBuildingGroup()) {
+
+			case TOL:
+				if (contract == null)
+					break;
+				buildingCreated = placeTreeOfLife(playerCharacter, origin, msg);
+				break;
+			case WAREHOUSE:
+				if (contract == null)
+					break;
+				if (!playerCharacter.getCharItemManager().doesCharOwnThisItem(contract.getObjectUUID()))
+					break;
+				buildingCreated = placeWarehouse(playerCharacter, origin, msg);
+				break;
+			case SIEGETENT:
+			case BULWARK:
+				if (contract == null)
+					break;
+				if (!playerCharacter.getCharItemManager().doesCharOwnThisItem(contract.getObjectUUID()))
+					break;
+				buildingCreated = placeSiegeEquip(playerCharacter, origin, msg);
+				break;
+			case SPIRE:
+				if (contract == null)
+					break;
+				if (!playerCharacter.getCharItemManager().doesCharOwnThisItem(contract.getObjectUUID()))
+					break;
+				buildingCreated = placeSpire(playerCharacter, origin, msg);
+				break;
+			case SHRINE:
+				if (contract == null)
+					break;
+				if (!playerCharacter.getCharItemManager().doesCharOwnThisItem(contract.getObjectUUID()))
+					break;
+				buildingCreated = placeShrine(playerCharacter, origin, msg);
+				break;
+			case BARRACK:
+				if (contract == null)
+					break;
+				if (!playerCharacter.getCharItemManager().doesCharOwnThisItem(contract.getObjectUUID()))
+					break;
+				buildingCreated = placeBarrack(playerCharacter, origin, msg);
+				break;
+			case WALLSTRAIGHT:
+			case WALLCORNER:
+			case SMALLGATE:
+			case ARTYTOWER:
+			case WALLSTAIRS:
+				buildingCreated = placeCityWalls(playerCharacter, origin, msg);
+				close = false;
+				break;
+			default:
+				if (contract == null)
+					break;
+				if (!playerCharacter.getCharItemManager().doesCharOwnThisItem(contract.getObjectUUID()))
+					break;
+				buildingCreated = placeSingleBuilding(playerCharacter, origin, msg);
+				break;
+			}
+		} catch (Exception e) {
+			Logger.error("PlaceAssetHandler", e.getMessage());
+			e.printStackTrace();
+		} finally {
+			lock.writeLock().unlock();
+		}
+
+		// Update the player's last contract (What is this used for?)
+
+		playerCharacter.setLastContract(msg.getContractID());
+
+		// Remove the appropiate deed.
+		if (buildingCreated == true)
+			if (contract != null) {
+				playerCharacter.getCharItemManager().delete(contract);
+				playerCharacter.getCharItemManager().updateInventory();
+			}
+
+		// Close the window.  We're done!
+		//DONT CLOSE THE WINDOW IF WALL KTHANX
+
+		if (close)
+			closePlaceAssetWindow(origin);
+		return true;
+	}
+
+	// Default method: Validates and places all buildings that do not
+	//  require special treatment in some fashion.
+
+	private boolean placeSingleBuilding(PlayerCharacter playerCharacter, ClientConnection origin, PlaceAssetMsg msg) {
+
+		PlacementInfo buildingList;
+		Zone serverZone;
+
+		// Retrieve the building details we're placing
+
+		buildingList = msg.getFirstPlacementInfo();
+
+		serverZone = ZoneManager.findSmallestZone(buildingList.getLoc());
+
+		// Early exit if something went horribly wrong
+		// with locating the current or zone
+
+		if (serverZone == null) {
+			Logger.error("Null zone in placeSingleBuilding");
+			return false;
+		}
+
+		// Method checks validation conditions arising when placing
+		// buildings.  Player must be on a city grid, must be
+		// inner council of the city's guild, etc.
+
+		if (validateBuildingPlacement(serverZone, msg, origin, playerCharacter, buildingList) == false)
+			return false; // Close window here?
+
+		// Place the building
+		if (createStructure(playerCharacter, buildingList, serverZone) == null) {
+			PlaceAssetMsg.sendPlaceAssetError(origin, 1, "A Serious error has occurred. Please post details for to ensure transaction integrity");
+			return false;
+		}
+
+		return true;
+	}
+
+	private boolean placeWarehouse(PlayerCharacter player, ClientConnection origin, PlaceAssetMsg msg) {
+
+		Zone serverZone;
+		City cityObject;
+		PlacementInfo buildingList;
+
+		// Retrieve the building details we're placing
+
+		buildingList = msg.getFirstPlacementInfo();
+
+		// Setup working variables we'll need
+
+		serverZone = ZoneManager.findSmallestZone(buildingList.getLoc());
+
+		// Early exit if something went horribly wrong
+
+		if (serverZone == null)
+			return false;
+
+		cityObject = City.getCity(serverZone.getPlayerCityUUID());
+
+		// Early exit if something went horribly wrong
+
+		if (cityObject == null)
+			return false;
+
+		// Method checks validation conditions arising when placing
+		// buildings.  Player must be on a city grid, must be
+		// inner council of the city's guild, etc.
+
+		if (validateCityBuildingPlacement(serverZone, msg, origin, player, buildingList) == false)
+			return false;
+
+		if (cityObject.getWarehouse() != null) {
+			PlaceAssetMsg.sendPlaceAssetError(origin, 50, "");  //"You can only have one warehouse"
+			return false;
+		}
+
+		// Create the warehouse object and it's entry in the database
+
+		if (createWarehouse(player, msg.getFirstPlacementInfo(), serverZone) == false) {
+			PlaceAssetMsg.sendPlaceAssetError(origin, 1, "A Serious error has occurred. Please post details for to ensure transaction integrity");
+			return false;
+		}
+
+		return true;
+	}
+
+	private boolean placeSiegeEquip(PlayerCharacter player, ClientConnection origin, PlaceAssetMsg msg) {
+
+		Zone serverZone;
+		Building siegeBuilding;
+		PlacementInfo buildingList;
+		City serverCity;
+		int numAttackerBuildings = 0;
+		int numDefenderBuildings = 0;
+
+		// Retrieve the building details we're placing
+
+		buildingList = msg.getFirstPlacementInfo();
+
+		// Setup working variables we'll need
+
+		serverZone = ZoneManager.findSmallestZone(buildingList.getLoc());
+
+		// Early exit if something went horribly wrong
+		// with locating the current city and/or zone
+
+		if (serverZone == null) {
+			Logger.error("Error obtaining reference to zone");
+			return false;
+		}
+
+		// Checks validation conditions arising when placing
+		// generic structures.
+
+		if (validateBuildingPlacement(serverZone, msg, origin, player, buildingList) == false)
+			return false;
+
+		// If there is a bane placed, only the attackers and defenders can
+		// place siege assets
+
+		serverCity = ZoneManager.getCityAtLocation(buildingList.getLoc());
+
+		//no city found
+		//check if attacker city.
+		if (serverCity == null){
+			Bane bane = Bane.getBaneByAttackerGuild(player.getGuild());
+			City attackerCity = null;
+			if (bane != null)
+				attackerCity = bane.getCity();
+			
+			if (attackerCity != null)
+				if (buildingList.getLoc().isInsideCircle(attackerCity.getLoc(), Enum.CityBoundsType.SIEGE.extents))
+				serverCity = attackerCity;
+		}
+		//no city found for attacker city,
+		//check if defender city
+		
+		if (serverCity == null){
+			if (player.getGuild().getOwnedCity() != null)
+				if (buildingList.getLoc().isInsideCircle(player.getGuild().getOwnedCity().getLoc(), Enum.CityBoundsType.SIEGE.extents))
+					serverCity = player.getGuild().getOwnedCity();
+		}
+		
+		
+
+		if ((serverCity != null) &&
+		    (serverCity.getBane() != null)) {
+
+			// Set the server zone to the city zone in order to account for being inside
+			// the siege bounds buffer area
+
+			serverZone = serverCity.getParent();
+
+				if ((player.getGuild().equals(serverCity.getBane().getOwner().getGuild()) == false)
+						&& (player.getGuild().equals(serverCity.getGuild()) == false)) {
+					PlaceAssetMsg.sendPlaceAssetError(origin, 54, ""); // Must belong to attacker or defender
+					return false;
+				}
+		}
+		
+		// cant place siege equipment off city zone.
+		
+		
+		// Create the siege Building
+
+				siegeBuilding = createStructure(player, msg.getFirstPlacementInfo(), serverZone);
+				if (serverCity == null)
+					return true;
+				// Oops something went really wrong
+
+				if (siegeBuilding == null)
+					return false;
+
+				
+				if (serverCity.getBane() == null)
+					return true;
+				
+		// If there is an bane placed, we protect 2x the stone rank's worth of attacker assets
+		// and 1x the tree rank's worth of assets automatically
+				
+				HashSet<AbstractWorldObject> awoList = WorldGrid.getObjectsInRangePartial(serverCity, 1000, MBServerStatics.MASK_BUILDING);
+
+				
+				
+		for (AbstractWorldObject awo : awoList) {
+			Building building = (Building)awo;
+			
+			if (building.getBlueprint() != null)
+				if (!building.getBlueprint().isSiegeEquip())
+					continue;
+
+			if (!building.getLoc().isInsideCircle(serverCity.getLoc(), Enum.CityBoundsType.SIEGE.extents))
+				continue;
+			
+			if (building.getGuild() == null)
+				continue;
+			
+			if (building.getGuild().isErrant())
+				continue;
+			
+
+			if (!building.getGuild().equals(serverCity.getGuild()) && !building.getGuild().equals(serverCity.getBane().getOwner().getGuild()))
+				continue;
+
+				// Only count auto protected buildings
+				if (building.getProtectionState() != ProtectionState.PROTECTED)
+					continue;
+
+				if (building.getGuild().equals(serverCity.getGuild()))
+					numDefenderBuildings++;
+				else
+				if (building.getGuild().equals(serverCity.getBane().getOwner().getGuild()))
+					numAttackerBuildings++;
+
+			
+		}
+
+		// Validate bane limits on siege assets
+
+		if (serverCity.getBane() != null)
+		if ((player.getGuild().equals(serverCity.getBane().getOwner().getGuild())) &&
+				(numAttackerBuildings >= serverCity.getBane().getStone().getRank() * 2)) {
+			return true;
+		}
+
+		if ((player.getGuild().equals(serverCity.getGuild())) &&
+				(numDefenderBuildings >= serverCity.getTOL().getRank())) {
+			return true;
+		}
+		
+		
+
+		// passes validation: can assign auto-protection to war asset
+		
+		if (serverCity.getBane() != null)
+		if (serverCity.isLocationOnCityGrid(siegeBuilding.getBounds()))
+			if (player.getGuild().equals(serverCity.getBane().getOwner().getGuild()))
+				return true;
+				
+		
+
+		
+		
+
+		siegeBuilding.setProtectionState(ProtectionState.PROTECTED);
+		// No bane placed.  We're done!
+
+		
+		return true;
+	}
+
+	private boolean placeTreeOfLife(PlayerCharacter playerCharacter, ClientConnection origin, PlaceAssetMsg msg) {
+
+		Realm serverRealm;
+		Zone serverZone;
+		ArrayList<AbstractGameObject> cityObjects; // MySql result set
+		PlacementInfo treeInfo;
+		Building treeObject = null;
+		City cityObject = null;
+		Zone cityZone = null;
+		Guild playerNation;
+		PlacementInfo treePlacement = msg.getFirstPlacementInfo();
+
+		// Setup working variables we'll need
+
+		serverRealm = RealmMap.getRealmAtLocation(treePlacement.getLoc());
+		serverZone = ZoneManager.findSmallestZone(treePlacement.getLoc());
+
+		// Early exit if something went horribly wrong
+		// with locating the current realm and/or zone
+
+		if (serverRealm == null || serverZone == null)
+			return false;
+
+		// Method checks validation conditions arising when placing
+		// trees
+
+		if (validateTreeOfLifePlacement(playerCharacter, serverRealm, serverZone, origin, msg) == false)
+			return false;
+
+		// Retrieve tree info for the w value it's passing.
+
+		treeInfo = msg.getFirstPlacementInfo();
+
+		if (treeInfo == null) {
+			PlaceAssetMsg.sendPlaceAssetError(origin, 1, "A Serious error has occurred. Please post details for to ensure transaction integrity");
+			return false;
+		}
+
+		Vector3fImmutable plantLoc = new Vector3fImmutable(treeInfo.getLoc().x,
+		                                                   serverZone.getHeightMap().getInterpolatedTerrainHeight(treeInfo.getLoc()),
+															treeInfo.getLoc().z);
+
+		cityObjects = DbManager.CityQueries.CREATE_CITY(playerCharacter.getObjectUUID(), serverZone.getObjectUUID(),
+				serverRealm.getRealmID(),
+				plantLoc.x - serverZone.getAbsX(), plantLoc.y,
+				plantLoc.z - serverZone.getAbsZ(), treeInfo.getRot().y, treeInfo.getW(), playerCharacter.getGuild().getName(), LocalDateTime.now());
+
+		// Uh oh!
+		if (cityObjects == null || cityObjects.isEmpty()) {
+			PlaceAssetMsg.sendPlaceAssetError(origin, 1, "A Serious error has occurred. Please post details for to ensure transaction integrity");
+			return false;
+		}
+
+		// Assign our worker variables after figuring out what
+		// is what in the result set.
+
+		for (AbstractGameObject gameObject : cityObjects) {
+
+			switch (gameObject.getObjectType()) {
+			case Building:
+				treeObject = (Building) gameObject;
+				treeObject.runAfterLoad();
+				break;
+			case City:
+				cityObject = (City) gameObject;
+				break;
+			case Zone:
+				cityZone = (Zone) gameObject;
+				break;
+			default:
+				// log some error here? *** Refactor
+			}
+		}
+
+		//?? your not allowed to plant a tree if ur not an errant guild.
+		// Desub from any previous nation.
+		// This should be done automatically in a method inside Guild *** Refactor
+		// Player is now a Soverign guild, configure them as such.
+
+		playerCharacter.getGuild().setNation(playerCharacter.getGuild());
+		playerNation = playerCharacter.getGuild();
+		playerNation.setGuildState(GuildState.Sovereign);
+
+		// Link the zone with the city and then add
+		// to the appropritae hash tables and cache
+
+		cityZone.setPlayerCity(true);
+
+		if (cityZone.getParent() != null)
+			cityZone.getParent().addNode(cityZone); //add as child to parent
+
+		ZoneManager.addZone(cityZone.getObjectUUID(), cityZone);
+		ZoneManager.addPlayerCityZone(cityZone);
+		serverZone.addNode(cityZone);
+		
+		cityZone.generateWorldAltitude();
+
+		cityObject.setParent(cityZone);
+		cityObject.setObjectTypeMask(MBServerStatics.MASK_CITY); // *** Refactor : should have it already
+		//Link the tree of life with the new zone
+
+		treeObject.setObjectTypeMask(MBServerStatics.MASK_BUILDING);
+		treeObject.setParentZone(cityZone);
+		MaintenanceManager.setMaintDateTime(treeObject, LocalDateTime.now().plusDays(7));
+
+		// Update guild binds and tags
+		//load the new city on the clients
+
+		CityZoneMsg czm = new CityZoneMsg(1, treeObject.getLoc().x, treeObject.getLoc().y, treeObject.getLoc().z, cityObject.getCityName(), cityZone, Enum.CityBoundsType.ZONE.extents, Enum.CityBoundsType.ZONE.extents);
+		DispatchMessage.dispatchMsgToAll(czm);
+
+		GuildManager.updateAllGuildBinds(playerNation, cityObject);
+		GuildManager.updateAllGuildTags(playerNation);
+
+		// Send all the cities to the clients?
+		// *** Refactor : figure out how to send like, one?
+
+		City.lastCityUpdate = System.currentTimeMillis();
+		WorldGrid.addObject(treeObject, playerCharacter);
+
+		serverRealm.addCity(cityObject.getObjectUUID());
+		playerNation.setCityUUID(cityObject.getObjectUUID());
+
+		// Bypass warehouse entry if we're an admin
+
+		if (playerCharacter.getAccount().status.equals(AccountStatus.ADMIN))
+			return true;
+
+		// Push this event to the data warehouse
+
+		CityRecord cityRecord = CityRecord.borrow(cityObject, RecordEventType.CREATE);
+		DataWarehouse.pushToWarehouse(cityRecord);
+
+		return true;
+	}
+
+	private boolean placeSpire(PlayerCharacter playerCharacter, ClientConnection origin, PlaceAssetMsg msg) {
+
+		Zone serverZone;
+		Building spireBuilding;
+		Blueprint blueprint;
+		City cityObject;
+		PlacementInfo buildingList;
+
+		// Setup working variables we'll need
+
+		buildingList = msg.getFirstPlacementInfo();
+
+		serverZone = ZoneManager.findSmallestZone(buildingList.getLoc());
+
+		// Early exit if something went horribly wrong
+		// with locating the current realm and/or city
+
+		if (serverZone == null)
+			return false;
+
+		cityObject = City.getCity(serverZone.getPlayerCityUUID());
+
+		if (cityObject == null)
+			return false;
+
+		// Method checks validation conditions arising when placing
+		// buildings.  Player must be on a city grid, must be
+		// inner council of the city's guild, etc.
+
+		if (validateCityBuildingPlacement(serverZone, msg, origin, playerCharacter, buildingList) == false)
+			return false;
+
+		// Loop through all buildings in this city looking for a spire of the.
+		// same type we are placing.  There can be only one of each type
+
+		int spireCount = 0;
+
+		blueprint = Blueprint.getBlueprint(msg.getFirstPlacementInfo().getBlueprintUUID());
+
+		for (Building building : serverZone.zoneBuildingSet) {
+
+			if (building.getBlueprint().getBuildingGroup() == BuildingGroup.SPIRE) {
+
+				if (building.getBlueprintUUID() == blueprint.getMeshForRank(0)) {
+					PlaceAssetMsg.sendPlaceAssetError(origin, 46, "");  // "Spire of that type exists"
+					return false;
+				}
+				spireCount++;
+			}
+		}
+
+		// Too many spires for this tree's rank?
+
+		if (spireCount >= Blueprint.getMaxShrines(cityObject.getTOL().getRank())) {
+			PlaceAssetMsg.sendPlaceAssetError(origin, 45, "");  //Tree cannot support anymore spires
+			return false;
+		}
+
+		// Create the spire
+
+		spireBuilding = createStructure(playerCharacter, msg.getFirstPlacementInfo(), serverZone);
+		return spireBuilding != null;
+	}
+
+	private boolean placeShrine(PlayerCharacter playerCharacter, ClientConnection origin, PlaceAssetMsg msg) {
+
+		Zone serverZone;
+		Blueprint blueprint;
+		City cityObject;
+		PlacementInfo buildingList;
+
+		// Setup working variables we'll need
+		buildingList = msg.getFirstPlacementInfo();
+
+		serverZone = ZoneManager.findSmallestZone(buildingList.getLoc());
+
+		// Early exit if something went horribly wrong
+		// with locating the current realm and/or zone
+		if (serverZone == null)
+			return false;
+
+		// Method checks validation conditions arising when placing
+		// buildings.  Player must be on a city grid, must be
+		// inner council of the city's guild, etc.
+
+		if (validateCityBuildingPlacement(serverZone, msg, origin, playerCharacter, buildingList) == false)
+			return false;
+
+		// Loop through all buildings in this city looking for a shrine of the.
+		// same type we are placing.  There can be only one of each type
+
+		int shrineCount = 0;
+
+		cityObject = City.getCity(serverZone.getPlayerCityUUID());
+
+		// Cannot place shrine in abanadoned city.  Shrines must be owned
+		// by the tol owner not the person placing them.
+
+		if (cityObject.getTOL().getOwnerUUID() == 0) {
+			PlaceAssetMsg.sendPlaceAssetError(origin, 42, "");  //Tree cannot support anymore shrines
+			return false;
+		}
+
+		blueprint = Blueprint.getBlueprint(msg.getFirstPlacementInfo().getBlueprintUUID());
+		
+		if (blueprint == null){
+			return false;
+		}
+
+		for (Building building : serverZone.zoneBuildingSet) {
+			if (building.getBlueprint() == null)
+				continue;
+			
+			if (building.getBlueprint().getBuildingGroup() == BuildingGroup.SHRINE) {
+				if (building.getBlueprintUUID() == blueprint.getMeshForRank(0)) {
+					PlaceAssetMsg.sendPlaceAssetError(origin, 43, "");  // "shrine of that type exists"
+					return false;
+				}
+				shrineCount++;
+			}
+		}
+
+		// Too many shrines for this tree's rank?
+
+		if (shrineCount >= Blueprint.getMaxShrines(cityObject.getTOL().getRank())) {
+			PlaceAssetMsg.sendPlaceAssetError(origin, 42, "");  //Tree cannot support anymore shrines
+			return false;
+		}
+
+		// Create the shrine
+
+		return createShrine((PlayerCharacter)cityObject.getTOL().getOwner(), msg.getFirstPlacementInfo(), serverZone);
+	}
+
+	private boolean placeBarrack(PlayerCharacter playerCharacter, ClientConnection origin, PlaceAssetMsg msg) {
+
+		Zone serverZone;
+		City cityObject;
+		PlacementInfo buildingList;
+
+		// Setup working variables we'll need
+		buildingList = msg.getFirstPlacementInfo();
+
+		serverZone = ZoneManager.findSmallestZone(buildingList.getLoc());
+
+		// Early exit if something went horribly wrong
+		// with locating the current realm and/or zone
+
+		if (serverZone == null)
+			return false;
+
+		// Method checks validation conditions arising when placing
+		// buildings.  Player must be on a city grid, must be
+		// inner council of the city's guild, etc.
+
+		if (validateCityBuildingPlacement(serverZone, msg, origin, playerCharacter, buildingList) == false)
+			return false;
+
+		// Loop through all buildings in this city counting barracks .
+
+		int barracksCount = 0;
+
+		cityObject = City.getCity(serverZone.getPlayerCityUUID());
+
+		// Cannot place barracks in abanadoned city.
+
+		if (cityObject.getTOL().getOwnerUUID() == 0) {
+			PlaceAssetMsg.sendPlaceAssetError(origin, 42, "");  //Tree cannot support anymore shrines
+			return false;
+		}
+
+		for (Building building : serverZone.zoneBuildingSet) {
+			if (building.getBlueprint().getBuildingGroup() == BuildingGroup.BARRACK)
+				barracksCount++;
+		}
+
+		// Too many shrines for this tree's rank?
+
+		if (barracksCount >= cityObject.getTOL().getRank()) {
+			PlaceAssetMsg.sendPlaceAssetError(origin, 47, "");  //Tree cannot support anymore shrines
+			return false;
+		}
+
+		// Create the shrine
+
+		return createBarracks((PlayerCharacter)cityObject.getTOL().getOwner(), msg.getFirstPlacementInfo(), serverZone);
+	}
+
+	private boolean placeCityWalls(PlayerCharacter player, ClientConnection origin, PlaceAssetMsg msg) {
+
+		// Member variables
+
+		Zone serverZone;
+		City cityObject;
+		int placementCost = 0;
+		CharacterItemManager itemMan;
+		Item goldItem;
+		Building wallPiece;
+
+		// Setup working variables we'll need
+
+		serverZone = ZoneManager.findSmallestZone(player.getLoc());
+
+		// Early exit if something went horribly wrong
+
+		if (serverZone == null)
+			return false;
+		
+		
+			if (player.getCharItemManager().getGoldTrading() > 0){
+				ErrorPopupMsg.sendErrorPopup(player, 195);
+				return false;
+			}
+		
+
+		// Method checks validation conditions arising when placing
+		// buildings.  Player must be on a city grid, must be
+		// inner council of the city's guild, etc.
+
+		if (validateCityBuildingPlacement(serverZone, msg, origin, player, msg.getFirstPlacementInfo()) == false)
+			return false;
+
+		cityObject = City.getCity(serverZone.getPlayerCityUUID());
+
+		// We need to be able to access how much gold a character is carrying
+
+		itemMan = player.getCharItemManager();
+
+		if (itemMan == null)
+
+			return false;
+
+		goldItem = itemMan.getGoldInventory();
+
+		// Grab list of walls we're placing
+
+		ArrayList<PlacementInfo> walls = msg.getPlacementInfo();
+
+		// Character must be able to afford walls
+
+		for (PlacementInfo wall : walls) {
+			placementCost += PlaceAssetMsg.getWallCost(wall.getBlueprintUUID());
+		}
+
+		// Early exit if not enough gold in character's inventory to place walls
+
+		if (placementCost > goldItem.getNumOfItems()) {
+			PlaceAssetMsg.sendPlaceAssetError(origin, 28, ""); // Not enough gold
+			return false;
+		}
+
+		placementCost = 0; // reset placement cost for fix bug with wall pieces somethings not taking gold out if forced an error.
+
+
+		// Overlap check and wall deed verifications
+		for (PlacementInfo wall : walls) {
+
+			if (Blueprint.isMeshWallPiece(wall.getBlueprintUUID()) == false) {
+				PlaceAssetMsg.sendPlaceAssetError(origin, 48, "");  //"Assets (except walls) must be placed one at a time"
+				continue;
+			}
+
+			// Ignore wall pieces not on the city grid
+			if (cityObject.isLocationOnCityGrid(wall.getLoc()) == false) {
+				PlaceAssetMsg.sendPlaceAssetError(origin, 1, "Asset " + cityObject.getName() + " not on citygrid");
+				continue;
+			}
+
+			// Does this wall collide with any other building?
+
+			for (Building building : serverZone.zoneBuildingSet) {
+
+
+				//TODO Clean up collision with placementInfo. don't need to create the same placementinfo bounds for collision checks on each building.
+				if ((building.getBlueprintUUID() != 0) && (Bounds.collide(wall, building) == true)) {
+
+					if (building.getRank() == -1) {
+						building.removeFromCache();
+						WorldGrid.RemoveWorldObject(building);
+						WorldGrid.removeObject(building);
+						building.getParentZone().getParent().zoneBuildingSet.remove(building);
+						continue;
+					}
+					// remove gold from walls already placed before returning.
+
+					PlaceAssetMsg.sendPlaceAssetError(origin, 3, building.getName());  //"Conflict between assets"
+					return false;
+				}
+			}
+			placementCost = PlaceAssetMsg.getWallCost(wall.getBlueprintUUID());
+
+			if (!itemMan.modifyInventoryGold(-placementCost)){
+				ChatManager.chatSystemInfo(player, player.getFirstName() + " can't has free moneys! no for real.. Thor.. seriously... I didnt fix it because you getting laid isnt important enough for me.");
+				return false;
+			}
+			// Attempt to place wall piece
+
+			wallPiece = createStructure(player, wall, serverZone);
+
+			if (wallPiece == null) {
+				PlaceAssetMsg.sendPlaceAssetError(origin, 1, "A Serious error has occurred. Please post details for to ensure transaction integrity");
+				continue;
+			}
+
+			// walls are auto protected
+			wallPiece.setProtectionState(ProtectionState.PROTECTED);
+			PlaceAssetMsg.sendPlaceAssetConfirmWall(origin,serverZone);
+
+		}
+
+		// Deduct gold from character's inventory
+
+
+		return true;
+	}
+
+	private static void closePlaceAssetWindow(ClientConnection origin) {
+
+		// Action type 4 is the server telling the client to
+		// close the asset placement window.
+		// This is believed to be a confirmation message to the client
+		PlaceAssetMsg pam = new PlaceAssetMsg();
+		pam.setActionType(4);
+		Dispatch dispatch = Dispatch.borrow(origin.getPlayerCharacter(), pam);
+		DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
+	}
+
+	// Method deletes one item from the player's inventory
+	// based on the mesh UUID the deed/seed spawns
+
+	private static void removeDeedByMeshUUID(PlayerCharacter player, int meshUUID) {
+
+		CharacterItemManager inventoryManager;
+		ArrayList<Item> itemList;
+
+		inventoryManager = player.getCharItemManager();
+		itemList = player.getInventory();
+
+		for (Item inventoryItem : itemList) {
+			if (inventoryItem.getItemBase().getUseID() == meshUUID) {
+				inventoryManager.delete(inventoryItem);
+
+				inventoryManager.updateInventory();
+				return;
+			}
+
+		}
+	}
+
+	// Method validates the location we have selected for our new city
+
+	private static boolean validateTreeOfLifePlacement(PlayerCharacter playerCharacter, Realm serverRealm, Zone serverZone,
+			ClientConnection origin, PlaceAssetMsg msg) {
+
+		PlacementInfo placementInfo = msg.getFirstPlacementInfo();
+
+		// Your guild already owns a tree
+
+		if (playerCharacter.getGuild().getOwnedCity() != null) {
+			PlaceAssetMsg.sendPlaceAssetError(origin, 1, "Your guild already owns a tree!");
+			return false;
+		}
+
+		// Validate that the player is the leader of a guild
+
+		if (GuildStatusController.isGuildLeader(playerCharacter.getGuildStatus()) == false) {
+			PlaceAssetMsg.sendPlaceAssetError(origin, 10, ""); // Must be a guild leader
+			return false;
+		}
+
+		// Validate that the player is the leader of a guild
+		// that is not currently Sovereign  *** BUG? Doesn't look right.  isGuildLeader()?
+
+		if ((playerCharacter.getGuild().getGuildState() != GuildState.Sworn
+				|| playerCharacter.getGuild().getGuildState() != GuildState.Errant) == false) {
+			PlaceAssetMsg.sendPlaceAssetError(origin, 17, ""); // Your is not an errant or soverign guild
+			return false;
+		}
+
+		// All trees must be placed within a continent.
+
+		if (!serverZone.isContininent()) {
+
+			PlaceAssetMsg.sendPlaceAssetError(origin, 69, ""); // Tree must be within a territory
+			return false;
+		}
+
+		RealmType realmType = RealmType.getRealmTypeByUUID(serverRealm.getRealmID());
+
+		if (
+				(realmType.equals(RealmType.MAELSTROM)) ||
+				(realmType.equals(RealmType.OBLIVION))) {
+			PlaceAssetMsg.sendPlaceAssetError(origin, 57, playerCharacter.getName()); // No building may be placed within this territory
+			return false;
+		}
+
+		// Cannot place a tree underwater
+
+		if (HeightMap.isLocUnderwater(placementInfo.getLoc())) {
+			PlaceAssetMsg.sendPlaceAssetError(origin, 6, ""); // Cannot place underwater
+			return false;
+		}
+
+		//Test city not too close to any other zone
+
+		if (!ZoneManager.validTreePlacementLoc(serverZone, placementInfo.getLoc().x, placementInfo.getLoc().z)) {
+			PlaceAssetMsg.sendPlaceAssetError(origin, 39, ""); // Too close to another tree
+			return false;
+		}
+
+		// Validate that Realm is not at it's city limit
+
+		if (serverRealm.isRealmFull() == true) {
+			int numCities;
+			numCities = serverRealm.getNumCities();
+			PlaceAssetMsg.sendPlaceAssetError(origin, 58, Integer.toString(numCities)); // This territory is full
+			return false;
+		}
+
+		return true;
+	}
+
+	private Building createStructure(PlayerCharacter playerCharacter, PlacementInfo buildingInfo, Zone currentZone) {
+
+		Blueprint blueprint;
+		Building newMesh;
+		DateTime completionDate;
+		float vendorRotation;
+		float buildingRotation;
+
+		blueprint = Blueprint.getBlueprint(buildingInfo.getBlueprintUUID());
+
+		if (blueprint == null) {
+			Logger.error("CreateStucture: DB returned null blueprint.");
+			return null;
+		}
+
+		// All seige buildings build in 15 minutes
+		if ((blueprint.getBuildingGroup().equals(BuildingGroup.SIEGETENT))
+				|| (blueprint.getBuildingGroup().equals(BuildingGroup.BULWARK)))
+			completionDate = DateTime.now().plusMinutes(15);
+		else
+			completionDate = DateTime.now().plusHours(blueprint.getRankTime(1));
+
+		Vector3fImmutable localLoc = new Vector3fImmutable(ZoneManager.worldToLocal(buildingInfo.getLoc(), currentZone));
+
+		buildingRotation = buildingInfo.getRot().y;
+		vendorRotation = buildingInfo.getW();
+
+		// if W return is negative, this is a -90 rotation not a 90?
+
+		newMesh = DbManager.BuildingQueries.CREATE_BUILDING(
+				currentZone.getObjectUUID(), playerCharacter.getObjectUUID(), blueprint.getName(), blueprint.getMeshForRank(0),
+				localLoc, 1.0f, blueprint.getMaxHealth(0), ProtectionState.NONE, 0, 0,
+				completionDate, blueprint.getMeshForRank(0), vendorRotation, buildingRotation);
+
+		// Make sure we have a valid mesh
+		if (newMesh == null) {
+			Logger.error("CreateStucture: DB returned null object.");
+			return null;
+		}
+
+		newMesh.setObjectTypeMask(MBServerStatics.MASK_BUILDING);
+		MaintenanceManager.setMaintDateTime(newMesh, LocalDateTime.now().plusDays(7));
+
+		WorldGrid.addObject(newMesh, playerCharacter);
+		return newMesh;
+
+	}
+
+	private boolean createShrine(PlayerCharacter player, PlacementInfo buildingInfo, Zone currentZone) {
+
+		Blueprint blueprint;
+		Building newMesh;
+		Shrine newShrine;
+		City city;
+		ShrineType shrineType;
+
+		if (player == null)
+			return false;
+
+		blueprint = Blueprint.getBlueprint(buildingInfo.getBlueprintUUID());
+
+		if (blueprint == null) {
+			Logger.error("CreateShrine: DB returned null blueprint.");
+			return false;
+		}
+
+		shrineType = Shrine.getShrineTypeByBlueprintUUID(blueprint.getBlueprintUUID());
+
+		city = City.getCity(currentZone.getPlayerCityUUID());
+
+		if (city == null)
+			return false;
+
+		if (!city.isLocationOnCityGrid(buildingInfo.getLoc()))
+			return false;
+
+		Vector3fImmutable localLoc = new Vector3fImmutable(ZoneManager.worldToLocal(buildingInfo.getLoc(), currentZone));
+
+		float buildingRotation = buildingInfo.getRot().y;
+		float vendorRotation = buildingInfo.getW();
+
+		
+		ArrayList<AbstractGameObject> shrineObjects = DbManager.ShrineQueries.CREATE_SHRINE(
+				currentZone.getObjectUUID(), player.getObjectUUID(), blueprint.getName(), blueprint.getMeshForRank(0),
+				localLoc, 1.0f, blueprint.getMaxHealth(0), ProtectionState.PROTECTED, 0, 0,
+				DateTime.now().plusHours(blueprint.getRankTime(1)), blueprint.getMeshForRank(0), vendorRotation, buildingRotation, shrineType.name());
+
+		if (shrineObjects == null) {
+			PlaceAssetMsg.sendPlaceAssetError(player.getClientConnection(), 1, "A Serious error has occurred. Please post details for to ensure transaction integrity");
+			return false;
+		}
+
+		for (AbstractGameObject ago : shrineObjects) {
+
+			switch (ago.getObjectType()) {
+			case Building:
+				newMesh = (Building) ago;
+				newMesh.runAfterLoad();
+				newMesh.setObjectTypeMask(MBServerStatics.MASK_BUILDING);
+				MaintenanceManager.setMaintDateTime(newMesh, LocalDateTime.now().plusDays(7));
+				WorldGrid.addObject(newMesh, player);
+				break;
+			case Shrine:
+				newShrine = (Shrine) ago;
+				newShrine.getShrineType().addShrineToServerList(newShrine);
+				break;
+			default:
+				PlaceAssetMsg.sendPlaceAssetError(player.getClientConnection(), 1, "A Serious error has occurred. Please post details for to ensure transaction integrity");
+				break;
+			}
+		}
+
+		return true;
+	}
+
+	private boolean createBarracks(PlayerCharacter player, PlacementInfo buildingInfo, Zone currentZone) {
+
+		Blueprint blueprint;
+		Building newMesh;
+		Shrine newShrine;
+		City city;
+
+		if (player == null)
+			return false;
+
+		blueprint = Blueprint.getBlueprint(buildingInfo.getBlueprintUUID());
+
+		if (blueprint == null) {
+			Logger.error("CreateShrine: DB returned null blueprint.");
+			return false;
+		}
+
+		city = City.getCity(currentZone.getPlayerCityUUID());
+
+		if (city == null)
+			return false;
+
+		if (!city.isLocationOnCityGrid(buildingInfo.getLoc()))
+			return false;
+
+		Vector3fImmutable localLoc = new Vector3fImmutable(ZoneManager.worldToLocal(buildingInfo.getLoc(), currentZone));
+
+		float buildingRotation = buildingInfo.getRot().y;
+		float vendorRotation = buildingInfo.getW();
+		DateTime completionDate = DateTime.now().plusHours(blueprint.getRankTime(1));
+
+		
+		newMesh = DbManager.BuildingQueries.CREATE_BUILDING(
+				currentZone.getObjectUUID(), player.getObjectUUID(), blueprint.getName(), blueprint.getMeshForRank(0),
+				localLoc, 1.0f, blueprint.getMaxHealth(0), ProtectionState.PROTECTED, 0, 0,
+				completionDate, blueprint.getMeshForRank(0), vendorRotation, buildingRotation);
+
+		// Make sure we have a valid mesh
+		if (newMesh == null) {
+			Logger.error("CreateStucture: DB returned null object.");
+			return false;
+		}
+
+		newMesh.setObjectTypeMask(MBServerStatics.MASK_BUILDING);
+		MaintenanceManager.setMaintDateTime(newMesh, LocalDateTime.now().plusDays(7));
+		WorldGrid.addObject(newMesh, player);
+
+		return true;
+	}
+
+	private boolean createWarehouse(PlayerCharacter player, PlacementInfo buildingInfo, Zone currentZone) {
+
+		Blueprint blueprint;
+		Building newMesh = null;
+		ArrayList<AbstractGameObject> warehouseObjects;
+
+		blueprint = Blueprint.getBlueprint(buildingInfo.getBlueprintUUID());
+
+		if (blueprint == null) {
+			Logger.error("CreateWarehouse: DB returned null blueprint.");
+			return false;
+		}
+
+		Vector3fImmutable localLoc = new Vector3fImmutable(ZoneManager.worldToLocal(buildingInfo.getLoc(), currentZone));
+
+		float buildingRotation = buildingInfo.getRot().y;
+		float vendorRotation = buildingInfo.getW();
+
+		warehouseObjects = DbManager.WarehouseQueries.CREATE_WAREHOUSE(
+				currentZone.getObjectUUID(), player.getObjectUUID(), blueprint.getName(), blueprint.getMeshForRank(0),
+				localLoc, 1.0f, blueprint.getMaxHealth(0), ProtectionState.NONE, 0, 0,
+				DateTime.now().plusHours(blueprint.getRankTime(1)), blueprint.getMeshForRank(0), vendorRotation, buildingRotation);
+
+		if (warehouseObjects == null) {
+			PlaceAssetMsg.sendPlaceAssetError(player.getClientConnection(), 1, "A Serious error has occurred. Please post details for to ensure transaction integrity");
+			return false;
+		}
+
+		// Load the building into the simulation
+
+		for (AbstractGameObject ago : warehouseObjects) {
+
+			if (ago.getObjectType() == GameObjectType.Building) {
+				newMesh = (Building) ago;
+				newMesh.setObjectTypeMask(MBServerStatics.MASK_BUILDING);
+				MaintenanceManager.setMaintDateTime(newMesh, LocalDateTime.now().plusDays(7));
+				WorldGrid.addObject(newMesh, player);
+				newMesh.runAfterLoad();
+			}
+			else if (ago.getObjectType() == GameObjectType.Warehouse) {
+				Warehouse warehouse = (Warehouse) ago;
+				City city = City.getCity(currentZone.getPlayerCityUUID());
+				if (city == null)
+					return true;
+				city.setWarehouseBuildingID(newMesh.getObjectUUID());
+				Warehouse.warehouseByBuildingUUID.put(newMesh.getObjectUUID(), warehouse);
+			}
+		}
+
+		return true;
+	}
+
+	// Validates that player is able to place buildings
+
+	private static boolean validateBuildingPlacement(Zone serverZone, PlaceAssetMsg msg, ClientConnection origin, PlayerCharacter player, PlacementInfo placementInfo) {
+
+		RealmType currentRealm;
+
+		// Retrieve the building details we're placing
+
+		if (serverZone.isNPCCity() == true) {
+			PlaceAssetMsg.sendPlaceAssetError(origin, 15, ""); // Cannot place in a peace zone
+			return false;
+		}
+
+		// Errant guilds cannot place assets
+
+		if (player.getGuild().getGuildState() == GuildState.Errant) {
+			PlaceAssetMsg.sendPlaceAssetError(origin, 1, "Only soverign or sworn guilds may place assets.");
+			return false;
+		}
+
+		// Cannot place a building underwater
+
+		if (HeightMap.isLocUnderwater(placementInfo.getLoc())) {
+			PlaceAssetMsg.sendPlaceAssetError(origin, 6, ""); // Cannot place underwater
+			return false;
+		}
+
+		// Players cannot place buildings in mob zones.
+
+		if ((serverZone.isMacroZone() == true)
+				|| (serverZone.getParent().isMacroZone() == true)) {
+			PlaceAssetMsg.sendPlaceAssetError(origin, 57, player.getName()); // No building may be placed within this territory
+			return false;
+		}
+
+		currentRealm = RealmType.getRealmTypeByUUID(RealmMap.getRealmIDAtLocation(player.getLoc()));
+
+		if (
+				(currentRealm.equals(RealmType.MAELSTROM)) ||
+				(currentRealm.equals(RealmType.OBLIVION))) {
+			PlaceAssetMsg.sendPlaceAssetError(origin, 57, player.getName()); // No building may be placed within this territory
+			return false;
+		}
+
+		// Cannot place assets on a dead tree
+
+		if ((serverZone.isPlayerCity())
+				&& (City.getCity(serverZone.getPlayerCityUUID()).getTOL().getRank() == -1)){
+			PlaceAssetMsg.sendPlaceAssetError(origin, 1, "Cannot place asset on dead tree until world heals");
+			return false;
+		}
+
+		// Overlap check
+
+		for (Building building : serverZone.zoneBuildingSet) {
+
+			if ((building.getBlueprintUUID() != 0) && (Bounds.collide(placementInfo, building) == true)) {
+
+				// Ignore and remove from simulation if we are placing over rubble
+
+				if (building.getRank() == -1) {
+
+					if ((building.getBlueprintUUID() != 0)
+							&& (building.getBlueprint().getBuildingGroup() == BuildingGroup.SHRINE)){
+						Shrine.RemoveShrineFromCacheByBuilding(building);
+						if (building.getCity() != null){
+
+						}
+					}
+
+					building.removeFromCache();
+					WorldGrid.RemoveWorldObject(building);
+					WorldGrid.removeObject(building);
+					building.getParentZone().zoneBuildingSet.remove(building);
+					continue;
+				}
+
+
+				PlaceAssetMsg.sendPlaceAssetError(origin, 3, "");  // Conflict between proposed assets
+				return false;
+			}
+		}
+
+		return true;
+	}
+
+	private static boolean validateCityBuildingPlacement(Zone serverZone, PlaceAssetMsg msg, ClientConnection origin, PlayerCharacter player, PlacementInfo buildingInfo) {
+
+		// Peform shared common validation first
+
+		if (validateBuildingPlacement(serverZone, msg, origin, player, buildingInfo) == false)
+			return false;
+
+		// Must be a player city
+
+		if (serverZone.isPlayerCity() == false) {
+			PlaceAssetMsg.sendPlaceAssetError(origin, 41, player.getName()); // Cannot place outisde a guild zone
+			return false;
+		}
+
+		//Test zone has a city object
+
+		City city = City.getCity(serverZone.getPlayerCityUUID());
+
+		if (city == null) {
+			PlaceAssetMsg.sendPlaceAssetError(origin, 52, ""); //"no city to associate asset with"
+			return false;
+		}
+
+		// City assets must be placed on the city grid
+
+		if (!city.isLocationOnCityGrid(buildingInfo.getLoc())) {
+			PlaceAssetMsg.sendPlaceAssetError(origin, 1, "Assset must be placed on a City Grid");
+			return false;
+		}
+
+		// Make sure it's not an errant tree
+
+		if ( (city.getGuild() == null || city.getGuild().isErrant() == true)) {
+			PlaceAssetMsg.sendPlaceAssetError(origin, 18, ""); //"There are no guild trees to be found"
+			return false;
+		}
+
+		//Test player is in correct guild to place buildings
+
+		if (!player.isCSR)
+			if (player.getGuild().getObjectUUID() != city.getGuild().getObjectUUID()) {
+				PlaceAssetMsg.sendPlaceAssetError(origin, 9, "");  //You must be a guild member to place this asset
+				return false;
+			}
+
+		return true;
+	}
+
+}
diff --git a/src/engine/net/client/handlers/RecommendNationMsgHandler.java b/src/engine/net/client/handlers/RecommendNationMsgHandler.java
new file mode 100644
index 00000000..8912dea4
--- /dev/null
+++ b/src/engine/net/client/handlers/RecommendNationMsgHandler.java
@@ -0,0 +1,90 @@
+package engine.net.client.handlers;
+
+import engine.Enum.AllianceType;
+import engine.exception.MsgSendException;
+import engine.gameManager.ChatManager;
+import engine.gameManager.SessionManager;
+import engine.net.Dispatch;
+import engine.net.client.ClientConnection;
+import engine.net.client.msg.AllianceChangeMsg;
+import engine.net.client.msg.ClientNetMsg;
+import engine.net.client.msg.RecommendNationMsg;
+import engine.objects.Guild;
+import engine.objects.PlayerCharacter;
+
+/*
+ * @Author:
+ * @Summary: Processes application protocol message which handles
+ * protecting and unprotecting city assets
+ */
+public class RecommendNationMsgHandler extends AbstractClientMsgHandler {
+
+	public RecommendNationMsgHandler() {
+		super(RecommendNationMsg.class);
+	}
+
+	@Override
+	protected boolean _handleNetMsg(ClientNetMsg baseMsg, ClientConnection origin) throws MsgSendException {
+
+		// Member variable declaration
+
+		PlayerCharacter player;
+		RecommendNationMsg msg;
+
+
+		// Member variable assignment
+
+		msg = (RecommendNationMsg) baseMsg;
+
+		player = SessionManager.getPlayerCharacter(origin);
+
+		if (player == null)
+			return true;
+
+
+		RecommendNationMsgHandler.RecommendNation(player.getGuild(), Guild.getGuild(msg.getGuildID()), msg, origin);
+
+
+
+
+		//		dispatch = Dispatch.borrow(player, baseMsg);
+		//		DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+
+		return true;
+
+	}
+
+	private static void RecommendNation(Guild fromGuild, Guild toGuild, RecommendNationMsg msg, ClientConnection origin) {
+
+		// Member variable declaration
+		Dispatch dispatch;
+
+		// Member variable assignment
+
+		if (fromGuild == null)
+			return;
+
+		if (toGuild == null)
+			return;
+
+		AllianceType allianceType;
+		if (msg.getAlly() == 1)
+			allianceType = AllianceType.RecommendedAlly;
+		else
+			allianceType = AllianceType.RecommendedEnemy;
+
+		if (!fromGuild.addGuildToAlliance(new AllianceChangeMsg(origin.getPlayerCharacter(),fromGuild.getObjectUUID(), toGuild.getObjectUUID(), (byte)0, 0), allianceType, toGuild, origin.getPlayerCharacter()))
+			return;
+		String alliance = msg.getAlly() == 1? "ally" : "enemy";
+
+		ChatManager.chatGuildInfo(fromGuild, origin.getPlayerCharacter().getFirstName() + " has recommended " + toGuild.getName() + " as an " + alliance );
+
+		//		dispatch = Dispatch.borrow(origin.getPlayerCharacter(), msg);
+		//		DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+
+
+	}
+
+
+
+}
diff --git a/src/engine/net/client/handlers/RemoveFriendHandler.java b/src/engine/net/client/handlers/RemoveFriendHandler.java
new file mode 100644
index 00000000..f17df1f4
--- /dev/null
+++ b/src/engine/net/client/handlers/RemoveFriendHandler.java
@@ -0,0 +1,64 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.handlers;
+
+import engine.Enum.DispatchChannel;
+import engine.exception.MsgSendException;
+import engine.net.Dispatch;
+import engine.net.DispatchMessage;
+import engine.net.client.ClientConnection;
+import engine.net.client.msg.ClientNetMsg;
+import engine.net.client.msg.RemoveFriendMessage;
+import engine.objects.PlayerCharacter;
+import engine.objects.PlayerFriends;
+
+
+
+public class RemoveFriendHandler extends AbstractClientMsgHandler {
+
+	public RemoveFriendHandler() {
+		super(RemoveFriendMessage.class);
+	}
+
+	@Override
+	protected boolean _handleNetMsg(ClientNetMsg baseMsg,
+			ClientConnection origin) throws MsgSendException {
+		
+		PlayerCharacter player = origin.getPlayerCharacter();
+		
+		if (player == null)
+			return true;
+		
+
+		RemoveFriendMessage msg = (RemoveFriendMessage)baseMsg;
+		
+			HandleRemoveFriend(player,msg);
+			
+		return true;
+	}
+	
+
+	
+	public static void HandleRemoveFriend(PlayerCharacter player, RemoveFriendMessage msg){
+		
+		//No friends in list. Early exit.
+		PlayerFriends.RemoveFromFriends(player.getObjectUUID(), msg.friendID);
+		PlayerFriends.RemoveFromFriends(msg.friendID, player.getObjectUUID());
+	
+	Dispatch dispatch = Dispatch.borrow(player, msg);
+	DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
+	
+	msg = new RemoveFriendMessage(msg.friendID);
+	
+	 dispatch = Dispatch.borrow(player, msg);
+	DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
+	}
+	
+}
diff --git a/src/engine/net/client/handlers/RemoveFromGroupHandler.java b/src/engine/net/client/handlers/RemoveFromGroupHandler.java
new file mode 100644
index 00000000..a7e7ede8
--- /dev/null
+++ b/src/engine/net/client/handlers/RemoveFromGroupHandler.java
@@ -0,0 +1,118 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.handlers;
+
+import engine.Enum;
+import engine.exception.MsgSendException;
+import engine.gameManager.ChatManager;
+import engine.gameManager.GroupManager;
+import engine.gameManager.SessionManager;
+import engine.net.Dispatch;
+import engine.net.DispatchMessage;
+import engine.net.client.ClientConnection;
+import engine.net.client.msg.ClientNetMsg;
+import engine.net.client.msg.group.GroupUpdateMsg;
+import engine.net.client.msg.group.RemoveFromGroupMsg;
+import engine.objects.Group;
+import engine.objects.PlayerCharacter;
+
+import java.util.Set;
+
+public class RemoveFromGroupHandler extends AbstractClientMsgHandler {
+
+    public RemoveFromGroupHandler() {
+        super(RemoveFromGroupMsg.class);
+    }
+
+    @Override
+    protected boolean _handleNetMsg(ClientNetMsg baseMsg,
+                                    ClientConnection origin) throws MsgSendException {
+
+        // Declar member variables
+
+        PlayerCharacter source;
+        PlayerCharacter target;
+        RemoveFromGroupMsg msg;
+        Group group;
+        GroupUpdateMsg gim;
+        ClientConnection gcc;
+        Set<PlayerCharacter> groupMembers;
+
+        // Assign member variables
+
+        msg = (RemoveFromGroupMsg) baseMsg;
+
+        source = SessionManager.getPlayerCharacter(origin);
+
+        if (source == null)
+            return false;
+
+        group = GroupManager.getGroup(source);
+
+        if (group == null)
+            return false;
+
+        if (group.getGroupLead() != source) // Only group lead can remove
+            return false;
+
+        target = SessionManager.getPlayerCharacterByID(msg.getTargetID());
+
+        if (target == null)
+            return false;
+
+        if (target == source) { // can't remove self, must quit
+            RemoveFromGroupMsg reply = new RemoveFromGroupMsg();
+            reply.setResponse(1);
+            Dispatch dispatch = Dispatch.borrow(source, reply);
+            DispatchMessage.dispatchMsgDispatch(dispatch, engine.Enum.DispatchChannel.SECONDARY);
+            return false;
+        }
+
+        gcc = SessionManager.getClientConnection(target);
+
+        if (gcc != null) {
+
+            // Cleanup group window for player quiting
+
+            groupMembers = group.getMembers();
+
+            for (PlayerCharacter groupMember : groupMembers) {
+
+                if (groupMember == null)
+                    continue;
+
+                gim = new GroupUpdateMsg();
+                gim.setGroup(group);
+                gim.setPlayer(target);
+                gim.setMessageType(3);
+                gim.setPlayer(groupMember);
+                Dispatch dispatch = Dispatch.borrow(target, gim);
+                DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+
+            }
+        }
+
+        // Remove from group and clean up everyone elses window
+        group.removeGroupMember(target);
+        GroupManager.removeFromGroups(target);
+
+        gim = new GroupUpdateMsg();
+        gim.setGroup(group);
+        gim.setMessageType(3);
+        gim.setPlayer(target);
+        group.sendUpdate(gim);
+
+        String text = target.getFirstName() + " has left your group.";
+        ChatManager.chatGroupInfo(source, text);
+
+        return true;
+    }
+
+}
diff --git a/src/engine/net/client/handlers/RepairBuildingMsgHandler.java b/src/engine/net/client/handlers/RepairBuildingMsgHandler.java
new file mode 100644
index 00000000..56085cf1
--- /dev/null
+++ b/src/engine/net/client/handlers/RepairBuildingMsgHandler.java
@@ -0,0 +1,139 @@
+package engine.net.client.handlers;
+
+import engine.Enum;
+import engine.Enum.BuildingGroup;
+import engine.exception.MsgSendException;
+import engine.gameManager.BuildingManager;
+import engine.gameManager.SessionManager;
+import engine.gameManager.ZoneManager;
+import engine.net.Dispatch;
+import engine.net.DispatchMessage;
+import engine.net.client.ClientConnection;
+import engine.net.client.msg.ClientNetMsg;
+import engine.net.client.msg.RepairBuildingMsg;
+import engine.net.client.msg.UpdateObjectMsg;
+import engine.objects.Building;
+import engine.objects.City;
+import engine.objects.PlayerCharacter;
+import engine.objects.Zone;
+
+/*
+ * @Author:
+ * @Summary: Processes application protocol message which handles
+ * protecting and unprotecting city assets
+ */
+public class RepairBuildingMsgHandler extends AbstractClientMsgHandler {
+
+	public RepairBuildingMsgHandler() {
+		super(RepairBuildingMsg.class);
+	}
+
+	@Override
+	protected boolean _handleNetMsg(ClientNetMsg baseMsg, ClientConnection origin) throws MsgSendException {
+
+		// Member variable declaration
+
+		PlayerCharacter player;
+		Building targetBuilding;
+		RepairBuildingMsg msg;
+
+
+		// Member variable assignment
+
+		msg = (RepairBuildingMsg) baseMsg;
+
+		player = SessionManager.getPlayerCharacter(origin);
+
+		if (player == null)
+			return true;
+
+
+
+		switch (msg.getType()) {
+		case 0:
+			targetBuilding =  BuildingManager.getBuildingFromCache(msg.getBuildingID());
+			RepairBuilding(targetBuilding, origin, msg);
+			break;
+
+			//		targetBuilding.createFurniture(item.getItemBase().getUseID(), 0, msg.getFurnitureLoc(), Vector3f.ZERO, 0, player);
+
+
+		}
+
+
+
+
+		//		dispatch = Dispatch.borrow(player, baseMsg);
+		//		DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+
+		return true;
+
+	}
+
+	private static void RepairBuilding(Building targetBuilding, ClientConnection origin, RepairBuildingMsg msg) {
+
+		// Member variable declaration
+
+		Zone serverZone;
+		Dispatch dispatch;
+
+		// Member variable assignment
+
+		if (targetBuilding == null)
+			return;
+
+		if (!targetBuilding.hasFunds(BuildingManager.GetRepairCost(targetBuilding)))
+			return;
+
+		PlayerCharacter pc = origin.getPlayerCharacter();
+
+		serverZone = ZoneManager.findSmallestZone(pc.getLoc());
+
+		if (serverZone.getPlayerCityUUID() == 0 && targetBuilding.getBlueprint() != null && targetBuilding.getBlueprint().getBuildingGroup() != BuildingGroup.MINE)
+			return;
+
+
+		City city = City.GetCityFromCache(serverZone.getPlayerCityUUID());
+
+		if (city != null){
+			if(city.getBane() != null && city.protectionEnforced == false)
+				return;
+
+		}
+
+		//cannot repair mines during 24/7 activity.
+
+		if (targetBuilding.getBlueprint() != null && targetBuilding.getBlueprint().getBuildingGroup() == BuildingGroup.MINE){
+			return;
+		}
+
+
+
+
+
+		int maxHP = (int) targetBuilding.getMaxHitPoints();
+		int repairCost = BuildingManager.GetRepairCost(targetBuilding);
+		int missingHealth = (int) BuildingManager.GetMissingHealth(targetBuilding);
+
+		if (!targetBuilding.transferGold(-repairCost,false))
+			return;
+
+		targetBuilding.modifyHealth(BuildingManager.GetMissingHealth(targetBuilding), null);
+
+		UpdateObjectMsg uom = new UpdateObjectMsg(targetBuilding,3);
+
+		dispatch = Dispatch.borrow(origin.getPlayerCharacter(), uom);
+		DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+
+
+
+		RepairBuildingMsg rbm = new RepairBuildingMsg( targetBuilding.getObjectUUID(),  maxHP, missingHealth, repairCost, targetBuilding.getStrongboxValue());
+
+
+		dispatch = Dispatch.borrow(origin.getPlayerCharacter(), rbm);
+		DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+	}
+
+
+
+}
diff --git a/src/engine/net/client/handlers/RequestBallListHandler.java b/src/engine/net/client/handlers/RequestBallListHandler.java
new file mode 100644
index 00000000..357a45e0
--- /dev/null
+++ b/src/engine/net/client/handlers/RequestBallListHandler.java
@@ -0,0 +1,47 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.handlers;
+
+import engine.exception.MsgSendException;
+import engine.net.client.ClientConnection;
+import engine.net.client.msg.ClientNetMsg;
+import engine.net.client.msg.RequestBallListMessage;
+import engine.objects.PlayerCharacter;
+public class RequestBallListHandler extends AbstractClientMsgHandler {
+
+	public RequestBallListHandler() {
+		super(RequestBallListMessage.class);
+	}
+
+	@Override
+	protected boolean _handleNetMsg(ClientNetMsg baseMsg,
+			ClientConnection origin) throws MsgSendException {
+		
+		PlayerCharacter player = origin.getPlayerCharacter();
+		
+		if (player == null)
+			return true;
+		
+
+		RequestBallListMessage msg = (RequestBallListMessage)baseMsg;
+		
+			HandleRequestBallList(player,msg);
+			
+		return true;
+	}
+	
+
+	
+	public static void HandleRequestBallList(PlayerCharacter player, RequestBallListMessage msg){
+		//currently not handled.
+	
+	}
+	
+}
diff --git a/src/engine/net/client/handlers/RequestEnterWorldHandler.java b/src/engine/net/client/handlers/RequestEnterWorldHandler.java
new file mode 100644
index 00000000..4724439f
--- /dev/null
+++ b/src/engine/net/client/handlers/RequestEnterWorldHandler.java
@@ -0,0 +1,162 @@
+package engine.net.client.handlers;
+
+import engine.Enum;
+import engine.Enum.DispatchChannel;
+import engine.InterestManagement.InterestManager;
+import engine.InterestManagement.WorldGrid;
+import engine.db.archive.CharacterRecord;
+import engine.db.archive.DataWarehouse;
+import engine.db.archive.PvpRecord;
+import engine.exception.MsgSendException;
+import engine.gameManager.SessionManager;
+import engine.math.Vector3fImmutable;
+import engine.net.Dispatch;
+import engine.net.DispatchMessage;
+import engine.net.client.ClientConnection;
+import engine.net.client.msg.*;
+import engine.objects.AbstractWorldObject;
+import engine.objects.Account;
+import engine.objects.PlayerCharacter;
+import engine.server.MBServerStatics;
+import engine.session.Session;
+import org.pmw.tinylog.Logger;
+
+/*
+ * @Author:
+ * @Summary: Processes application protocol message which requests
+ * that a character enters the game world from login screen
+ */
+
+public class RequestEnterWorldHandler extends AbstractClientMsgHandler {
+
+	public RequestEnterWorldHandler() {
+		super(RequestEnterWorldMsg.class);
+	}
+
+	@Override
+	protected boolean _handleNetMsg(ClientNetMsg baseMsg, ClientConnection origin) throws MsgSendException {
+
+		RequestEnterWorldMsg msg;
+
+		msg = (RequestEnterWorldMsg) baseMsg;
+
+		Session session = SessionManager.getSession(origin);
+
+		if (session == null)
+			return true;
+
+		PlayerCharacter player = origin.getPlayerCharacter();
+
+		WorldGrid.RemoveWorldObject(player);
+		Dispatch dispatch;
+
+		if (player == null) {
+			Logger.error("Unable to find player for session" + session.getSessionID());
+			origin.kickToLogin(MBServerStatics.LOGINERROR_UNABLE_TO_LOGIN, "Player not found.");
+			return true;
+		}
+
+		player.setEnteredWorld(false);
+
+		Account acc = SessionManager.getAccount(origin);
+
+		if (acc.status.ordinal() < MBServerStatics.worldAccessLevel.ordinal() || MBServerStatics.blockLogin) {
+			origin.disconnect();
+			return true;
+		}
+
+		// Brand new character.  Send the city select screen
+
+			if (player.getLevel() == 1 && player.getBindBuildingID() == -1) {
+			SelectCityMsg scm = new SelectCityMsg(player, true);
+				dispatch = Dispatch.borrow(player, scm);
+				DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
+				return true;
+			}
+
+		player.resetRegenUpdateTime();
+
+		// Map Data
+
+		try {
+			WorldDataMsg wdm = new WorldDataMsg();
+			dispatch = Dispatch.borrow(player, wdm);
+			DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.PRIMARY);
+		} catch (Exception e) {
+			// TODO Auto-generated catch block
+			Logger.error("WORLDDATAMESSAGE" + e.getMessage());
+		}
+
+		// Realm Data
+
+		try {
+			WorldRealmMsg wrm = new WorldRealmMsg();
+			dispatch = Dispatch.borrow(player, wrm);
+			DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.PRIMARY);
+		} catch (Exception e) {
+			// TODO Auto-generated catch block
+			Logger.error("REALMMESSAGE" + e.getMessage());
+		}
+
+		// Object Data
+		WorldObjectMsg wom = new WorldObjectMsg(session, true);
+		dispatch = Dispatch.borrow(player, wom);
+		DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.PRIMARY);
+
+		player.getTimestamps().put("EnterWorld", System.currentTimeMillis());
+
+		if (player.getLoc().equals(Vector3fImmutable.ZERO) || System.currentTimeMillis() > player.getTimeStamp("logout") + (15 * 60 * 1000)) {
+			player.stopMovement(player.getBindLoc());
+			player.setSafeMode();
+			player.updateLocation();
+			player.setRegion(AbstractWorldObject.GetRegionByWorldObject(player));
+		}
+
+		player.setTimeStamp("logout", 0);
+		player.respawnLock.writeLock().lock();
+		try{
+			if (!player.isAlive()){
+				Logger.info("respawning player on enter world.");
+				player.respawn(true, true,true);
+			}
+				
+		}catch (Exception e){
+			Logger.error(e);
+		}finally{
+			player.respawnLock.writeLock().unlock();
+		}
+		
+
+		player.resetDataAtLogin();
+
+		InterestManager.INTERESTMANAGER.HandleLoadForEnterWorld(player);
+
+		// If this is a brand new character...
+		// when they enter world is a great time to write their
+		// character record to the data warehouse.
+
+		if (player.getHash() == null) {
+
+			if (DataWarehouse.recordExists(Enum.DataRecordType.CHARACTER, player.getObjectUUID()) == false) {
+				CharacterRecord characterRecord = CharacterRecord.borrow(player);
+				DataWarehouse.pushToWarehouse(characterRecord);
+			}
+			player.setHash();
+		}
+
+        //
+		// We will load the kill/death lists here as data is only pertinent
+		// to characters actually logged into the game.
+        //
+
+		player.pvpKills = PvpRecord.getCharacterPvPHistory(player.getObjectUUID(), Enum.PvpHistoryType.KILLS);
+		player.pvpDeaths = PvpRecord.getCharacterPvPHistory(player.getObjectUUID(), Enum.PvpHistoryType.DEATHS);
+
+		SendOwnPlayerMsg sopm = new SendOwnPlayerMsg(SessionManager.getSession(origin));
+		dispatch = Dispatch.borrow(player, sopm);
+		DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.PRIMARY);
+
+		return true;
+	}
+
+	}
diff --git a/src/engine/net/client/handlers/RequestGuildListHandler.java b/src/engine/net/client/handlers/RequestGuildListHandler.java
new file mode 100644
index 00000000..5700cc0b
--- /dev/null
+++ b/src/engine/net/client/handlers/RequestGuildListHandler.java
@@ -0,0 +1,55 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.handlers;
+
+
+import engine.Enum;
+import engine.exception.MsgSendException;
+import engine.gameManager.SessionManager;
+import engine.net.Dispatch;
+import engine.net.DispatchMessage;
+import engine.net.client.ClientConnection;
+import engine.net.client.msg.ClientNetMsg;
+import engine.net.client.msg.ErrorPopupMsg;
+import engine.net.client.msg.guild.ReqGuildListMsg;
+import engine.net.client.msg.guild.SendGuildEntryMsg;
+import engine.objects.GuildStatusController;
+import engine.objects.PlayerCharacter;
+
+
+public class RequestGuildListHandler extends AbstractClientMsgHandler {
+
+	public RequestGuildListHandler() {
+		super(ReqGuildListMsg.class);
+	}
+
+	@Override
+	protected boolean _handleNetMsg(ClientNetMsg baseMsg, ClientConnection origin) throws MsgSendException {
+        Dispatch dispatch;
+
+		// get PlayerCharacter of person accepting invite
+		PlayerCharacter pc = SessionManager.getPlayerCharacter(
+				origin);
+		if (pc == null)
+			return true;
+		
+		if (GuildStatusController.isGuildLeader(pc.getGuildStatus()) == false){
+			ErrorPopupMsg.sendErrorMsg(pc, "You do not have such authority!");
+		}
+		SendGuildEntryMsg msg = new SendGuildEntryMsg(pc);
+
+
+        dispatch = Dispatch.borrow(pc, msg);
+        DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+
+		return true;
+	}
+
+}
diff --git a/src/engine/net/client/handlers/SendBallEntryHandler.java b/src/engine/net/client/handlers/SendBallEntryHandler.java
new file mode 100644
index 00000000..33ca1f9a
--- /dev/null
+++ b/src/engine/net/client/handlers/SendBallEntryHandler.java
@@ -0,0 +1,47 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.handlers;
+
+import engine.exception.MsgSendException;
+import engine.net.client.ClientConnection;
+import engine.net.client.msg.ClientNetMsg;
+import engine.net.client.msg.SendBallEntryMessage;
+import engine.objects.PlayerCharacter;
+public class SendBallEntryHandler extends AbstractClientMsgHandler {
+
+	public SendBallEntryHandler() {
+		super(SendBallEntryMessage.class);
+	}
+
+	@Override
+	protected boolean _handleNetMsg(ClientNetMsg baseMsg,
+			ClientConnection origin) throws MsgSendException {
+		
+		PlayerCharacter player = origin.getPlayerCharacter();
+		
+		if (player == null)
+			return true;
+		
+
+		SendBallEntryMessage msg = (SendBallEntryMessage)baseMsg;
+		
+			HandleAddBall(player,msg);
+			
+		return true;
+	}
+	
+
+	
+	public static void HandleAddBall(PlayerCharacter player, SendBallEntryMessage msg){
+		//currently not handled.
+	
+	}
+	
+}
diff --git a/src/engine/net/client/handlers/SwearInGuildHandler.java b/src/engine/net/client/handlers/SwearInGuildHandler.java
new file mode 100644
index 00000000..a27029fa
--- /dev/null
+++ b/src/engine/net/client/handlers/SwearInGuildHandler.java
@@ -0,0 +1,138 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.handlers;
+
+import engine.Enum;
+import engine.Enum.GameObjectType;
+import engine.Enum.GuildState;
+import engine.exception.MsgSendException;
+import engine.gameManager.ChatManager;
+import engine.gameManager.DbManager;
+import engine.gameManager.GuildManager;
+import engine.gameManager.SessionManager;
+import engine.net.Dispatch;
+import engine.net.DispatchMessage;
+import engine.net.client.ClientConnection;
+import engine.net.client.msg.ClientNetMsg;
+import engine.net.client.msg.ErrorPopupMsg;
+import engine.net.client.msg.guild.SendGuildEntryMsg;
+import engine.net.client.msg.guild.SwearInGuildMsg;
+import engine.objects.City;
+import engine.objects.Guild;
+import engine.objects.GuildStatusController;
+import engine.objects.PlayerCharacter;
+import org.pmw.tinylog.Logger;
+
+import java.util.ArrayList;
+
+public class SwearInGuildHandler extends AbstractClientMsgHandler {
+
+    public SwearInGuildHandler() {
+        super(SwearInGuildMsg.class);
+    }
+
+    @Override
+    protected boolean _handleNetMsg(ClientNetMsg baseMsg, ClientConnection origin) throws MsgSendException {
+        PlayerCharacter player;
+        SwearInGuildMsg swearInMsg;
+        Guild targetGuild;
+        Guild nation;
+        Dispatch dispatch;
+
+        swearInMsg = (SwearInGuildMsg) baseMsg;
+        player = SessionManager.getPlayerCharacter(origin);
+
+        if (player == null)
+            return true;
+
+        targetGuild = (Guild) DbManager.getObject(GameObjectType.Guild, swearInMsg.getGuildUUID());
+
+        if (targetGuild == null) {
+             ErrorPopupMsg.sendErrorMsg(player, "A Serious error has occured. Please post details for to ensure transaction integrity");
+            return true;
+        }
+
+        nation = player.getGuild();
+
+        if (nation == null) {
+             ErrorPopupMsg.sendErrorMsg(player, "You do not belong to a guild!");
+            return true;
+        }
+
+        try {
+            if (!nation.isNation()) {
+                 ErrorPopupMsg.sendErrorMsg(player, "Your guild is not a nation!");
+                return true;
+            }
+            if (!nation.getSubGuildList().contains(targetGuild)) {
+                ErrorPopupMsg.sendErrorMsg(player, "Your do not have such authority!");
+                return true;
+            }
+
+            if (!Guild.canSwearIn(targetGuild)) {
+                ErrorPopupMsg.sendErrorMsg(player, targetGuild.getGuildState().name() + "cannot be sworn in");
+                return true;
+            }
+
+            if (GuildStatusController.isGuildLeader(player.getGuildStatus()) == false){
+                ErrorPopupMsg.sendErrorMsg(player, "Your do not have such authority!");
+                return true;
+            }
+
+            if (!DbManager.GuildQueries.UPDATE_PARENT(targetGuild.getObjectUUID(), nation.getObjectUUID())) {
+                ErrorPopupMsg.sendErrorMsg(player, "A Serious error has occured. Please post details for to ensure transaction integrity");
+                return true;
+            }
+
+            switch (targetGuild.getGuildState()) {
+                case Petitioner:
+                    GuildManager.updateAllGuildBinds(targetGuild, nation.getOwnedCity());
+                    break;
+                case Protectorate:
+                    break;
+                default:
+                    //shouldn't get here.
+                    break;
+            }
+
+            //update Guild state.
+            targetGuild.setNation(nation);
+            GuildManager.updateAllGuildTags(targetGuild);
+            targetGuild.upgradeGuildState(false);
+
+            if (nation.getGuildState() == GuildState.Sovereign)
+                nation.upgradeGuildState(true);
+
+            SendGuildEntryMsg msg = new SendGuildEntryMsg(player);
+            dispatch = Dispatch.borrow(player, msg);
+            DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+
+           City.lastCityUpdate = System.currentTimeMillis();
+
+            ArrayList<PlayerCharacter> guildMembers = SessionManager.getActivePCsInGuildID(nation.getObjectUUID());
+
+            for (PlayerCharacter member : guildMembers) {
+                ChatManager.chatGuildInfo(member, "Your Guild is now a Nation!");
+            }
+
+            ArrayList<PlayerCharacter> swornMembers = SessionManager.getActivePCsInGuildID(targetGuild.getObjectUUID());
+
+            for (PlayerCharacter member : swornMembers) {
+                ChatManager.chatGuildInfo(member, "Your Guild has sword fealty to " + nation.getName() + '.');
+            }
+        } catch (Exception e) {
+            Logger.error( e.getMessage());
+            return true;
+        }
+
+        return true;
+    }
+
+}
diff --git a/src/engine/net/client/handlers/SwearInHandler.java b/src/engine/net/client/handlers/SwearInHandler.java
new file mode 100644
index 00000000..fca29d69
--- /dev/null
+++ b/src/engine/net/client/handlers/SwearInHandler.java
@@ -0,0 +1,79 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+
+package engine.net.client.handlers;
+
+import engine.Enum;
+import engine.exception.MsgSendException;
+import engine.gameManager.ChatManager;
+import engine.gameManager.SessionManager;
+import engine.net.Dispatch;
+import engine.net.DispatchMessage;
+import engine.net.client.ClientConnection;
+import engine.net.client.msg.ClientNetMsg;
+import engine.net.client.msg.ErrorPopupMsg;
+import engine.net.client.msg.guild.GuildInfoMsg;
+import engine.net.client.msg.guild.GuildListMsg;
+import engine.net.client.msg.guild.SwearInMsg;
+import engine.objects.GuildStatusController;
+import engine.objects.PlayerCharacter;
+
+public class SwearInHandler extends AbstractClientMsgHandler {
+
+	public SwearInHandler() {
+		super(SwearInMsg.class);
+	}
+
+	@Override
+	protected boolean _handleNetMsg(ClientNetMsg baseMsg, ClientConnection origin) throws MsgSendException {
+		SwearInMsg msg = (SwearInMsg) baseMsg;
+        Dispatch dispatch;
+
+		// get source player
+		PlayerCharacter source = SessionManager.getPlayerCharacter(origin);
+
+		if (source == null)
+			return true;
+
+		// get target player
+		PlayerCharacter target = SessionManager.getPlayerCharacterByID(msg.getTargetID());
+
+		if (target == null) {
+			ChatManager.chatGuildError(source,
+					"No such character found!");
+			return true;
+		}
+
+		if(source.getGuild() != target.getGuild()) {
+			ChatManager.chatGuildError(source,
+				"That player is not a member of " + source.getGuild().getName());
+			return true;
+		}
+
+		// Verify source has authority to swear in
+		if (GuildStatusController.isInnerCouncil(source.getGuildStatus()) == false) {
+			ErrorPopupMsg.sendErrorMsg(source, "Your do not have such authority!");
+			return true;
+		}
+
+		// Swear target in and send message to guild
+		target.setFullMember(true);
+		target.incVer();
+
+		ChatManager.chatGuildInfo(source,target.getFirstName() + " has been sworn in as a full member!");
+
+        dispatch = Dispatch.borrow(source, new GuildListMsg(source.getGuild()));
+        DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+		DispatchMessage.sendToAllInRange(target, new GuildInfoMsg(target, target.getGuild(), 2));
+
+		return true;
+	}
+
+}
diff --git a/src/engine/net/client/handlers/TaxCityMsgHandler.java b/src/engine/net/client/handlers/TaxCityMsgHandler.java
new file mode 100644
index 00000000..e03a6528
--- /dev/null
+++ b/src/engine/net/client/handlers/TaxCityMsgHandler.java
@@ -0,0 +1,154 @@
+package engine.net.client.handlers;
+
+import engine.Enum;
+import engine.InterestManagement.RealmMap;
+import engine.exception.MsgSendException;
+import engine.gameManager.BuildingManager;
+import engine.net.Dispatch;
+import engine.net.DispatchMessage;
+import engine.net.client.ClientConnection;
+import engine.net.client.msg.ClientNetMsg;
+import engine.net.client.msg.ErrorPopupMsg;
+import engine.net.client.msg.TaxCityMsg;
+import engine.net.client.msg.ViewResourcesMessage;
+import engine.objects.*;
+
+/*
+ * @Author:
+ * @Summary: Processes application protocol message which handles
+ * protecting and unprotecting city assets
+ */
+public class TaxCityMsgHandler extends AbstractClientMsgHandler {
+
+	public TaxCityMsgHandler() {
+		super(TaxCityMsg.class);
+	}
+
+	@Override
+	protected boolean _handleNetMsg(ClientNetMsg baseMsg, ClientConnection origin) throws MsgSendException {
+
+		// Member variable declaration
+
+		PlayerCharacter player;
+		TaxCityMsg msg;
+
+		player = origin.getPlayerCharacter();
+
+
+		msg = (TaxCityMsg) baseMsg;
+
+		ViewTaxes(msg,player);
+
+
+
+		return true;
+
+	}
+
+	private static boolean ViewTaxes(TaxCityMsg msg, PlayerCharacter player) {
+
+		// Member variable declaration
+		Building building = BuildingManager.getBuildingFromCache(msg.getGuildID());
+		Guild playerGuild = player.getGuild();
+
+		if (building == null){
+			ErrorPopupMsg.sendErrorMsg(player, "Not a valid Building!");
+			return true;
+		}
+
+		City city = building.getCity();
+		if (city == null){
+			ErrorPopupMsg.sendErrorMsg(player, "This building does not belong to a city.");
+			return true;
+		}
+
+		if (city.getWarehouse() == null){
+			ErrorPopupMsg.sendErrorMsg(player, "This city does not have a warehouse!");
+			return true;
+		}
+
+
+		if (playerGuild == null || playerGuild.isErrant()){
+			ErrorPopupMsg.sendErrorMsg(player, "You must belong to a guild to do that!");
+			return true;
+		}
+
+		if (playerGuild.getOwnedCity() == null){
+			ErrorPopupMsg.sendErrorMsg(player, "Your Guild needs to own a city!");
+			return true;
+		}
+
+		if (playerGuild.getOwnedCity().getWarehouse() == null){
+			ErrorPopupMsg.sendErrorMsg(player, "Your Guild needs to own a warehouse!");
+			return true;
+		}
+
+		if (playerGuild.getOwnedCity().getTOL() == null){
+			ErrorPopupMsg.sendErrorMsg(player, "Cannot find Tree of Life for your city!");
+			return true;
+		}
+
+//		if (playerGuild.getOwnedCity().getTOL().getRank() != 8){
+//			ErrorPopupMsg.sendErrorMsg(player, "Your City needs to Own a realm!");
+//			return true;
+//		}
+
+		if (playerGuild.getOwnedCity().getRealm() == null){
+			ErrorPopupMsg.sendErrorMsg(player, "Cannot find realm for your city!");
+			return true;
+		}
+		Realm targetRealm = RealmMap.getRealmForCity(city);
+
+		if (targetRealm == null){
+			ErrorPopupMsg.sendErrorMsg(player, "Cannot find realm for city you are attempting to tax!");
+			return true;
+		}
+
+//		if (targetRealm.getRulingCity() == null){
+//			ErrorPopupMsg.sendErrorMsg(player, "Realm Does not have a ruling city!");
+//			return true;
+//		}
+
+//		if (targetRealm.getRulingCity().getObjectUUID() != playerGuild.getOwnedCity().getObjectUUID()){
+//			ErrorPopupMsg.sendErrorMsg(player, "Your guild does not rule this realm!");
+//			return true;
+//		}
+
+//		if (playerGuild.getOwnedCity().getObjectUUID() == city.getObjectUUID()){
+//			ErrorPopupMsg.sendErrorMsg(player, "You cannot tax your own city!");
+//			return true;
+//		}
+
+
+
+
+		if (!GuildStatusController.isTaxCollector(player.getGuildStatus())){
+			ErrorPopupMsg.sendErrorMsg(player, "You Must be a tax Collector!");
+			return true;
+		}
+
+
+//		if (!city.isAfterTaxPeriod(DateTime.now(), player))
+//			return true;
+
+
+
+		ViewResourcesMessage vrm = new ViewResourcesMessage(player);
+		vrm.setGuild(building.getGuild());
+		vrm.setWarehouseBuilding(BuildingManager.getBuildingFromCache(building.getCity().getWarehouse().getBuildingUID()));
+		vrm.configure();
+		Dispatch dispatch = Dispatch.borrow(player, msg);
+		DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+		dispatch = Dispatch.borrow(player, vrm);
+		DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+		return true;
+
+
+
+	}
+
+
+
+
+
+}
diff --git a/src/engine/net/client/handlers/TaxResourcesMsgHandler.java b/src/engine/net/client/handlers/TaxResourcesMsgHandler.java
new file mode 100644
index 00000000..2e4e0e90
--- /dev/null
+++ b/src/engine/net/client/handlers/TaxResourcesMsgHandler.java
@@ -0,0 +1,73 @@
+package engine.net.client.handlers;
+
+import engine.exception.MsgSendException;
+import engine.gameManager.BuildingManager;
+import engine.net.client.ClientConnection;
+import engine.net.client.msg.ClientNetMsg;
+import engine.net.client.msg.ErrorPopupMsg;
+import engine.net.client.msg.TaxResourcesMsg;
+import engine.objects.Building;
+import engine.objects.City;
+import engine.objects.PlayerCharacter;
+
+/*
+ * @Author:
+ * @Summary: Processes application protocol message which handles
+ * protecting and unprotecting city assets
+ */
+public class TaxResourcesMsgHandler extends AbstractClientMsgHandler {
+
+	public TaxResourcesMsgHandler() {
+		super(TaxResourcesMsg.class);
+	}
+
+	@Override
+	protected boolean _handleNetMsg(ClientNetMsg baseMsg, ClientConnection origin) throws MsgSendException {
+
+		// Member variable declaration
+
+		PlayerCharacter player;
+		TaxResourcesMsg msg;
+
+		player = origin.getPlayerCharacter();
+		if (player == null)
+			return true;
+
+
+		msg = (TaxResourcesMsg) baseMsg;
+
+		TaxWarehouse(msg,player);
+
+
+
+		return true;
+
+	}
+
+	private static boolean TaxWarehouse(TaxResourcesMsg msg, PlayerCharacter player) {
+
+		// Member variable declaration
+		Building building = BuildingManager.getBuildingFromCache(msg.getBuildingID());
+
+
+		if (building == null){
+			ErrorPopupMsg.sendErrorMsg(player, "Not a valid Building!");
+			return true;
+		}
+
+		City city = building.getCity();
+		if (city == null){
+			ErrorPopupMsg.sendErrorMsg(player, "This building does not belong to a city.");
+			return true;
+		}
+
+		city.TaxWarehouse(msg, player);
+
+
+		return true;
+
+
+	}
+
+
+}
diff --git a/src/engine/net/client/handlers/ToggleGroupSplitHandler.java b/src/engine/net/client/handlers/ToggleGroupSplitHandler.java
new file mode 100644
index 00000000..39eefc2d
--- /dev/null
+++ b/src/engine/net/client/handlers/ToggleGroupSplitHandler.java
@@ -0,0 +1,71 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.handlers;
+
+import engine.exception.MsgSendException;
+import engine.gameManager.ChatManager;
+import engine.gameManager.GroupManager;
+import engine.gameManager.SessionManager;
+import engine.net.client.ClientConnection;
+import engine.net.client.msg.ClientNetMsg;
+import engine.net.client.msg.group.GroupUpdateMsg;
+import engine.net.client.msg.group.ToggleGroupSplitMsg;
+import engine.objects.Group;
+import engine.objects.PlayerCharacter;
+
+public class ToggleGroupSplitHandler extends AbstractClientMsgHandler {
+
+    public ToggleGroupSplitHandler() {
+        super(ToggleGroupSplitMsg.class);
+    }
+
+    @Override
+    protected boolean _handleNetMsg(ClientNetMsg baseMsg,
+            ClientConnection origin) throws MsgSendException {
+
+        // Member variable declaration
+        
+        PlayerCharacter source;
+        Group group;
+        boolean split;
+        
+        source = SessionManager.getPlayerCharacter(origin);
+        
+        if (source == null)
+            return false;
+
+        group = GroupManager.getGroup(source);
+        
+        if (group == null)
+            return false;
+
+        if (group.getGroupLead() != source) // Only group lead can toggle
+            return false;
+
+        split = group.toggleSplitGold();
+
+        // update split button
+        GroupUpdateMsg gum = new GroupUpdateMsg();
+        gum.setGroup(group);
+        gum.setMessageType(6);
+
+        group.sendUpdate(gum);
+
+        // Send split message
+        
+        if (split)
+            ChatManager.chatGroupInfo(source, "Treasure is now being split.");
+         else 
+            ChatManager.chatGroupInfo(source, "Treasure is no longer being split.");
+
+        return false;
+    }
+
+}
diff --git a/src/engine/net/client/handlers/TransferAssetMsgHandler.java b/src/engine/net/client/handlers/TransferAssetMsgHandler.java
new file mode 100644
index 00000000..e0be0a85
--- /dev/null
+++ b/src/engine/net/client/handlers/TransferAssetMsgHandler.java
@@ -0,0 +1,99 @@
+package engine.net.client.handlers;
+
+import engine.Enum;
+import engine.exception.MsgSendException;
+import engine.gameManager.BuildingManager;
+import engine.gameManager.ChatManager;
+import engine.net.client.ClientConnection;
+import engine.net.client.msg.ClientNetMsg;
+import engine.net.client.msg.ErrorPopupMsg;
+import engine.net.client.msg.TransferAssetMsg;
+import engine.objects.Blueprint;
+import engine.objects.Building;
+import engine.objects.PlayerCharacter;
+
+/*
+ * @Author:
+ * @Summary: Processes application protocol message which transers
+ * assets between characters.
+ */
+
+public class TransferAssetMsgHandler extends AbstractClientMsgHandler {
+
+	public TransferAssetMsgHandler() {
+		super(TransferAssetMsg.class);
+	}
+
+	@Override
+	protected boolean _handleNetMsg(ClientNetMsg baseMsg, ClientConnection origin) throws MsgSendException {
+
+		TransferAssetMsg transferAssetMsg = (TransferAssetMsg) baseMsg;
+
+		int Buildingid = transferAssetMsg.getObjectID();
+		int BuildingType = transferAssetMsg.getObjectType(); //ToDue Later
+		int TargetID = transferAssetMsg.getTargetID();
+		int TargetType = transferAssetMsg.getTargetType();  //ToDue later
+
+		Building building = BuildingManager.getBuildingFromCache(Buildingid);
+		PlayerCharacter newOwner = PlayerCharacter.getFromCache(TargetID);
+		PlayerCharacter player = origin.getPlayerCharacter();
+
+		if (player == null || building == null || newOwner == null)
+			return true;
+
+		Blueprint blueprint = building.getBlueprint();
+
+		if (blueprint == null)
+			return true;
+
+		if (building.getBlueprint().getBuildingGroup() == Enum.BuildingGroup.MINE) {
+			ErrorPopupMsg.sendErrorMsg(player, "You cannot transfer a mine!");
+			return true;
+		}
+
+		// Players cannot transfer shrines
+
+		if ((building.getBlueprint().getBuildingGroup() == Enum.BuildingGroup.SHRINE)) {
+			ErrorPopupMsg.sendErrorMsg(player, "Cannot for to transfer shrine!");
+			return true;
+		}
+
+		if (Blueprint.isMeshWallPiece(building.getBlueprintUUID())) {
+			ErrorPopupMsg.sendErrorMsg(player, "Cannot for to transfer fortress asset!");
+			return true;
+		}
+
+		if ((building.getBlueprint().getBuildingGroup() == Enum.BuildingGroup.BARRACK)) {
+			ErrorPopupMsg.sendErrorMsg(player, "Cannot for to transfer fortress asset!");
+			return true;
+		}
+
+		if ((building.getBlueprint().getBuildingGroup() == Enum.BuildingGroup.BULWARK)) {
+			ErrorPopupMsg.sendErrorMsg(player, "Cannot for to transfer siege asset!");
+			return true;
+		}
+
+		if ((building.getBlueprint().getBuildingGroup() == Enum.BuildingGroup.SIEGETENT)) {
+			ErrorPopupMsg.sendErrorMsg(player, "Cannot for to transfer siege asset!");
+			return true;
+		}
+
+		if ((building.getBlueprint().getBuildingGroup() == Enum.BuildingGroup.BANESTONE)) {
+			ErrorPopupMsg.sendErrorMsg(player, "Cannot for to transfer banestone!");
+			return true;
+		}
+		if (building.getOwnerUUID() != player.getObjectUUID()) {
+			ChatManager.chatSystemError(player, "You do not own this asset.");
+			return true;
+		}
+
+		if (building.getOwnerUUID() == newOwner.getObjectUUID()) {
+			ChatManager.chatSystemError(player, "You already own this asset.");
+			return true;
+		}
+
+		building.setOwner(newOwner);
+		return true;
+	}
+
+}
\ No newline at end of file
diff --git a/src/engine/net/client/handlers/TransferGoldToFromBuildingMsgHandler.java b/src/engine/net/client/handlers/TransferGoldToFromBuildingMsgHandler.java
new file mode 100644
index 00000000..e5cc9aed
--- /dev/null
+++ b/src/engine/net/client/handlers/TransferGoldToFromBuildingMsgHandler.java
@@ -0,0 +1,115 @@
+package engine.net.client.handlers;
+
+import engine.Enum;
+import engine.Enum.DispatchChannel;
+import engine.exception.MsgSendException;
+import engine.gameManager.BuildingManager;
+import engine.gameManager.SessionManager;
+import engine.net.Dispatch;
+import engine.net.DispatchMessage;
+import engine.net.client.ClientConnection;
+import engine.net.client.msg.ClientNetMsg;
+import engine.net.client.msg.PlaceAssetMsg;
+import engine.net.client.msg.TransferGoldToFromBuildingMsg;
+import engine.net.client.msg.UpdateGoldMsg;
+import engine.objects.Building;
+import engine.objects.CharacterItemManager;
+import engine.objects.Item;
+import engine.objects.PlayerCharacter;
+import org.pmw.tinylog.Logger;
+
+/*
+ * @Author:
+ * @Summary: Processes application protocol message which transfers
+ * gold between a building's strongbox and a player character.
+ */
+
+public class TransferGoldToFromBuildingMsgHandler extends AbstractClientMsgHandler {
+
+	public TransferGoldToFromBuildingMsgHandler() {
+		super(TransferGoldToFromBuildingMsg.class);
+	}
+
+	@Override
+	protected boolean _handleNetMsg(ClientNetMsg baseMsg, ClientConnection origin) throws MsgSendException {
+
+		PlayerCharacter player;
+		Building building;
+		CharacterItemManager itemMan;
+		Item goldItem;
+		TransferGoldToFromBuildingMsg msg;
+		Dispatch dispatch;
+
+		player = SessionManager.getPlayerCharacter(origin);
+
+		if (player == null)
+			return true;
+
+		msg = (TransferGoldToFromBuildingMsg) baseMsg;
+
+		building =  BuildingManager.getBuildingFromCache(msg.getObjectID());
+
+		if (building == null)
+			return true;
+
+		if (msg.getDirection() == 2){
+
+			if(!ManageCityAssetMsgHandler.playerCanManageNotFriends(player, building))
+				return true;
+			if (building.setReserve(msg.getUnknown01(),player)){
+				 dispatch = Dispatch.borrow(player, msg);
+				DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
+			}
+			
+			return true;
+		}
+
+		//        if (building.getTimeStamp(MBServerStatics.STRONGBOX_DELAY_STRING) > System.currentTimeMillis()){
+		//        	ErrorPopupMsg.sendErrorMsg(player, MBServerStatics.STRONGBOX_DELAY_OUTPUT);
+		//        	return true;
+		//        }
+
+		//building.getTimestamps().put(MBServerStatics.STRONGBOX_DELAY_STRING, System.currentTimeMillis() + MBServerStatics.ONE_MINUTE);
+
+		itemMan = player.getCharItemManager();
+
+		goldItem = itemMan.getGoldInventory();
+
+		if (goldItem == null) {
+			Logger.error("Could not access gold item");
+			return true;
+		}
+
+
+		// Update in-game gold values for player and building
+
+
+		try {
+
+
+			if (!itemMan.transferGoldToFromBuilding(msg.getAmount(), building))
+				return true;
+
+
+			UpdateGoldMsg ugm = new UpdateGoldMsg(player);
+			ugm.configure();
+			dispatch = Dispatch.borrow(player, ugm);
+			DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+
+			// Refresh the player's inventory if it's currently open
+
+			// Refresh the tree's window to update strongbox
+
+
+			msg.setAmount(building.getStrongboxValue());
+			dispatch = Dispatch.borrow(player, msg);
+			DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+
+		} catch (Exception e) {
+			PlaceAssetMsg.sendPlaceAssetError(player.getClientConnection(), 1, "A Serious error has occurred. Please post details for to ensure transaction integrity");
+		}
+
+		return true;
+	}
+
+}
diff --git a/src/engine/net/client/handlers/UpdateFriendStatusHandler.java b/src/engine/net/client/handlers/UpdateFriendStatusHandler.java
new file mode 100644
index 00000000..cba23748
--- /dev/null
+++ b/src/engine/net/client/handlers/UpdateFriendStatusHandler.java
@@ -0,0 +1,84 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.handlers;
+
+import engine.Enum.DispatchChannel;
+import engine.Enum.FriendStatus;
+import engine.exception.MsgSendException;
+import engine.gameManager.SessionManager;
+import engine.net.Dispatch;
+import engine.net.DispatchMessage;
+import engine.net.client.ClientConnection;
+import engine.net.client.msg.ClientNetMsg;
+import engine.net.client.msg.UpdateFriendStatusMessage;
+import engine.objects.PlayerCharacter;
+import engine.objects.PlayerFriends;
+import org.pmw.tinylog.Logger;
+
+import java.util.HashSet;
+
+public class UpdateFriendStatusHandler extends AbstractClientMsgHandler {
+
+	public UpdateFriendStatusHandler() {
+		super(UpdateFriendStatusMessage.class);
+	}
+
+	@Override
+	protected boolean _handleNetMsg(ClientNetMsg baseMsg,
+			ClientConnection origin) throws MsgSendException {
+		
+		PlayerCharacter player = origin.getPlayerCharacter();
+		
+		if (player == null)
+			return true;
+		
+
+		UpdateFriendStatusMessage msg = (UpdateFriendStatusMessage)baseMsg;
+		
+		
+			HandleUpdateFriend(player,msg);
+			
+		return true;
+	}
+	
+
+	//change to Request
+	public static void HandleUpdateFriend(PlayerCharacter player, UpdateFriendStatusMessage msg){
+	FriendStatus friendStatus = FriendStatus.Available;
+	
+	try {
+		friendStatus = FriendStatus.values()[msg.statusType];
+	}catch (Exception e){
+		Logger.error(e);
+	}
+	player.friendStatus = friendStatus;
+	SendUpdateToFriends(player);
+	}
+	
+	public static void SendUpdateToFriends(PlayerCharacter player){
+		
+		HashSet<Integer> friends = PlayerFriends.PlayerFriendsMap.get(player.getObjectUUID());
+		
+		if (friends == null)
+			return;
+		
+		UpdateFriendStatusMessage outMsg = new UpdateFriendStatusMessage(player);
+		
+		for (int friendID : friends){
+			PlayerCharacter playerFriend = SessionManager.getPlayerCharacterByID(friendID);
+			if (playerFriend == null)
+				return;
+			Dispatch dispatch = Dispatch.borrow(playerFriend, outMsg);
+			DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
+		}
+		
+		
+	}
+}
diff --git a/src/engine/net/client/handlers/UpgradeAssetMsgHandler.java b/src/engine/net/client/handlers/UpgradeAssetMsgHandler.java
new file mode 100644
index 00000000..3f3d0c9c
--- /dev/null
+++ b/src/engine/net/client/handlers/UpgradeAssetMsgHandler.java
@@ -0,0 +1,154 @@
+package engine.net.client.handlers;
+
+import engine.Enum;
+import engine.Enum.GameObjectType;
+import engine.exception.MsgSendException;
+import engine.gameManager.BuildingManager;
+import engine.gameManager.DbManager;
+import engine.gameManager.SessionManager;
+import engine.net.Dispatch;
+import engine.net.DispatchMessage;
+import engine.net.client.ClientConnection;
+import engine.net.client.msg.*;
+import engine.objects.Building;
+import engine.objects.PlayerCharacter;
+import org.pmw.tinylog.Logger;
+
+import java.time.LocalDateTime;
+
+import static engine.net.client.msg.ErrorPopupMsg.sendErrorPopup;
+
+/*
+ *
+ * @Summary: Processes application protocol message where a
+ * client requests that a building be upgraded.
+ */
+public class UpgradeAssetMsgHandler extends AbstractClientMsgHandler {
+
+	// Constructor
+	public UpgradeAssetMsgHandler() {
+
+		super(UpgradeAssetMessage.class);
+	}
+
+	@Override
+	protected boolean _handleNetMsg(ClientNetMsg baseMsg, ClientConnection origin) throws MsgSendException {
+
+		// Member variable declaration
+
+		UpgradeAssetMessage msg;
+		ManageCityAssetsMsg outMsg;
+		PlayerCharacter player;
+		int buildingUUID;
+		Building buildingToRank;
+		LocalDateTime dateToUpgrade;
+		int nextRank;
+		int rankCost;
+		Dispatch dispatch;
+
+		// Assign member variables
+
+		msg = (UpgradeAssetMessage) baseMsg;
+
+		// Grab pointer to the requesting player
+
+		player = SessionManager.getPlayerCharacter(origin);
+
+		// Grab pointer to the building from the cache
+
+		buildingUUID = msg.getBuildingUUID();
+
+		buildingToRank = (Building) DbManager.getObject(GameObjectType.Building, buildingUUID);
+
+		// Early exit if building not in cache.
+
+		if (buildingToRank == null) {
+			Logger.error("Attempt to upgrade null building by " + player.getName());
+			return true;
+		}
+
+		// Early exit for building that is already ranking
+
+		if (buildingToRank.isRanking()) {
+			Logger.error("Attempt to upgrade a building already ranking by " + player.getName());
+			return true;
+		}
+
+		// Calculate and set time/cost to upgrade
+
+		nextRank = (buildingToRank.getRank() + 1);
+
+		if (buildingToRank.getBlueprint() == null)
+			return true;
+		if (buildingToRank.getBlueprint().getMaxRank() < nextRank || nextRank == 8){
+			ErrorPopupMsg.sendErrorMsg(player, "Building is already at it's Max rank.");
+			return true;
+		}
+
+		rankCost = buildingToRank.getBlueprint().getRankCost(nextRank);
+
+		// SEND NOT ENOUGH GOLD ERROR
+
+		if (!buildingToRank.hasFunds(rankCost)){
+			ErrorPopupMsg.sendErrorPopup(player, 127); // Not enough gold in strongbox
+			return true;
+		}
+
+		if (rankCost > buildingToRank.getStrongboxValue()) {
+			sendErrorPopup(player, 127);
+			return true;
+		}
+
+		// Validation appears good.  Let's now process the upgrade
+		
+		try {
+			if (buildingToRank.getCity() != null){
+				buildingToRank.getCity().transactionLock.writeLock().lock();
+				try{
+					if (!buildingToRank.transferGold(-rankCost,false)) {
+						sendErrorPopup(player, 127);
+						return true;
+					}
+				}catch(Exception e){
+					Logger.error(e);
+				}finally{
+					buildingToRank.getCity().transactionLock.writeLock().unlock();
+				}
+			}else
+			if (!buildingToRank.transferGold(-rankCost,false)) {
+				sendErrorPopup(player, 127);
+				return true;
+			}
+
+			dateToUpgrade = LocalDateTime.now().plusHours(buildingToRank.getBlueprint().getRankTime(nextRank));
+
+			BuildingManager.setUpgradeDateTime(buildingToRank, dateToUpgrade, 0);
+
+			// Schedule upgrade job
+
+			BuildingManager.submitUpgradeJob(buildingToRank);
+
+			// Refresh the client's manage asset window
+			// *** Refactor : We have some of these unknowns
+
+			outMsg = new ManageCityAssetsMsg(player, buildingToRank);
+
+			// Action TYPE
+			outMsg.actionType = 3;
+			outMsg.setTargetType(buildingToRank.getObjectType().ordinal());
+			outMsg.setTargetID(buildingToRank.getObjectUUID());
+			outMsg.setTargetType3(buildingToRank.getObjectType().ordinal());
+			outMsg.setTargetID3(buildingToRank.getObjectUUID());
+			outMsg.setAssetName1(buildingToRank.getName());
+			outMsg.setUnknown54(1);
+
+			dispatch = Dispatch.borrow(player, outMsg);
+			DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+
+		} catch (Exception e) {
+			PlaceAssetMsg.sendPlaceAssetError(player.getClientConnection(), 1, "A Serious error has occurred. Please post details for to ensure transaction integrity");
+		}
+
+		return true;
+	}
+}
diff --git a/src/engine/net/client/msg/AbandonAssetMsg.java b/src/engine/net/client/msg/AbandonAssetMsg.java
new file mode 100644
index 00000000..38bfb67e
--- /dev/null
+++ b/src/engine/net/client/msg/AbandonAssetMsg.java
@@ -0,0 +1,75 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+import engine.exception.SerializationException;
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+
+
+public class AbandonAssetMsg extends ClientNetMsg {
+
+    private int pad = 0;
+    private int objectType;
+    private int objectUUID;
+
+    /**
+     * This constructor is used by NetMsgFactory. It attempts to deserialize the
+     * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+     * past the limit) then this constructor Throws that Exception to the
+     * caller.
+     */
+    public AbandonAssetMsg(AbstractConnection origin, ByteBufferReader reader){
+        super(Protocol.ABANDONASSET, origin, reader);
+    }
+
+    /**
+     * Deserializes the subclass specific items from the supplied NetMsgReader.
+     */
+    @Override
+    protected void _deserialize(ByteBufferReader reader)  {
+        this.pad = reader.getInt();
+        this.objectType = reader.getInt();
+        this.objectUUID = reader.getInt();
+    }
+
+    /**
+     * Serializes the subclass specific items to the supplied NetMsgWriter.
+     */
+    @Override
+    protected void _serialize(ByteBufferWriter writer) throws SerializationException {
+        writer.putInt(this.pad);
+        writer.putInt(this.objectType);
+        writer.putInt(this.objectUUID);
+    }
+
+    public int getObjectType() {
+        return objectType;
+    }
+
+    public void setObjectType(int value) {
+        this.objectType = value;
+    }
+
+    public void setPad(int value) {
+        this.pad = value;
+    }
+
+    public int getUUID() {
+        return objectUUID;
+
+    }
+
+    public int getPad() {
+        return pad;
+    }
+}
diff --git a/src/engine/net/client/msg/AcceptFriendMsg.java b/src/engine/net/client/msg/AcceptFriendMsg.java
new file mode 100644
index 00000000..c0344689
--- /dev/null
+++ b/src/engine/net/client/msg/AcceptFriendMsg.java
@@ -0,0 +1,65 @@
+/*
+HashSet<Integer> playerFriendSet = PlayerFriendsMap.get(playerUID);
+			playerFriendSet.add(friendUID); * Copyright 2013 MagicBane Emulator Project
+ * All Rights Reserved
+ */
+package engine.net.client.msg;
+
+
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.objects.PlayerCharacter;
+
+
+public class AcceptFriendMsg extends ClientNetMsg {
+
+	public String sourceName;
+	public String friendName;
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public AcceptFriendMsg(PlayerCharacter pc) {
+		super(Protocol.FRIENDACCEPT);
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public AcceptFriendMsg(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.FRIENDACCEPT, origin, reader);
+	}
+
+	/**
+	 * Copy constructor
+	 */
+	public AcceptFriendMsg(AcceptFriendMsg msg) {
+		super(Protocol.FRIENDACCEPT);
+	}
+
+	
+	
+	/**
+	 * Deserializes the subclass specific items from the supplied ByteBufferReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		//Do we even want to try this?
+		this.sourceName = reader.getString(); //This is source name.. this friends list must never been updated since launch, not using IDS.
+		this.friendName = reader.getString();
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied ByteBufferWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+	writer.putString(this.sourceName);
+	writer.putString(this.friendName);
+	}
+}
diff --git a/src/engine/net/client/msg/AcceptTradeRequestMsg.java b/src/engine/net/client/msg/AcceptTradeRequestMsg.java
new file mode 100644
index 00000000..2e2f3344
--- /dev/null
+++ b/src/engine/net/client/msg/AcceptTradeRequestMsg.java
@@ -0,0 +1,133 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+
+import engine.exception.SerializationException;
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.objects.AbstractGameObject;
+
+/**
+ * Accept trade request
+ *
+ * @author Eighty
+ */
+public class AcceptTradeRequestMsg extends ClientNetMsg {
+
+    private int unknown01; //pad?
+    private int playerType;
+    private int playerID;
+    private int targetType;
+    private int targetID;
+  
+
+    /**
+     * This is the general purpose constructor
+     */
+    public AcceptTradeRequestMsg(int unknown01, AbstractGameObject player, AbstractGameObject target) {
+        super(Protocol.REQUESTTRADEOK);
+        this.unknown01 = unknown01;
+        this.playerType = player.getObjectType().ordinal();
+        this.playerID =player.getObjectUUID();
+        this.targetType = target.getObjectType().ordinal();
+        this.targetID =target.getObjectUUID();
+    }
+
+    /**
+     * This constructor is used by NetMsgFactory. It attempts to deserialize the
+     * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+     * past the limit) then this constructor Throws that Exception to the
+     * caller.
+     */
+    public AcceptTradeRequestMsg(AbstractConnection origin, ByteBufferReader reader)
+             {
+        super(Protocol.REQUESTTRADEOK, origin, reader);
+    }
+
+    /**
+     * Deserializes the subclass specific items to the supplied NetMsgWriter.
+     */
+    @Override
+    protected void _deserialize(ByteBufferReader reader)
+             {
+        unknown01 = reader.getInt();
+        playerType = reader.getInt();
+        playerID = reader.getInt();
+        targetType = reader.getInt();
+        targetID = reader.getInt();
+      
+    }
+
+    /**
+     * Serializes the subclass specific items from the supplied NetMsgReader.
+     */
+    @Override
+    protected void _serialize(ByteBufferWriter writer)
+            throws SerializationException {
+        writer.putInt(unknown01);
+        writer.putInt(playerType);
+        writer.putInt(playerID);
+        writer.putInt(targetType);
+        writer.putInt(targetID);
+    }
+
+    /**
+     * @return the unknown01
+     */
+    public int getUnknown01() {
+        return unknown01;
+    }
+
+    /**
+     * @param unknown01 the unknown01 to set
+     */
+    public void setUnknown01(int unknown01) {
+        this.unknown01 = unknown01;
+    }
+
+  
+	
+
+	public int getPlayerType() {
+		return playerType;
+	}
+
+	public void setPlayerType(int playerType) {
+		this.playerType = playerType;
+	}
+
+	public int getPlayerID() {
+		return playerID;
+	}
+
+	public void setPlayerID(int playerID) {
+		this.playerID = playerID;
+	}
+
+	public int getTargetType() {
+		return targetType;
+	}
+
+	public void setTargetType(int targetType) {
+		this.targetType = targetType;
+	}
+
+	public int getTargetID() {
+		return targetID;
+	}
+
+	public void setTargetID(int targetID) {
+		this.targetID = targetID;
+	}
+
+}
diff --git a/src/engine/net/client/msg/AckBankWindowOpenedMsg.java b/src/engine/net/client/msg/AckBankWindowOpenedMsg.java
new file mode 100644
index 00000000..c33d0a29
--- /dev/null
+++ b/src/engine/net/client/msg/AckBankWindowOpenedMsg.java
@@ -0,0 +1,90 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+
+import engine.exception.SerializationException;
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.objects.PlayerCharacter;
+
+/**
+ * Bank window opened
+ *
+ * @author Burfo
+ */
+public class AckBankWindowOpenedMsg extends ClientNetMsg {
+
+	private int playerType;
+	private int playerID;
+	private long unknown01; // possibly NPC ID?
+    private long unknown02;
+
+    /**
+     * This is the general purpose constructor.
+     */
+    public AckBankWindowOpenedMsg(PlayerCharacter pc, long unknown01, long unknown02) {
+        super(Protocol.COSTTOOPENBANK);
+        this.playerType = pc.getObjectType().ordinal();
+        this.playerID = pc.getObjectUUID();
+        this.unknown01 = unknown01;
+        this.unknown02 = unknown02;
+    }
+
+    /**
+     * This constructor is used by NetMsgFactory. It attempts to deserialize the
+     * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+     * past the limit) then this constructor Throws that Exception to the
+     * caller.
+     */
+    public AckBankWindowOpenedMsg(AbstractConnection origin, ByteBufferReader reader)  {
+        super(Protocol.COSTTOOPENBANK, origin, reader);
+    }
+
+    /**
+     * Deserializes the subclass specific items to the supplied NetMsgWriter.
+     */
+    @Override
+    protected void _deserialize(ByteBufferReader reader)  {
+    	playerType = reader.getInt();
+    	playerID = reader.getInt();
+        unknown01 = reader.getLong();
+        unknown02 = reader.getLong();
+    }
+
+    /**
+     * Serializes the subclass specific items from the supplied NetMsgReader.
+     */
+    @Override
+    protected void _serialize(ByteBufferWriter writer) throws SerializationException {
+    	writer.putInt(playerType);
+    	writer.putInt(playerID);
+        writer.putLong(unknown01);
+        writer.putLong(unknown02);
+    }
+    public int getPlayerID() {
+		return playerID;
+	}
+
+	public void setPlayerID(int playerID) {
+		this.playerID = playerID;
+	}
+
+	public int getPlayerType() {
+		return playerType;
+	}
+
+	public void setPlayerType(int playerType) {
+		this.playerType = playerType;
+	}
+
+}
diff --git a/src/engine/net/client/msg/ActivateNPCMessage.java b/src/engine/net/client/msg/ActivateNPCMessage.java
new file mode 100644
index 00000000..af423ff3
--- /dev/null
+++ b/src/engine/net/client/msg/ActivateNPCMessage.java
@@ -0,0 +1,159 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+
+import engine.net.AbstractConnection;
+import engine.net.AbstractNetMsg;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.objects.Item;
+
+import java.util.ArrayList;
+
+public class ActivateNPCMessage extends ClientNetMsg {
+
+    private ArrayList<Item> ItemList;
+    private int unknown01;
+    private int unknown02;
+    private int buildingUUID;
+    private int unknown03;
+    private int unknown04;
+    private int unknown05;
+    private int size;
+
+    /**
+     * This is the general purpose constructor.
+     */
+    public ActivateNPCMessage() {
+        this(new ArrayList<>());
+    }
+
+    /**
+     * This is the general purpose constructor.
+     *
+     */
+    public ActivateNPCMessage(ArrayList<Item> items) {
+        super(Protocol.ACTIVATENPC);
+        this.unknown01 = 0;
+        this.size = 0;
+        this.ItemList = items;
+    }
+
+    /**
+     * This constructor is used by NetMsgFactory. It attempts to deserialize the
+     * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+     * past the limit) then this constructor Throws that Exception to the
+     * caller.
+     */
+    public ActivateNPCMessage(AbstractConnection origin, ByteBufferReader reader)  {
+        super(Protocol.ACTIVATENPC, origin, reader);
+    }
+
+    /**
+     * @see AbstractNetMsg#getPowerOfTwoBufferSize()
+     */
+    @Override
+    protected int getPowerOfTwoBufferSize() {
+        //Larger size for historically larger opcodes
+        return (16); // 2^16 == 64k
+    }
+
+    /**
+     * Serializes the subclass specific items to the supplied NetMsgWriter.
+     */
+    @Override
+    protected void _serialize(ByteBufferWriter writer) {
+        for (int i = 0; i < 6; i++) {
+            writer.putInt(0);
+        }
+        writer.putInt(this.size);
+        for (Item item : this.ItemList) {
+        	writer.putInt(item.getObjectType().ordinal());
+        	writer.putInt(item.getObjectUUID());
+           
+        }
+    }
+
+    /**
+     * Deserializes the subclass specific items from the supplied NetMsgReader.
+     */
+    @Override
+    protected void _deserialize(ByteBufferReader reader)  {
+        unknown01 = reader.getInt();
+        unknown02 = reader.getInt();
+        reader.getInt(); // Object Type Padding
+        buildingUUID = reader.getInt();
+        unknown03 = reader.getInt();
+        unknown04 = reader.getInt();
+        unknown05 = reader.getInt();
+
+    }
+
+    // TODO fix ArrayList Accessability.
+
+    public int size() {
+        return this.ItemList.size();
+    }
+
+    public int getUnknown01() {
+        return unknown01;
+    }
+
+    public int getUnknown02() {
+        return unknown02;
+    }
+
+    public int getUnknown03() {
+        return unknown03;
+    }
+
+    public int getUnknown04() {
+        return unknown04;
+    }
+
+    public int getUnknown05() {
+        return unknown05;
+    }
+
+    public int buildingUUID() {
+        return buildingUUID;
+    }
+
+    public void setUnknown01(int unknown01) {
+        this.unknown01 = unknown01;
+    }
+
+    public void setSize(int size) {
+        this.size = size;
+    }
+
+    public void setUnknown02(int unknown02) {
+        this.unknown02 = unknown02;
+    }
+
+    public void setUnknown03(int unknown03) {
+        this.unknown03 = unknown03;
+    }
+
+    public void setUnknown04(int unknown04) {
+        this.unknown04 = unknown04;
+    }
+
+    public void setUnknown05(int unknown05) {
+        this.unknown05 = unknown05;
+    }
+
+    public void setItemList(ArrayList<Item> value) {
+        this.ItemList = value;
+    }
+
+}
diff --git a/src/engine/net/client/msg/AddFriendMessage.java b/src/engine/net/client/msg/AddFriendMessage.java
new file mode 100644
index 00000000..9d8d33f1
--- /dev/null
+++ b/src/engine/net/client/msg/AddFriendMessage.java
@@ -0,0 +1,67 @@
+/*
+HashSet<Integer> playerFriendSet = PlayerFriendsMap.get(playerUID);
+			playerFriendSet.add(friendUID); * Copyright 2013 MagicBane Emulator Project
+ * All Rights Reserved
+ */
+package engine.net.client.msg;
+
+
+import engine.Enum.GameObjectType;
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.objects.PlayerCharacter;
+
+
+public class AddFriendMessage extends ClientNetMsg {
+
+	public PlayerCharacter friend;
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public AddFriendMessage(PlayerCharacter pc) {
+		super(Protocol.ADDFRIEND);
+		friend = pc;
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public AddFriendMessage(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.ADDFRIEND, origin, reader);
+	}
+
+	/**
+	 * Copy constructor
+	 */
+	public AddFriendMessage(AddFriendMessage msg) {
+		super(Protocol.ADDFRIEND);
+	}
+
+	
+	
+	/**
+	 * Deserializes the subclass specific items from the supplied ByteBufferReader.
+	 */
+	//message is serialize only, no need for deserialize.
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied ByteBufferWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+	writer.putInt(GameObjectType.PlayerCharacter.ordinal());
+	writer.putInt(friend.getObjectUUID());
+	writer.putString(friend.getName());
+	writer.putInt(0); //possibly available, busy, away?
+	writer.putInt(0);
+	}
+}
diff --git a/src/engine/net/client/msg/AddGoldToTradeWindowMsg.java b/src/engine/net/client/msg/AddGoldToTradeWindowMsg.java
new file mode 100644
index 00000000..462d6334
--- /dev/null
+++ b/src/engine/net/client/msg/AddGoldToTradeWindowMsg.java
@@ -0,0 +1,97 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+
+import engine.exception.SerializationException;
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+
+
+public class AddGoldToTradeWindowMsg extends ClientNetMsg {
+
+    private int unknown01;
+    private long playerCompID;
+    private int amount;
+
+    /**
+     * This is the general purpose constructor
+     */
+    public AddGoldToTradeWindowMsg(int unknown01, long playerCompID, int amount) {
+        super(Protocol.TRADEADDGOLD);
+        this.unknown01 = unknown01;
+        this.playerCompID = playerCompID;
+        this.amount = amount;
+    }
+
+    /**
+     * This constructor is used by NetMsgFactory. It attempts to deserialize the
+     * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+     * past the limit) then this constructor Throws that Exception to the
+     * caller.
+     */
+    public AddGoldToTradeWindowMsg(AbstractConnection origin, ByteBufferReader reader)
+             {
+        super(Protocol.TRADEADDGOLD, origin, reader);
+    }
+
+    /**
+     * Deserializes the subclass specific items to the supplied NetMsgWriter.
+     */
+    @Override
+    protected void _deserialize(ByteBufferReader reader)
+             {
+        unknown01 = reader.getInt();
+        playerCompID = reader.getLong();
+        amount = reader.getInt();
+    }
+
+    /**
+     * Serializes the subclass specific items from the supplied NetMsgReader.
+     */
+    @Override
+    protected void _serialize(ByteBufferWriter writer)
+            throws SerializationException {
+        writer.putInt(unknown01);
+        writer.putLong(playerCompID);
+        writer.putInt(amount);
+    }
+
+    /**
+     * @return the unknown01
+     */
+    public int getUnknown01() {
+        return unknown01;
+    }
+
+    /**
+     * @param unknown01 the unknown01 to set
+     */
+    public void setUnknown01(int unknown01) {
+        this.unknown01 = unknown01;
+    }
+
+    /**
+     * @return the amount
+     */
+    public int getAmount() {
+        return amount;
+    }
+
+    /**
+     * @param amount the amount to set
+     */
+    public void setAmount(int amount) {
+        this.amount = amount;
+    }
+
+}
diff --git a/src/engine/net/client/msg/AddItemToTradeWindowMsg.java b/src/engine/net/client/msg/AddItemToTradeWindowMsg.java
new file mode 100644
index 00000000..5d420d70
--- /dev/null
+++ b/src/engine/net/client/msg/AddItemToTradeWindowMsg.java
@@ -0,0 +1,120 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+
+import engine.exception.SerializationException;
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.objects.AbstractGameObject;
+
+public class AddItemToTradeWindowMsg extends ClientNetMsg {
+
+    private int unknown01;
+    private int playerType;
+    private int playerID;
+    private int itemID;
+    private int itemType;
+ 
+
+    /**
+     * This is the general purpose constructor
+     */
+    public AddItemToTradeWindowMsg(int unknown01, AbstractGameObject player, AbstractGameObject item) {
+        super(Protocol.TRADEADDOBJECT);
+        this.unknown01 = unknown01;
+        this.playerType = player.getObjectType().ordinal();
+        this.playerID = player.getObjectUUID();
+        this.itemType = item.getObjectType().ordinal();
+        this.itemID = item.getObjectUUID();
+    }
+
+    /**
+     * This constructor is used by NetMsgFactory. It attempts to deserialize the
+     * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+     * past the limit) then this constructor Throws that Exception to the
+     * caller.
+     */
+    public AddItemToTradeWindowMsg(AbstractConnection origin, ByteBufferReader reader)
+             {
+        super(Protocol.TRADEADDOBJECT, origin, reader);
+    }
+
+    /**
+     * Deserializes the subclass specific items to the supplied NetMsgWriter.
+     */
+    @Override
+    protected void _deserialize(ByteBufferReader reader)
+             {
+        unknown01 = reader.getInt();
+        playerType = reader.getInt();
+        playerID = reader.getInt();
+        itemType = reader.getInt();
+        itemID = reader.getInt();
+    }
+
+    /**
+     * Serializes the subclass specific items from the supplied NetMsgReader.
+     */
+    @Override
+    protected void _serialize(ByteBufferWriter writer)
+            throws SerializationException {
+        writer.putInt(unknown01);
+        writer.putInt(playerType);
+        writer.putInt(playerID);
+        writer.putInt(itemType);
+        writer.putInt(itemID);
+    }
+
+    /**
+     * @return the unknown01
+     */
+    public int getUnknown01() {
+        return unknown01;
+    }
+
+    /**
+     * @param unknown01 the unknown01 to set
+     */
+    public void setUnknown01(int unknown01) {
+        this.unknown01 = unknown01;
+    }
+
+  
+
+   
+
+	public int getPlayerType() {
+		return playerType;
+	}
+
+	public void setPlayerType(int playerType) {
+		this.playerType = playerType;
+	}
+
+	public int getPlayerID() {
+		return playerID;
+	}
+
+	public void setPlayerID(int playerID) {
+		this.playerID = playerID;
+	}
+
+	public int getItemID() {
+		return itemID;
+	}
+
+	public void setItemID(int itemID) {
+		this.itemID = itemID;
+	}
+
+}
diff --git a/src/engine/net/client/msg/AllianceChangeMsg.java b/src/engine/net/client/msg/AllianceChangeMsg.java
new file mode 100644
index 00000000..a1023b46
--- /dev/null
+++ b/src/engine/net/client/msg/AllianceChangeMsg.java
@@ -0,0 +1,144 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+import engine.Enum.GameObjectType;
+import engine.exception.SerializationException;
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.objects.PlayerCharacter;
+
+
+
+public class AllianceChangeMsg extends ClientNetMsg {
+	public final static int MAKE_ENEMY = 4;
+	public final static int MAKE_ALLY = 6;
+	public final static int REMOVE = 7;
+	public final static byte INFO_SUCCESS = 0;
+	public final static byte ERROR_NOT_RECOMMENDED = 1;
+	public final static byte ERROR_NOT_SAME_GUILD = 2;
+	public final static byte ERROR_NOT_AUTHORIZED = 4;
+	public final static byte ERROR_NOT_SAME_FACTION = 7;
+	public final static byte ERROR_TOO_MANY = 13;
+	public final static byte ERROR_TO0_EARLY = 14;
+	private byte msgType;
+	private int sourceGuildID;
+	private int targetGuildID;
+	private int secondsToWait;
+	private boolean ally;
+
+
+	public AllianceChangeMsg(PlayerCharacter player, int sourceGuildID,int targetGuildID, byte msgType, int secondsToWait) {
+		super(Protocol.ALLIANCECHANGE);
+		this.sourceGuildID = sourceGuildID;
+		this.targetGuildID = targetGuildID;
+		this.msgType = msgType;
+		this.secondsToWait = secondsToWait;
+
+	}
+
+	public AllianceChangeMsg() {
+		super(Protocol.ALLIANCECHANGE);
+	}
+
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public AllianceChangeMsg(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.ALLIANCECHANGE, origin, reader);
+	}
+	//CALL THIS AFTER SANITY CHECKS AND BEFORE UPDATING HEALTH/GOLD.
+
+
+	/**
+	 * Deserializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		this.msgType = reader.get();
+
+		switch (this.msgType){
+		case 1:
+		case 2:
+		case 3:
+		case MAKE_ENEMY:
+		case MAKE_ALLY:
+		case REMOVE:
+		case 5:
+			reader.getInt(); //source guild type;
+			this.sourceGuildID = reader.getInt();
+			reader.getInt();
+			this.targetGuildID = reader.getInt();
+			break;
+
+
+		}
+
+
+
+
+	}
+
+
+	// Precache and configure this message before we serialize it
+
+
+	/**
+	 * Serializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) throws SerializationException {
+		writer.put(this.msgType);
+		if (this.msgType == ERROR_TO0_EARLY)
+			writer.putInt(this.secondsToWait);
+		writer.putInt(GameObjectType.Guild.ordinal());
+		writer.putInt(this.sourceGuildID);
+		writer.putInt(GameObjectType.Guild.ordinal());
+		writer.putInt(this.targetGuildID);
+
+
+
+	}
+
+
+	public int getSecondsToWait() {
+		return secondsToWait;
+	}
+
+	public void setSecondsToWait(int secondsToWait) {
+		this.secondsToWait = secondsToWait;
+	}
+
+	public int getMsgType() {
+		return msgType;
+	}
+
+	public int getSourceGuildID() {
+		return sourceGuildID;
+	}
+
+	public int getTargetGuildID() {
+		return targetGuildID;
+	}
+
+	public boolean isAlly() {
+		return ally;
+	}
+
+	public void setMsgType(byte msgType) {
+		this.msgType = msgType;
+	}
+}
diff --git a/src/engine/net/client/msg/AllyEnemyListMsg.java b/src/engine/net/client/msg/AllyEnemyListMsg.java
new file mode 100644
index 00000000..aa47bb9a
--- /dev/null
+++ b/src/engine/net/client/msg/AllyEnemyListMsg.java
@@ -0,0 +1,122 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+import engine.Enum.GameObjectType;
+import engine.exception.SerializationException;
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.objects.Guild;
+import engine.objects.GuildAlliances;
+import engine.objects.GuildTag;
+import engine.objects.PlayerCharacter;
+
+
+
+public class AllyEnemyListMsg extends ClientNetMsg {
+
+
+	private int guildID;
+
+
+	public AllyEnemyListMsg(PlayerCharacter player) {
+		super(Protocol.ALLYENEMYLIST);
+		this.guildID = player.getGuildUUID();
+
+	}
+
+	public AllyEnemyListMsg() {
+		super(Protocol.ALLYENEMYLIST);
+	}
+
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public AllyEnemyListMsg(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.ALLYENEMYLIST, origin, reader);
+	}
+	//CALL THIS AFTER SANITY CHECKS AND BEFORE UPDATING HEALTH/GOLD.
+
+
+	/**
+	 * Deserializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		reader.getInt();
+		this.guildID = reader.getInt();
+	}
+
+
+	// Precache and configure this message before we serialize it
+
+
+	/**
+	 * Serializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) throws SerializationException {
+
+		writer.putInt(GameObjectType.Guild.ordinal());
+		writer.putInt(this.guildID);
+
+		Guild guild = Guild.getGuild(this.guildID);
+
+		writer.putInt(guild.getAllyList().size());
+
+		for (Guild ally: guild.getAllyList()){
+			writer.putInt(GameObjectType.Guild.ordinal());//guildType
+			writer.putInt(ally.getObjectUUID());//GuildID
+			writer.putString(ally.getName());
+			GuildTag._serializeForDisplay(ally.getGuildTag(),writer);
+			writer.put((byte)0);
+		}
+
+
+		writer.putInt(guild.getEnemyList().size());
+
+		for (Guild enemy: guild.getEnemyList()){
+			writer.putInt(GameObjectType.Guild.ordinal());//guildType
+			writer.putInt(enemy.getObjectUUID());//GuildID
+			writer.putString(enemy.getName());
+			GuildTag._serializeForDisplay(enemy.getGuildTag(),writer);
+			writer.put((byte)1);
+		}
+
+
+		writer.putInt(guild.getRecommendList().size());
+		for (Guild recommended: guild.getRecommendList()){
+
+			GuildAlliances guildAlliance = guild.guildAlliances.get(recommended.getObjectUUID());
+			writer.putInt(GameObjectType.Guild.ordinal());//guildType
+			writer.putInt(recommended.getObjectUUID());//GuildID
+			writer.putString(recommended.getName());
+			GuildTag._serializeForDisplay(recommended.getGuildTag(),writer);
+			writer.put((byte)1); // ?
+			writer.putString(guildAlliance.getRecommender()); // recommender name.
+			writer.put((byte) (guildAlliance.isAlly()?1:0)); //ally 1 enemy 0
+
+		}
+
+		writer.put((byte)1);
+
+	}
+
+	public int getGuildID() {
+		return guildID;
+	}
+
+}
diff --git a/src/engine/net/client/msg/ApplyBuildingEffectMsg.java b/src/engine/net/client/msg/ApplyBuildingEffectMsg.java
new file mode 100644
index 00000000..0aa953e0
--- /dev/null
+++ b/src/engine/net/client/msg/ApplyBuildingEffectMsg.java
@@ -0,0 +1,110 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+
+
+public class ApplyBuildingEffectMsg extends ClientNetMsg {
+
+	protected int unknown01;
+	protected int unknown02;
+	protected int buildingType;
+	protected int buildingID;
+	protected int unknown03;
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public ApplyBuildingEffectMsg() {
+		super(Protocol.VISUALUPDATE);
+	}
+
+	public ApplyBuildingEffectMsg(int unknown01, int unknown02, int buildingType, int buildingID, int unknown03) {
+		super(Protocol.VISUALUPDATE);
+		this.unknown01 = unknown01;
+		this.unknown02 = unknown02;
+		this.buildingType = buildingType;
+		this.buildingID = buildingID;
+		this.unknown03 = unknown03;
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public ApplyBuildingEffectMsg(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.VISUALUPDATE, origin, reader);
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+		writer.putInt(this.unknown01);
+		writer.putInt(this.unknown02);
+		if (this.unknown02 == 0){
+			writer.putInt(this.unknown03);
+			writer.putInt(this.buildingType);
+			writer.putInt(this.buildingID);
+			writer.putInt(0);
+			return;
+		}
+		writer.putInt(this.buildingType);
+		writer.putInt(this.buildingID);
+		writer.putInt(this.unknown03);
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+	}
+
+	public void setUnknown01(int unknown01) {
+		this.unknown01 = unknown01;
+	}
+
+	public void setUnknown02(int unknown02) {
+		this.unknown02 = unknown02;
+	}
+
+	public void setBuildingID(int buildingID) {
+		this.buildingID = buildingID;
+	}
+
+	public void setUnknown03(int unknown03) {
+		this.unknown03 = unknown03;
+	}
+
+	public int getUnknown01() {
+		return unknown01;
+	}
+
+	public int getUnknown02() {
+		return unknown02;
+	}
+
+	public int getBuildingID() {
+		return buildingID;
+	}
+
+	public int getUnknown03() {
+		return unknown03;
+	}
+}
diff --git a/src/engine/net/client/msg/ApplyEffectMsg.java b/src/engine/net/client/msg/ApplyEffectMsg.java
new file mode 100644
index 00000000..1644c011
--- /dev/null
+++ b/src/engine/net/client/msg/ApplyEffectMsg.java
@@ -0,0 +1,264 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.objects.AbstractWorldObject;
+
+public class ApplyEffectMsg extends ClientNetMsg {
+
+    protected int numTrains;
+    protected int effectID;
+    protected int sourceType;
+    protected int sourceID;
+    protected int targetType;
+    protected int targetID;
+
+    protected int unknown02;
+    protected int unknown03;
+    protected int duration;
+    protected int unknown05;
+    protected byte unknown06;
+
+    protected int powerUsedID;
+
+    protected String powerUsedName;
+    private int effectSourceType = 0;
+    private int effectSourceID = 0;
+
+    /**
+     * This is the general purpose constructor.
+     */
+    public ApplyEffectMsg() {
+        super(Protocol.POWERACTION);
+        this.numTrains = 0;
+        this.effectID = 0;
+        this.sourceType = 0;
+        this.sourceID = 0;
+        this.targetType = 0;
+        this.targetID = 0;
+
+        this.unknown02 = 0;
+        this.unknown03 = 0;
+        this.duration = 0;
+        this.unknown05 = 0;
+        this.unknown06 = (byte) 0;
+
+        this.powerUsedID = 0;
+        this.powerUsedName = "";
+    }
+
+    /**
+     * This is the general purpose constructor.
+     */
+    public ApplyEffectMsg(AbstractWorldObject source, AbstractWorldObject target, int numTrains, int effectID, int duration,
+            int powerUsedID, String powerUsedName) {
+        super(Protocol.POWERACTION);
+        this.numTrains = numTrains;
+        this.effectID = effectID;
+
+        if (source != null) {
+            this.sourceType = source.getObjectType().ordinal();
+            this.sourceID = source.getObjectUUID();
+        } else {
+            this.sourceType = 0;
+            this.sourceID = 0;
+        }
+
+        if (target != null) {
+            this.targetType = target.getObjectType().ordinal();
+            this.targetID = target.getObjectUUID();
+        } else {
+            this.targetType = 0;
+            this.targetID = 0;
+        }
+        this.unknown02 = 0;
+        this.unknown03 = 0;
+        this.duration = duration;
+        this.unknown05 = 0;
+        this.unknown06 = (byte) 0;
+
+        this.powerUsedID = powerUsedID;
+        this.powerUsedName = powerUsedName;
+    }
+
+    /**
+     * This constructor is used by NetMsgFactory. It attempts to deserialize the
+     * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+     * past the limit) then this constructor Throws that Exception to the
+     * caller.
+     */
+    public ApplyEffectMsg(AbstractConnection origin, ByteBufferReader reader)  {
+        super(Protocol.POWERACTION, origin, reader);
+    }
+
+    /**
+     * Serializes the subclass specific items to the supplied NetMsgWriter.
+     */
+    @Override
+    protected void _serialize(ByteBufferWriter writer) {
+        writer.putInt(this.numTrains);
+        writer.putInt(this.effectID);
+
+        writer.putInt(this.sourceType);
+        writer.putInt(this.sourceID);
+        writer.putInt(this.targetType);
+        writer.putInt(this.targetID);
+
+        writer.putInt(this.unknown02);
+        writer.putInt(this.unknown03);
+        writer.putInt(this.duration);
+        writer.putInt(this.unknown05);
+        writer.put(this.unknown06);
+
+        if (this.unknown06 == (byte) 1) {
+            writer.putInt(this.effectSourceType);
+            writer.putInt(this.effectSourceID);
+        }
+           
+        	else {
+            writer.putInt(this.powerUsedID);
+        }
+
+        writer.putString(this.powerUsedName);
+    }
+
+    /**
+     * Deserializes the subclass specific items from the supplied NetMsgReader.
+     */
+    @Override
+    protected void _deserialize(ByteBufferReader reader)  {
+        this.numTrains = reader.getInt();
+        this.effectID = reader.getInt();
+
+        this.sourceType = reader.getInt();
+        this.sourceID = reader.getInt();
+        this.targetType = reader.getInt();
+        this.targetID = reader.getInt();
+
+        this.unknown02 = reader.getInt();
+        this.unknown03 = reader.getInt();
+        this.duration = reader.getInt();
+        this.unknown05 = reader.getInt();
+        this.unknown06 = reader.get();
+
+        this.powerUsedID = reader.getInt();
+
+        this.powerUsedName = reader.getString();
+    }
+
+    public int getNumTrains() {
+        return this.numTrains;
+    }
+
+    public int getEffectID() {
+        return this.effectID;
+    }
+
+    public int getSourceType() {
+        return this.sourceType;
+    }
+
+    public int getSourceID() {
+        return this.sourceID;
+    }
+
+    public int getTargetType() {
+        return this.targetType;
+    }
+
+    public int getTargetID() {
+        return this.targetID;
+    }
+
+    public int getUnknown02() {
+        return this.unknown02;
+    }
+
+    public int getUnknown03() {
+        return this.unknown03;
+    }
+
+    public int getDuration() {
+        return this.duration;
+    }
+
+    public int getUnknown05() {
+        return this.unknown05;
+    }
+
+    public byte getUnknown06() {
+        return this.unknown06;
+    }
+
+    public void setNumTrains(int value) {
+        this.numTrains = value;
+    }
+
+    public void setEffectID(int value) {
+        this.effectID = value;
+    }
+
+    public void setSourceType(int value) {
+        this.sourceType = value;
+    }
+
+    public void setSourceID(int value) {
+        this.sourceID = value;
+    }
+
+    public void setTargetType(int value) {
+        this.targetType = value;
+    }
+
+    public void setTargetID(int value) {
+        this.targetID = value;
+    }
+
+    public void setUnknown02(int value) {
+        this.unknown02 = value;
+    }
+
+    public void setUnknown03(int value) {
+        this.unknown03 = value;
+    }
+
+    public void setDuration(int value) {
+        this.duration = value;
+    }
+
+    public void setUnknown05(int value) {
+        this.unknown05 = value;
+    }
+
+    public void setUnknown06(byte value) {
+        this.unknown06 = value;
+    }
+
+    public void setPowerUsedID(int value) {
+        this.powerUsedID = value;
+    }
+
+    public void setPowerUsedName(String value) {
+        this.powerUsedName = value;
+    }
+
+    public void setEffectSourceType(int effectSourceType) {
+		this.effectSourceType = effectSourceType;
+	}
+
+    public void setEffectSourceID(int effectSourceID) {
+		this.effectSourceID = effectSourceID;
+	}
+}
diff --git a/src/engine/net/client/msg/ApplyRuneMsg.java b/src/engine/net/client/msg/ApplyRuneMsg.java
new file mode 100644
index 00000000..5850e67e
--- /dev/null
+++ b/src/engine/net/client/msg/ApplyRuneMsg.java
@@ -0,0 +1,393 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+import engine.Enum;
+import engine.gameManager.DbManager;
+import engine.net.*;
+import engine.net.client.ClientConnection;
+import engine.net.client.Protocol;
+import engine.objects.CharacterRune;
+import engine.objects.PlayerCharacter;
+import engine.objects.RuneBase;
+import engine.objects.RuneBaseAttribute;
+import engine.server.MBServerStatics;
+import org.pmw.tinylog.Logger;
+
+import java.util.ArrayList;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class ApplyRuneMsg extends ClientNetMsg {
+
+	private int targetType;
+	private int targetID;
+	private int removeRuneBase;
+	private int runeBase;
+	private int runeType;
+	private int runeID;
+	private Boolean isPromo;
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public ApplyRuneMsg(int targetType, int targetID, int runeBase, int runeType, int runeID, Boolean isPromo) {
+		super(Protocol.SETRUNE);
+		this.targetType = targetType;
+		this.targetID = targetID;
+		this.runeBase = runeBase;
+		this.runeType = runeType;
+		this.runeID = runeID;
+		this.isPromo = isPromo;
+		this.removeRuneBase = 0;
+	}
+
+	public ApplyRuneMsg(int targetType, int targetID, int removeRuneBase) {
+		super(Protocol.SETRUNE);
+		this.targetType = targetType;
+		this.targetID = targetID;
+		this.runeBase = 0;
+		this.runeType = 0;
+		this.runeID = 0;
+		this.isPromo = false;
+		this.removeRuneBase = removeRuneBase;
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public ApplyRuneMsg(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.SETRUNE, origin, reader);
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+		writer.putInt(this.targetType);
+		writer.putInt(this.targetID);
+		writer.putInt(0);
+		writer.putInt(this.removeRuneBase);
+		writer.putInt(0);
+		writer.putInt(this.runeBase);
+		writer.putInt(this.runeType);
+		writer.putInt(this.runeID);
+		if (this.isPromo) {
+			writer.put((byte) 1);
+		} else {
+			writer.put((byte) 0);
+		}
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		this.targetType = reader.getInt();
+		this.targetID = reader.getInt();
+		reader.getInt();
+		this.removeRuneBase = reader.getInt();
+		reader.getInt();
+		this.runeBase = reader.getInt();
+		this.runeType = reader.getInt();
+		this.runeID = reader.getInt();
+		this.isPromo = (reader.get() == 1) ? true : false;
+	}
+
+	public int getTargetType() {
+		return targetType;
+	}
+
+	public void setTargetType(int value) {
+		this.targetType = value;
+	}
+
+	public int getTargetID() {
+		return targetID;
+	}
+
+	public void setTargetID(int value) {
+		this.targetID = value;
+	}
+
+	public int getRuneID() {
+		return runeID;
+	}
+
+	public void setRuneID(int value) {
+		this.runeID = value;
+	}
+
+	public static boolean applyRune(int runeID, ClientConnection origin, PlayerCharacter playerCharacter) {
+
+		RuneBase rb = RuneBase.getRuneBase(runeID);
+		Dispatch dispatch;
+
+		if (playerCharacter == null || origin == null || rb == null) {
+			return false;
+		}
+
+		//Check race is met
+		ConcurrentHashMap<Integer, Boolean> races = rb.getRace();
+		if (races.size() > 0) {
+			int raceID = playerCharacter.getRaceID();
+			boolean valid = false;
+			for (int validID : races.keySet()) {
+				if (validID == raceID) {
+					valid = true;
+					break;
+				}
+			}
+			if (!valid) {
+				return false;
+			}
+		}
+
+		//Check base class is met
+		ConcurrentHashMap<Integer, Boolean> baseClasses = rb.getBaseClass();
+		if (baseClasses.size() > 0) {
+			int baseClassID = playerCharacter.getBaseClassID();
+			boolean valid = false;
+			for (int validID : baseClasses.keySet()) {
+				if (validID == baseClassID) {
+					valid = true;
+					break;
+				}
+			}
+			if (!valid) {
+				return false;
+			}
+		}
+
+		//Check promotion class is met
+		ConcurrentHashMap<Integer, Boolean> promotionClasses = rb.getPromotionClass();
+		if (promotionClasses.size() > 0) {
+			int promotionClassID = playerCharacter.getPromotionClassID();
+			boolean valid = false;
+			for (int validID : promotionClasses.keySet()) {
+				if (validID == promotionClassID) {
+					valid = true;
+					break;
+				}
+			}
+			if (!valid) {
+				return false;
+			}
+		}
+
+		//Check disciplines are met
+		ArrayList<CharacterRune> runes = playerCharacter.getRunes();
+		ConcurrentHashMap<Integer, Boolean> disciplines = rb.getDiscipline();
+		if (disciplines.size() > 0) {
+			for (CharacterRune cr : runes) {
+				int runeBaseID = cr.getRuneBaseID();
+				for (Integer prohID : disciplines.keySet()) {
+					if (runeBaseID == prohID) {
+						return false; //Prohibited rune
+					}
+				}
+			}
+		}
+
+		int discCount = 0;
+		for (CharacterRune cr : runes) {
+			int runeBaseID = cr.getRuneBaseID();
+			//count number of discipline runes
+			if (runeBaseID > 3000 && runeBaseID < 3049) {
+				discCount++;
+			}
+			//see if rune is already applied
+			if (runeBaseID == runeID) {
+				return false;
+			}
+		}
+
+		//Check level is met
+		if (playerCharacter.getLevel() < rb.getLevelRequired()) {
+			return false;
+		}
+
+		int strTotal = 0;
+		int dexTotal = 0;
+		int conTotal = 0;
+		int intTotal = 0;
+		int spiTotal = 0;
+		int cost = 0;
+
+		//Check any attributes are met
+		ArrayList<RuneBaseAttribute> attrs = rb.getAttrs();
+
+		if (rb.getAttrs() != null)
+			for (RuneBaseAttribute rba : attrs) {
+				int attrID = rba.getAttributeID();
+				int mod = rba.getModValue();
+				switch (attrID) {
+				case MBServerStatics.RUNE_COST_ATTRIBUTE_ID:
+					if (mod > playerCharacter.getUnusedStatPoints()) {
+						return false;
+					}
+					cost = mod;
+					break;
+				case MBServerStatics.RUNE_STR_MIN_NEEDED_ATTRIBUTE_ID:
+					if ((int) playerCharacter.statStrBase < mod) {
+						return false;
+					}
+					strTotal = mod;
+					break;
+				case MBServerStatics.RUNE_DEX_MIN_NEEDED_ATTRIBUTE_ID:
+					if ((int) playerCharacter.statDexBase < mod) {
+						return false;
+					}
+					dexTotal = mod;
+					break;
+				case MBServerStatics.RUNE_CON_MIN_NEEDED_ATTRIBUTE_ID:
+					if ((int) playerCharacter.statConBase < mod) {
+						return false;
+					}
+					conTotal = mod;
+					break;
+				case MBServerStatics.RUNE_INT_MIN_NEEDED_ATTRIBUTE_ID:
+					if ((int) playerCharacter.statIntBase < mod) {
+						return false;
+					}
+					intTotal = mod;
+					break;
+				case MBServerStatics.RUNE_SPI_MIN_NEEDED_ATTRIBUTE_ID:
+					if ((int) playerCharacter.statSpiBase < mod) {
+						return false;
+					}
+					spiTotal = mod;
+					break;
+				case MBServerStatics.RUNE_STR_ATTRIBUTE_ID:
+					strTotal += mod;
+					break;
+				case MBServerStatics.RUNE_DEX_ATTRIBUTE_ID:
+					dexTotal += mod;
+					break;
+				case MBServerStatics.RUNE_CON_ATTRIBUTE_ID:
+					conTotal += mod;
+					break;
+				case MBServerStatics.RUNE_INT_ATTRIBUTE_ID:
+					intTotal += mod;
+					break;
+				case MBServerStatics.RUNE_SPI_ATTRIBUTE_ID:
+					spiTotal += mod;
+					break;
+				}
+			}
+
+		//Check if max number runes already reached
+		if (runes.size() > 12) {
+			return false;
+		}
+
+		//if discipline, check number applied
+		if (isDiscipline(runeID)) {
+			if (playerCharacter.getLevel() < 70) {
+				if (discCount > 2) {
+					return false;
+				}
+			} else {
+				if (discCount > 3) {
+					return false;
+				}
+			}
+		}
+
+		//Everything succeeded. Let's apply the rune
+		//Attempt add rune to database
+		CharacterRune runeWithoutID = new CharacterRune(rb, playerCharacter.getObjectUUID());
+		CharacterRune cr;
+		try {
+			cr = DbManager.CharacterRuneQueries.ADD_CHARACTER_RUNE(runeWithoutID);
+		} catch (Exception e) {
+			cr = null;
+			Logger.error(e);
+		}
+		if (cr == null) {
+			return false;
+		}
+
+		//remove any overridden runes from player
+		ArrayList<Integer> overwrite = rb.getOverwrite();
+		CharacterRune toRemove = null;
+		if (overwrite.size() > 0) {
+			for (int overwriteID : overwrite) {
+				toRemove = playerCharacter.removeRune(overwriteID);
+			}
+		}
+
+		//add rune to player
+		playerCharacter.addRune(cr);
+
+		// recalculate all bonuses/formulas/skills/powers
+		playerCharacter.recalculate();
+
+		//if overwriting a stat rune, add any amount granted from previous rune.
+		if (toRemove != null) {
+			RuneBase rbs = toRemove.getRuneBase();
+			if (rbs != null && rbs.getObjectUUID() > 249999 && rbs.getObjectUUID() < 250045) {
+				//add any additional stats to match old amount
+				int dif = strTotal - (int) playerCharacter.statStrBase;
+				if (dif > 0 && strTotal < (int) playerCharacter.statStrMax) {
+					playerCharacter.addStr(dif);
+				}
+				dif = dexTotal - (int) playerCharacter.statDexBase;
+				if (dif > 0 && dexTotal < (int) playerCharacter.statDexMax) {
+					playerCharacter.addDex(dif);
+				}
+				dif = conTotal - (int) playerCharacter.statConBase;
+				if (dif > 0 && conTotal < (int) playerCharacter.statConMax) {
+					playerCharacter.addCon(dif);
+				}
+				dif = intTotal - (int) playerCharacter.statIntBase;
+				if (dif > 0 && intTotal < (int) playerCharacter.statIntMax) {
+					playerCharacter.addInt(dif);
+				}
+				dif = spiTotal - (int) playerCharacter.statSpiBase;
+				if (dif > 0 && spiTotal < (int) playerCharacter.statSpiMax) {
+					playerCharacter.addSpi(dif);
+				}
+
+				// recalculate all bonuses/formulas/skills/powers
+				playerCharacter.recalculate();
+			}
+		}
+
+		if (cost > 0) {
+			ModifyStatMsg msm = new ModifyStatMsg((0 - cost), 0, 3);
+			dispatch = Dispatch.borrow(playerCharacter, msm);
+			DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+		}
+
+		//send apply rune message to client
+		ApplyRuneMsg arm = new ApplyRuneMsg(playerCharacter.getObjectType().ordinal(), playerCharacter.getObjectUUID(), runeID, cr.getObjectType().ordinal(), cr.getObjectUUID(), false);
+		dispatch = Dispatch.borrow(playerCharacter, arm);
+		DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+
+
+		//alert them of success
+		ErrorPopupMsg.sendErrorPopup(playerCharacter, 160);
+
+		//reapply bonuses
+		playerCharacter.applyBonuses();
+
+		return true;
+	}
+
+	public static boolean isDiscipline(int runeID) {
+
+		return runeID > 3000 && runeID < 3050;
+	}
+}
diff --git a/src/engine/net/client/msg/ArcLoginNotifyMsg.java b/src/engine/net/client/msg/ArcLoginNotifyMsg.java
new file mode 100644
index 00000000..57266e55
--- /dev/null
+++ b/src/engine/net/client/msg/ArcLoginNotifyMsg.java
@@ -0,0 +1,148 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+
+public class ArcLoginNotifyMsg extends ClientNetMsg {
+
+	private int unknown01;
+	private int unknown02;
+	private int unknown03;
+	private short unknown04;
+	private byte unknown05;
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public ArcLoginNotifyMsg() {
+		super(Protocol.ARCLOGINNOTIFY);
+		this.unknown01 = 0x40A5BDB0;
+		this.unknown02 = 0x342AA9F0;
+		this.unknown03 = 0;
+		this.unknown04 = (short) 0;
+		this.unknown05 = (byte) 0;
+
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public ArcLoginNotifyMsg(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.ARCLOGINNOTIFY, origin, reader);
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+		writer.putInt(this.unknown01);
+		writer.putInt(this.unknown02);
+		writer.putInt(this.unknown03);
+		writer.putShort(this.unknown04);
+		writer.put(this.unknown05);
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		this.unknown01 = reader.getInt();
+		this.unknown02 = reader.getInt();
+		this.unknown03 = reader.getInt();
+		this.unknown04 = reader.getShort();
+		this.unknown05 = reader.get();
+	}
+
+	/**
+	 * @return the unknown01
+	 */
+	public int getUnknown01() {
+		return unknown01;
+	}
+
+	/**
+	 * @param unknown01
+	 *            the unknown01 to set
+	 */
+	public void setUnknown01(int unknown01) {
+		this.unknown01 = unknown01;
+	}
+
+	/**
+	 * @return the unknown02
+	 */
+	public int getUnknown02() {
+		return unknown02;
+	}
+
+	/**
+	 * @param unknown02
+	 *            the unknown02 to set
+	 */
+	public void setUnknown02(int unknown02) {
+		this.unknown02 = unknown02;
+	}
+
+	/**
+	 * @return the unknown03
+	 */
+	public int getUnknown03() {
+		return unknown03;
+	}
+
+	/**
+	 * @param unknown03
+	 *            the unknown03 to set
+	 */
+	public void setUnknown03(int unknown03) {
+		this.unknown03 = unknown03;
+	}
+
+	/**
+	 * @return the unknown04
+	 */
+	public short getUnknown04() {
+		return unknown04;
+	}
+
+	/**
+	 * @param unknown04
+	 *            the unknown04 to set
+	 */
+	public void setUnknown04(short unknown04) {
+		this.unknown04 = unknown04;
+	}
+
+	/**
+	 * @return the unknown05
+	 */
+	public byte getUnknown05() {
+		return unknown05;
+	}
+
+	/**
+	 * @param unknown05
+	 *            the unknown05 to set
+	 */
+	public void setUnknown05(byte unknown05) {
+		this.unknown05 = unknown05;
+	}
+
+}
diff --git a/src/engine/net/client/msg/ArcMineChangeProductionMsg.java b/src/engine/net/client/msg/ArcMineChangeProductionMsg.java
new file mode 100644
index 00000000..f6f8a3aa
--- /dev/null
+++ b/src/engine/net/client/msg/ArcMineChangeProductionMsg.java
@@ -0,0 +1,60 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+
+import engine.exception.SerializationException;
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+
+public class ArcMineChangeProductionMsg extends ClientNetMsg {
+
+    private int mineID;
+    private int resourceHash;
+
+    /**
+     * This constructor is used by NetMsgFactory. It attempts to deserialize the
+     * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+     * past the limit) then this constructor Throws that Exception to the
+     * caller.
+     */
+    public ArcMineChangeProductionMsg(AbstractConnection origin, ByteBufferReader reader)  {
+        super(Protocol.ARCMINECHANGEPRODUCTION, origin, reader);
+    }
+
+    /**
+     * Deserializes the subclass specific items from the supplied NetMsgReader.
+     */
+    @Override
+    protected void _deserialize(ByteBufferReader reader)  {
+        this.mineID = reader.getInt();
+        this.resourceHash = reader.getInt();
+    }
+
+    /**
+     * Serializes the subclass specific items to the supplied NetMsgWriter.
+     */
+    @Override
+    protected void _serialize(ByteBufferWriter writer) throws SerializationException {
+        writer.putInt(this.mineID);
+        writer.putInt(this.resourceHash);
+    }
+
+    public int getMineID() {
+        return this.mineID;
+    }
+
+    public int getResourceHash() {
+        return this.resourceHash;
+    }
+
+}
diff --git a/src/engine/net/client/msg/ArcMineWindowAvailableTimeMsg.java b/src/engine/net/client/msg/ArcMineWindowAvailableTimeMsg.java
new file mode 100644
index 00000000..ac95d4ef
--- /dev/null
+++ b/src/engine/net/client/msg/ArcMineWindowAvailableTimeMsg.java
@@ -0,0 +1,110 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+
+import engine.exception.SerializationException;
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.objects.Building;
+import engine.objects.Guild;
+import engine.server.MBServerStatics;
+import org.joda.time.DateTime;
+import org.joda.time.Seconds;
+
+public class ArcMineWindowAvailableTimeMsg extends ClientNetMsg {
+
+	private int buildingUUID;
+	private Building treeOfLife;
+
+	private int currentMineHour;
+	private Seconds secondsLeft;
+	private DateTime lateTime;
+	private int late;
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public ArcMineWindowAvailableTimeMsg(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.ARCMINEWINDOWAVAILABLETIME, origin, reader);
+	}
+
+	public ArcMineWindowAvailableTimeMsg(Building treeOfLife, int timeLeft) {
+		super(Protocol.ARCMINEWINDOWAVAILABLETIME);
+		this.treeOfLife = treeOfLife;
+		this.buildingUUID = treeOfLife.getObjectUUID();
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		reader.getInt();
+		reader.getInt();
+		reader.getInt();
+		reader.getInt(); // Object type padding (We know it's a building)
+		this.buildingUUID = reader.getInt();
+		reader.getInt();
+
+	}
+
+	// Configures and pre-caches values for this message
+	// so everything is already available during serialisation.
+
+	public void configure() {
+
+		 Guild guild;
+	        guild = this.treeOfLife.getGuild();
+
+	        if (guild != null)
+	            currentMineHour = guild.getMineTime();
+
+	        late = MBServerStatics.MINE_LATE_WINDOW;
+	        lateTime = DateTime.now();
+
+	        if (late == 0)
+	            lateTime = lateTime.plusDays(1);
+
+	        lateTime = lateTime.hourOfDay().setCopy(late);
+
+	        late = ((late > 23) ? (late - 24) : late);
+	        secondsLeft = Seconds.secondsBetween(DateTime.now(), lateTime);
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) throws SerializationException {
+
+		writer.putInt(MBServerStatics.MINE_EARLY_WINDOW); //15);
+		writer.putInt(late);
+		writer.putInt(currentMineHour);
+
+		writer.putInt(this.treeOfLife.getObjectType().ordinal());
+		writer.putInt(this.treeOfLife.getObjectUUID());
+
+		writer.putInt((int)secondsLeft.getSeconds());
+	}
+
+	public int getBuildingUUID() {
+		return buildingUUID;
+	}
+
+	public void setBuildingUUID(int buildingUUID) {
+		this.buildingUUID = buildingUUID;
+	}
+}
diff --git a/src/engine/net/client/msg/ArcMineWindowChangeMsg.java b/src/engine/net/client/msg/ArcMineWindowChangeMsg.java
new file mode 100644
index 00000000..a6da69fe
--- /dev/null
+++ b/src/engine/net/client/msg/ArcMineWindowChangeMsg.java
@@ -0,0 +1,70 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+
+import engine.exception.SerializationException;
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+
+public class ArcMineWindowChangeMsg extends ClientNetMsg {
+
+    private int time;
+    private int buildingType;
+    private int buildingID;
+
+    /**
+     * This constructor is used by NetMsgFactory. It attempts to deserialize the
+     * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+     * past the limit) then this constructor Throws that Exception to the
+     * caller.
+     */
+    public ArcMineWindowChangeMsg(AbstractConnection origin, ByteBufferReader reader)  {
+        super(Protocol.ARCMINEWINDOWCHANGE, origin, reader);
+    }
+
+    /**
+     * Deserializes the subclass specific items from the supplied NetMsgReader.
+     */
+    @Override
+    protected void _deserialize(ByteBufferReader reader)  {
+        this.time = reader.getInt();
+        this.buildingType = reader.getInt();
+        this.buildingID = reader.getInt();
+    }
+
+    /**
+     * Serializes the subclass specific items to the supplied NetMsgWriter.
+     */
+    @Override
+    protected void _serialize(ByteBufferWriter writer) throws SerializationException {
+        writer.putInt(this.time);
+        writer.putInt(this.buildingType);
+        writer.putInt(this.buildingID);
+    }
+
+    public int getTime() {
+        return this.time;
+    }
+
+    public int getBuildingID() {
+        return this.buildingID;
+    }
+
+    public void setTime(int value) {
+        this.time = value;
+    }
+
+    public void setBuildingID(int value) {
+        this.buildingID = value;
+    }
+}
diff --git a/src/engine/net/client/msg/ArcOwnedMinesListMsg.java b/src/engine/net/client/msg/ArcOwnedMinesListMsg.java
new file mode 100644
index 00000000..a78b9cd1
--- /dev/null
+++ b/src/engine/net/client/msg/ArcOwnedMinesListMsg.java
@@ -0,0 +1,59 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+
+import engine.exception.SerializationException;
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.objects.Mine;
+
+import java.util.ArrayList;
+
+public class ArcOwnedMinesListMsg extends ClientNetMsg {
+
+    ArrayList<Mine> mineList = new ArrayList<>();
+
+    /**
+     * This constructor is used by NetMsgFactory. It attempts to deserialize the
+     * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+     * past the limit) then this constructor Throws that Exception to the
+     * caller.
+     */
+    public ArcOwnedMinesListMsg(AbstractConnection origin, ByteBufferReader reader)  {
+        super(Protocol.ARCOWNEDMINESLIST, origin, reader);
+    }
+
+    /**
+     * Deserializes the subclass specific items from the supplied NetMsgReader.
+     */
+    @Override
+    protected void _deserialize(ByteBufferReader reader)  {
+        reader.getInt();
+    }
+
+    /**
+     * Serializes the subclass specific items to the supplied NetMsgWriter.
+     */
+    @Override
+    protected void _serialize(ByteBufferWriter writer) throws SerializationException {
+        writer.putInt(mineList.size());
+        for (Mine mine : mineList) {
+            mine.serializeForMineProduction(writer);
+        }
+    }
+
+    public void setMineList(ArrayList<Mine> mineList) {
+        this.mineList = mineList;
+    }
+
+}
diff --git a/src/engine/net/client/msg/ArcSiegeSpireMsg.java b/src/engine/net/client/msg/ArcSiegeSpireMsg.java
new file mode 100644
index 00000000..817e038c
--- /dev/null
+++ b/src/engine/net/client/msg/ArcSiegeSpireMsg.java
@@ -0,0 +1,60 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+
+package engine.net.client.msg;
+
+
+import engine.exception.SerializationException;
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+
+
+public class ArcSiegeSpireMsg extends ClientNetMsg {
+
+    private int buildingUUID;
+
+    /**
+     * This constructor is used by NetMsgFactory. It attempts to deserialize the
+     * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+     * past the limit) then this constructor Throws that Exception to the
+     * caller.
+     */
+    public ArcSiegeSpireMsg(AbstractConnection origin, ByteBufferReader reader)  {
+        super(Protocol.ARCSIEGESPIRE, origin, reader);
+    }
+
+    public ArcSiegeSpireMsg() {
+        super(Protocol.ARCSIEGESPIRE);
+    }
+
+    /**
+     * Deserializes the subclass specific items from the supplied NetMsgReader.
+     */
+    @Override
+    protected void _deserialize(ByteBufferReader reader)  {
+        reader.getInt(); // object type padding
+        this.buildingUUID = reader.getInt();
+    }
+
+    /**
+     * Serializes the subclass specific items to the supplied NetMsgWriter.
+     */
+    @Override
+    protected void _serialize(ByteBufferWriter writer) throws SerializationException {
+
+    }
+
+    public int getBuildingUUID() {
+        return buildingUUID;
+    }
+ 
+}
diff --git a/src/engine/net/client/msg/ArcViewAssetTransactionsMsg.java b/src/engine/net/client/msg/ArcViewAssetTransactionsMsg.java
new file mode 100644
index 00000000..df827a11
--- /dev/null
+++ b/src/engine/net/client/msg/ArcViewAssetTransactionsMsg.java
@@ -0,0 +1,150 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+import engine.Enum.GameObjectType;
+import engine.Enum.TransactionType;
+import engine.exception.SerializationException;
+import engine.gameManager.BuildingManager;
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.objects.*;
+
+import java.util.ArrayList;
+
+public class ArcViewAssetTransactionsMsg extends ClientNetMsg {
+
+	private int warehouseID;
+	private Warehouse warehouse;
+	private int transactionID;
+	private ArrayList<Transaction> transactions;
+	Building warehouseBuilding;
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public ArcViewAssetTransactionsMsg(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.ARCVIEWASSETTRANSACTIONS, origin, reader);
+	}
+
+	public ArcViewAssetTransactionsMsg(Warehouse warehouse, ArcViewAssetTransactionsMsg msg) {
+		super(Protocol.ARCVIEWASSETTRANSACTIONS);
+        this.warehouseID = msg.warehouseID;
+        this.transactionID = msg.transactionID;
+		this.warehouse = warehouse;
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		this.transactionID = reader.getInt(); //some odd type?
+		this.warehouseID = reader.getInt();
+		reader.getInt();
+	}
+
+	// Method pre-caches and configures values so they are
+	// available before we attempt serialization
+
+	public void configure() {
+
+		warehouseBuilding = BuildingManager.getBuilding(this.warehouse.getBuildingUID());
+		transactions = new ArrayList<>(50);
+
+		if (this.warehouse.getTransactions().size() > 150){
+			transactions.addAll(this.warehouse.getTransactions().subList(this.warehouse.getTransactions().size() - 150, this.warehouse.getTransactions().size()));
+		}else
+			transactions = this.warehouse.getTransactions();
+
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) throws SerializationException {
+
+		writer.putInt(this.transactionID);
+		writer.putInt(this.warehouse.getBuildingUID());
+		writer.putInt(transactions.size()); //list Size
+
+		for (Transaction transaction:transactions){
+			String name = "No Name";
+			switch (transaction.getTargetType()){
+			case Building:
+				Building building = BuildingManager.getBuildingFromCache(transaction.getTargetUUID());
+				if (building != null)
+					name = building.getName();
+				Mine mine = Mine.getMineFromTower(transaction.getTargetUUID());
+				//
+				if (mine != null)
+					name = mine.getZoneName();
+				
+				if (transaction.getTransactionType().equals(TransactionType.TAXRESOURCE) || transaction.getTransactionType().equals(TransactionType.TAXRESOURCEDEPOSIT)){
+					City city = building.getCity();
+					
+					if (city != null)
+						name = city.getCityName();
+				}
+				
+				break;
+			case PlayerCharacter:
+				PlayerCharacter pc = PlayerCharacter.getPlayerCharacter(transaction.getTargetUUID());
+				if (pc != null)
+					name = pc.getCombinedName();
+				break;
+			case NPC:
+				NPC npc = NPC.getFromCache(transaction.getTargetUUID());
+				if (npc != null){
+					
+					if (npc.getBuilding() != null)
+						name = npc.getBuilding().getName();
+					else
+					name = npc.getName();
+				}
+					
+				
+			default:
+				break;
+			}
+			writer.putInt(transaction.getTargetType().ordinal()); //Type
+			writer.putInt(transaction.getTargetUUID()); //ID
+			writer.putString(name); //Name of depositer/withdrawler or mine name
+			writer.putInt(GameObjectType.Building.ordinal()); //Type
+			writer.putInt(warehouse.getBuildingUID()); //ID
+			writer.putString(warehouseBuilding.getName()); //warehouse
+			writer.putInt(transaction.getTransactionType().getID()); //79,80 withdrew, 81 mine produced, 82 deposit
+			writer.putInt(transaction.getAmount()); //amount
+			writer.putString(transaction.getResource().name().toLowerCase()); //item type
+			writer.putDateTime(transaction.getDate());
+		}
+
+
+		//writer.putString("balls");
+	}
+
+	@Override
+	protected int getPowerOfTwoBufferSize() {
+		return (16); // 2^14 == 16384
+	}
+
+	public int getWarehouseID() {
+		return warehouseID;
+	}
+	public int getTransactionID() {
+		return transactionID;
+	}
+}
diff --git a/src/engine/net/client/msg/AssetSupportMsg.java b/src/engine/net/client/msg/AssetSupportMsg.java
new file mode 100644
index 00000000..bf0864ab
--- /dev/null
+++ b/src/engine/net/client/msg/AssetSupportMsg.java
@@ -0,0 +1,334 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+
+import engine.Enum;
+import engine.exception.SerializationException;
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import org.pmw.tinylog.Logger;
+
+
+public class AssetSupportMsg extends ClientNetMsg {
+
+    private int npcType;
+    private int npcID;
+    private int buildingType;
+    private int buildingID;
+    private int messageType;
+    private int pad = 0;
+    private int objectType;
+    private int objectUUID;
+    private int protectedBuildingType;
+    private int protectedBuildingID;
+    private int profitTax;
+    private int weeklyTax;
+    private byte enforceKOS;
+    private Enum.SupportMsgType supportMsgType;
+    public static int confirmProtect;
+
+
+    /**
+     * This constructor is used by NetMsgFactory. It attempts to deserialize the
+     * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+     * past the limit) then this constructor Throws that Exception to the
+     * caller.
+     */
+    public AssetSupportMsg(AbstractConnection origin, ByteBufferReader reader) {
+        super(Protocol.ASSETSUPPORT, origin, reader);
+    }
+
+    /**
+     * Deserializes the subclass specific items from the supplied NetMsgReader.
+     */
+    @Override
+    protected void _deserialize(ByteBufferReader reader) {
+
+        this.messageType = reader.getInt();
+        this.supportMsgType = Enum.SupportMsgType.typeLookup.get(this.messageType);
+
+        if (this.supportMsgType == null) {
+            this.supportMsgType = Enum.SupportMsgType.NONE;
+            Logger.error("No enumeration for support type" + this.messageType);
+        }
+
+        switch (supportMsgType) {
+
+            case PROTECT:
+                this.buildingType = reader.getInt();
+                this.buildingID = reader.getInt();
+                this.npcType = reader.getInt();
+                this.npcID = reader.getInt();
+                reader.getInt();
+                reader.getInt();
+                this.protectedBuildingType = reader.getInt();
+                this.protectedBuildingID = reader.getInt();
+                reader.getInt();
+                this.weeklyTax = reader.getInt();
+                this.profitTax = reader.getInt();
+                this.enforceKOS = reader.get();
+                reader.get();
+                reader.getInt();
+                reader.getInt();
+                break;
+
+            case UNPROTECT:
+                this.buildingType = reader.getInt();
+                this.buildingID = reader.getInt();
+                this.npcType = reader.getInt();
+                this.npcID = reader.getInt();
+                this.protectedBuildingType = reader.getInt();
+                this.protectedBuildingID = reader.getInt();
+                reader.getInt();
+                reader.getInt();
+                reader.getInt();
+                reader.getInt();
+                break;
+            case VIEWUNPROTECTED:
+                this.buildingType = reader.getInt();
+                this.buildingID = reader.getInt();
+                this.npcType = reader.getInt();
+                this.npcID = reader.getInt();
+                reader.getInt();
+                reader.getInt();
+                reader.getInt();
+                reader.getInt();
+                reader.getInt();
+                reader.getInt();
+                break;
+            case REMOVETAX:
+                reader.getInt();
+                this.buildingID = reader.getInt();
+
+                reader.getInt();
+                reader.getInt();
+                reader.getInt();
+                reader.getInt();
+                reader.getInt();
+                reader.getInt();
+                reader.get();
+                reader.get();
+                reader.get();
+                reader.getInt();
+                reader.getInt();
+                break;
+
+            case ACCEPTTAX:
+                reader.getInt();
+                this.buildingID = reader.getInt();
+
+                reader.getInt();
+                reader.getInt();
+                reader.getInt();
+                reader.getInt();
+                reader.getInt();
+                reader.getInt();
+                reader.getInt();
+                reader.getInt();
+                reader.getInt();
+                reader.getInt();
+                reader.getInt();
+                reader.getInt();
+                reader.getInt();
+
+                reader.get();
+                reader.get();
+                reader.get();
+                break;
+        }
+    }
+
+    /**
+     * Serializes the subclass specific items to the supplied NetMsgWriter.
+     */
+    @Override
+    protected void _serialize(ByteBufferWriter writer) throws SerializationException {
+
+        writer.putInt(this.messageType);
+        this.supportMsgType = Enum.SupportMsgType.typeLookup.get(this.messageType);
+
+        if (this.supportMsgType == null) {
+            this.supportMsgType = Enum.SupportMsgType.NONE;
+            Logger.error("No enumeration for support type" + this.messageType);
+        }
+
+        switch (this.supportMsgType) {
+
+            case PROTECT:
+                writer.putInt(this.buildingType);
+                writer.putInt(this.buildingID);
+                writer.putInt(npcType);
+                writer.putInt(npcID);
+                writer.putInt(0);
+                writer.putInt(0);
+                writer.putInt(this.protectedBuildingType);
+                writer.putInt(this.protectedBuildingID);
+                writer.putInt(0);
+                writer.putInt(this.weeklyTax);
+                writer.putInt(this.profitTax);
+                writer.put(this.enforceKOS);
+                writer.put((byte) 0);
+                writer.putInt(0);
+                writer.putInt(0);
+                break;
+            case CONFIRMPROTECT:
+            	  writer.putInt(this.buildingType);
+                  writer.putInt(this.buildingID);
+                  writer.putInt(this.npcType);
+                  writer.putInt(this.npcID);
+                  writer.putInt(0);
+                  writer.putInt(0);
+                  writer.putInt(0);
+                  writer.putInt(0);
+                  writer.putInt(0);
+                  writer.putInt(0);
+                  writer.putInt(0);
+                  writer.putInt(0);
+                  writer.putInt(0);
+                  writer.putInt(0);
+                  writer.putInt(0);
+                  writer.put((byte)0);
+                  writer.put((byte)0);
+                  writer.put((byte)0);
+              
+                  
+                  break;
+            case UNPROTECT:
+                writer.putInt(this.buildingType);
+                writer.putInt(this.buildingID);
+                writer.putInt(npcType);
+                writer.putInt(npcID);
+                writer.putInt(this.protectedBuildingType);
+                writer.putInt(this.protectedBuildingID);
+                writer.putInt(0);
+                writer.putInt(0);
+                writer.putInt(0);
+                writer.putInt(0);
+                break;
+            case VIEWUNPROTECTED:
+                writer.putInt(this.buildingType);
+                writer.putInt(this.buildingID);
+                writer.putInt(npcType);
+                writer.putInt(npcID);
+                writer.putInt(0);
+                writer.putInt(0);
+                writer.putInt(0);
+                writer.putInt(0);
+                writer.putInt(0);
+                writer.putInt(0);
+                break;
+        }
+    }
+
+    public int getObjectType() {
+        return objectType;
+    }
+
+    public void setObjectType(int value) {
+        this.objectType = value;
+    }
+
+    public void setPad(int value) {
+        this.pad = value;
+    }
+
+    public int getUUID() {
+        return objectUUID;
+    }
+
+    public int getPad() {
+        return pad;
+    }
+
+
+    public int getMessageType() {
+        return messageType;
+    }
+
+    public void setMessageType(int messageType) {
+        this.messageType = messageType;
+    }
+
+
+    public int getNpcType() {
+        return npcType;
+    }
+
+    public void setNpcType(int npcType) {
+        this.npcType = npcType;
+    }
+
+    public int getNpcID() {
+        return npcID;
+    }
+
+    public void setNpcID(int npcID) {
+        this.npcID = npcID;
+    }
+
+    public int getBuildingType() {
+        return buildingType;
+    }
+
+    public void setBuildingType(int buildingType) {
+        this.buildingType = buildingType;
+    }
+
+    public int getBuildingID() {
+        return buildingID;
+    }
+
+    public void setBuildingID(int buildingID) {
+        this.buildingID = buildingID;
+    }
+
+    public int getProtectedBuildingType() {
+        return protectedBuildingType;
+    }
+
+    public void setProtectedBuildingType(int protectedBuildingType) {
+        this.protectedBuildingType = protectedBuildingType;
+    }
+
+    public int getProtectedBuildingID() {
+        return protectedBuildingID;
+    }
+
+    public void setProtectedBuildingID(int protectedBuildingID) {
+        this.protectedBuildingID = protectedBuildingID;
+    }
+
+    public int getWeeklyTax() {
+        return weeklyTax;
+    }
+
+    public void setWeeklyTax(int weeklyTax) {
+        this.weeklyTax = weeklyTax;
+    }
+
+    public int getProfitTax() {
+        return profitTax;
+    }
+
+    public void setProfitTax(int profitTax) {
+        this.profitTax = profitTax;
+    }
+
+    public byte getEnforceKOS() {
+        return enforceKOS;
+    }
+
+    public void setEnforceKOS(byte enforceKOS) {
+        this.enforceKOS = enforceKOS;
+    }
+}
diff --git a/src/engine/net/client/msg/AttackCmdMsg.java b/src/engine/net/client/msg/AttackCmdMsg.java
new file mode 100644
index 00000000..ed5b398c
--- /dev/null
+++ b/src/engine/net/client/msg/AttackCmdMsg.java
@@ -0,0 +1,113 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+
+
+public class AttackCmdMsg extends ClientNetMsg {
+
+    private int sourceType;
+    private int sourceID;
+    private int targetType;
+    private int targetID;
+
+    /**
+     * This is the general purpose constructor.
+     */
+    public AttackCmdMsg(int sourceType, int sourceID, int targetType, int targetID) {
+        super(Protocol.REQUESTMELEEATTACK);
+        this.sourceType = sourceType;
+        this.sourceID = sourceID;
+        this.targetType = targetType;
+        this.targetID = targetID;
+    }
+
+    /**
+     * This constructor is used by NetMsgFactory. It attempts to deserialize the
+     * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+     * past the limit) then this constructor Throws that Exception to the
+     * caller.
+     */
+    public AttackCmdMsg(AbstractConnection origin, ByteBufferReader reader)  {
+        super(Protocol.REQUESTMELEEATTACK, origin, reader);
+    }
+
+    /**
+     * Serializes the subclass specific items to the supplied NetMsgWriter.
+     */
+    @Override
+    protected void _serialize(ByteBufferWriter writer) {
+        writer.putInt(this.sourceType);
+        writer.putInt(this.sourceID);
+        writer.putInt(this.targetType);
+        writer.putInt(this.targetID);
+    }
+
+    /**
+     * Deserializes the subclass specific items from the supplied NetMsgReader.
+     */
+    @Override
+    protected void _deserialize(ByteBufferReader reader)  {
+        this.sourceType = reader.getInt();
+        this.sourceID = reader.getInt();
+        this.targetType = reader.getInt();
+        this.targetID = reader.getInt();
+    }
+
+    /**
+     * @return the sourceType
+     */
+    public int getSourceType() {
+        return sourceType;
+    }
+
+    /**
+     * @return the sourceID
+     */
+    public int getSourceID() {
+        return sourceID;
+    }
+
+    /**
+     * @return the targetType
+     */
+    public int getTargetType() {
+        return targetType;
+    }
+
+    /**
+     * @return the targetID
+     */
+    public int getTargetID() {
+        return targetID;
+    }
+
+    public void setSourceType(int value) {
+        this.sourceType = value;
+    }
+
+    public void setSourceID(int value) {
+        this.sourceID = value;
+    }
+
+    public void setTargetType(int value) {
+        this.targetType = value;
+    }
+
+    public void setTargetID(int value) {
+        this.targetID = value;
+    }
+
+}
diff --git a/src/engine/net/client/msg/BuyFromNPCMsg.java b/src/engine/net/client/msg/BuyFromNPCMsg.java
new file mode 100644
index 00000000..de63bd55
--- /dev/null
+++ b/src/engine/net/client/msg/BuyFromNPCMsg.java
@@ -0,0 +1,139 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+
+import engine.exception.SerializationException;
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.objects.Item;
+
+public class BuyFromNPCMsg extends ClientNetMsg {
+
+	private int npcType;
+	private int npcID;
+	private int itemType;
+	private int itemID;
+	private byte unknown01;
+	private Item item;
+
+	/**
+	 * This is the general purpose constructor
+	 */
+	public BuyFromNPCMsg() {
+		super(Protocol.BUYFROMNPC);
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public BuyFromNPCMsg(AbstractConnection origin, ByteBufferReader reader)
+			 {
+		super(Protocol.BUYFROMNPC, origin, reader);
+	}
+
+	/**
+	 * Deserializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)
+			 {
+		this.npcType = reader.getInt();
+		this.npcID = reader.getInt();
+		this.itemType = reader.getInt();
+		this.itemID = reader.getInt();
+		this.unknown01 = reader.get();
+		reader.get();
+	}
+
+	/**
+	 * Serializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer)
+			throws SerializationException {
+
+		writer.putInt(this.npcType);
+		writer.putInt(this.npcID);
+
+		if (this.item != null){
+			writer.putInt(this.item.getObjectType().ordinal());
+			writer.putInt(this.item.getObjectUUID());
+		}else{
+			writer.putInt(this.itemType);
+			writer.putInt(this.itemID);
+		}
+
+		writer.put(this.unknown01);
+		if (item != null) {
+			writer.put((byte) 1);
+			Item.serializeForClientMsgWithoutSlot(item,writer);
+		} else {
+			writer.put((byte) 0);
+		}
+	}
+
+	@Override
+	protected int getPowerOfTwoBufferSize() {
+		return (16); // 2^14 == 16384
+	}
+	public int getNPCType() {
+		return this.npcType;
+	}
+
+	public int getNPCID() {
+		return this.npcID;
+	}
+
+	public int getItemType() {
+		return this.itemType;
+	}
+
+	public int getItemID() {
+		return this.itemID;
+	}
+
+	public byte getUnknown01() {
+		return this.unknown01;
+	}
+
+	public void setNPCType(int value) {
+		this.npcType = value;
+	}
+
+	public void setNPCID(int value) {
+		this.npcID = value;
+	}
+
+	public void setItemType(int value) {
+		this.itemType = value;
+	}
+
+	public void setItemID(int value) {
+		this.itemID = value;
+	}
+
+	public void setUnknown01(byte value) {
+		this.unknown01 = value;
+	}
+
+	public Item getItem() {
+		return item;
+	}
+
+	public void setItem(Item item) {
+		this.item = item;
+	}
+}
diff --git a/src/engine/net/client/msg/BuyFromNPCWindowMsg.java b/src/engine/net/client/msg/BuyFromNPCWindowMsg.java
new file mode 100644
index 00000000..b54d6919
--- /dev/null
+++ b/src/engine/net/client/msg/BuyFromNPCWindowMsg.java
@@ -0,0 +1,269 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+import engine.Enum.ItemType;
+import engine.exception.SerializationException;
+import engine.net.AbstractConnection;
+import engine.net.AbstractNetMsg;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.ClientConnection;
+import engine.net.client.Protocol;
+import engine.objects.*;
+
+import java.util.ArrayList;
+
+public class BuyFromNPCWindowMsg extends ClientNetMsg {
+
+	private int unknown01;
+	private int npcType;
+	private int npcID;
+	private float unknown02;
+	private byte unknown03;
+	private int unknown04;
+
+	/**
+	 * This is the general purpose constructor
+	 */
+	public BuyFromNPCWindowMsg(int unknown01, int npcType, int npcID,
+			float unknown02, byte unknown03, int unknown04) {
+		super(Protocol.SHOPLIST);
+		this.unknown01 = unknown01;
+		this.npcType = npcType;
+		this.npcID = npcID;
+		this.unknown02 = unknown02;
+		this.unknown03 = unknown03;
+		this.unknown04 = unknown04;
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public BuyFromNPCWindowMsg(AbstractConnection origin, ByteBufferReader reader)
+			 {
+		super(Protocol.SHOPLIST, origin, reader);
+	}
+
+	/**
+	 * @see AbstractNetMsg#getPowerOfTwoBufferSize()
+	 */
+	@Override
+	protected int getPowerOfTwoBufferSize() {
+		// Larger size for historically larger opcodes
+		return (16);
+	}
+
+	/**
+	 * Deserializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)
+			 {
+		unknown01 = reader.getInt();
+		npcType = reader.getInt();
+		npcID = reader.getInt();
+		unknown02 = reader.getFloat();
+		unknown03 = reader.get();
+		unknown04 = reader.getInt();
+	}
+
+	/**
+	 * Serializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer)
+			throws SerializationException {
+		
+		ClientConnection clientConnection = (ClientConnection) this.getOrigin();
+		PlayerCharacter player = null;
+		
+		if (clientConnection != null)
+			player = clientConnection.getPlayerCharacter();
+		
+		float sellPercent = 1;
+
+		NPC npc = NPC.getFromCache(npcID);
+		CharacterItemManager man = null;
+		ArrayList<Item> inventory = null;
+		ArrayList<MobEquipment> sellInventory = null;
+
+		if (npc != null) {
+			man = npc.getCharItemManager();
+			Contract contract = npc.getContract();
+			if (player != null){
+				float barget = player.getBargain();
+				float profit = npc.getSellPercent(player) - barget;
+				
+				if (profit < 1)
+					profit = 1;
+				
+				sellPercent = 1 * profit;
+			}
+				
+			else sellPercent = 1 * npc.getSellPercent();
+
+			if (contract != null)
+				sellInventory = contract.getSellInventory();
+		}
+
+		if (man != null)
+			inventory = man.getInventory();
+
+		writer.putInt(unknown01);
+		writer.putInt(npcType);
+		writer.putInt(npcID);
+		
+		writer.putFloat(sellPercent); //npc sell markup
+
+		int size = 0;
+
+		if (inventory != null)
+			size += inventory.size();
+
+		if (sellInventory != null)
+			size += sellInventory.size();
+
+		if (size == 0) {
+			writer.put((byte) 0);
+			writer.putInt(0);
+			return;
+		}
+
+		writer.put((byte) 1);
+
+		int ownerID = npc.getObjectUUID();
+		int	indexPosition = writer.position();
+		writer.putInt(0); //placeholder for item cnt
+		int total = 0;
+
+		//add generic sell inventory from contract
+		if (sellInventory != null) {
+
+			for (MobEquipment mobEquipment : sellInventory) {
+				try {
+					MobEquipment.serializeForVendor(mobEquipment,writer, sellPercent);
+				} catch (SerializationException se) {
+					continue;
+				}
+				++total;
+			}
+		}
+
+		//add npc inventory
+		if (inventory != null) {
+			for (Item item : inventory) {
+				if (item.getOwnerID() != ownerID)
+					continue;
+				if (item.getItemBase().getType().equals(ItemType.GOLD)) {
+					if (item.getNumOfItems() == 0)
+						continue;
+				}
+				Item.serializeForClientMsgForVendorWithoutSlot(item,writer, sellPercent);
+				++total;
+			}
+		}
+		writer.putIntAt(total, indexPosition);
+	}
+
+	/**
+	 * @return the unknown01
+	 */
+	public int getUnknown01() {
+		return unknown01;
+	}
+
+	/**
+	 * @param unknown01
+	 *            the unknown01 to set
+	 */
+	public void setUnknown01(int unknown01) {
+		this.unknown01 = unknown01;
+	}
+
+	/**
+	 * @return the unknown02
+	 */
+	public float getUnknown02() {
+		return unknown02;
+	}
+
+	/**
+	 * @param unknown02
+	 *            the unknown02 to set
+	 */
+	public void setUnknown02(float unknown02) {
+		this.unknown02 = unknown02;
+	}
+
+	/**
+	 * @return the unknown03
+	 */
+	public byte getUnknown03() {
+		return unknown03;
+	}
+
+	/**
+	 * @param unknown03
+	 *            the unknown03 to set
+	 */
+	public void setUnknown03(byte unknown03) {
+		this.unknown03 = unknown03;
+	}
+
+	/**
+	 * @return the unknown04
+	 */
+	public int getUnknown04() {
+		return unknown04;
+	}
+
+	/**
+	 * @param unknown04
+	 *            the unknown04 to set
+	 */
+	public void setUnknown04(int unknown04) {
+		this.unknown04 = unknown04;
+	}
+
+	/**
+	 * @return the npcType
+	 */
+	public int getNpcType() {
+		return npcType;
+	}
+
+	/**
+	 * @param npcType
+	 *            the npcType to set
+	 */
+	public void setNpcType(int npcType) {
+		this.npcType = npcType;
+	}
+
+	/**
+	 * @return the npcID
+	 */
+	public int getNpcID() {
+		return npcID;
+	}
+
+	/**
+	 * @param npcID
+	 *            the npcID to set
+	 */
+	public void setNpcID(int npcID) {
+		this.npcID = npcID;
+	}
+
+}
diff --git a/src/engine/net/client/msg/ChangeAltitudeMsg.java b/src/engine/net/client/msg/ChangeAltitudeMsg.java
new file mode 100644
index 00000000..bb527b15
--- /dev/null
+++ b/src/engine/net/client/msg/ChangeAltitudeMsg.java
@@ -0,0 +1,146 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.objects.PlayerCharacter;
+
+public class ChangeAltitudeMsg extends ClientNetMsg {
+
+	private int sourceType;
+	private int sourceID;
+	private boolean up;
+	private float startAlt;
+	private float targetAlt;
+	private float amountToMove;
+	private byte unknown01 = (byte) 0;
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public ChangeAltitudeMsg() {
+		super(Protocol.CHANGEALTITUDE);
+	}
+
+	public ChangeAltitudeMsg(int sourceType, int sourceID, boolean up, float startAlt, float targetAlt, float amountToMove) {
+		super(Protocol.CHANGEALTITUDE);
+		this.sourceType = sourceType;
+		this.sourceID = sourceID;
+		this.startAlt = startAlt;
+		this.targetAlt = targetAlt;
+		this.amountToMove = amountToMove;
+		this.up = up;
+	}
+	
+	public static ChangeAltitudeMsg GroundPlayerMsg(PlayerCharacter pc){
+		
+		ChangeAltitudeMsg msg = new ChangeAltitudeMsg(pc.getObjectType().ordinal(),pc.getObjectUUID(),false,pc.getAltitude(),0,pc.getAltitude());
+		return msg;
+		
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public ChangeAltitudeMsg(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.CHANGEALTITUDE, origin, reader);
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+		writer.putInt(this.sourceType);
+		writer.putInt(this.sourceID);
+		writer.put((this.up ? (byte) 1 : (byte) 0));
+		writer.putFloat(this.startAlt);
+		writer.putFloat(this.targetAlt);
+		writer.putFloat(this.amountToMove);
+		writer.put((byte)0);
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		this.sourceType = reader.getInt();
+		this.sourceID = reader.getInt();
+		this.up = (reader.get() == (byte) 1);
+		this.startAlt = reader.getFloat();
+		this.targetAlt = reader.getFloat();
+		this.amountToMove = reader.getFloat();
+		this.unknown01 = reader.get();
+	}
+
+	public int getSourceType() {
+		return this.sourceType;
+	}
+
+	public int getSourceID() {
+		return this.sourceID;
+	}
+
+	public boolean up() {
+		return this.up;
+	}
+
+	public float getStartAlt() {
+		return this.startAlt;
+	}
+
+	public float getTargetAlt() {
+		return this.targetAlt;
+	}
+
+	public float getAmountToMove() {
+		return this.amountToMove;
+	}
+
+	public byte getUnknown01() {
+		return this.unknown01;
+	}
+
+	public void setSourceType(int value) {
+		this.sourceType = value;
+	}
+
+	public void setSourceID(int value) {
+		this.sourceID = value;
+	}
+
+	public void setUp(boolean value) {
+		this.up = value;
+	}
+
+	public void setStartAlt(float value) {
+		this.startAlt = value;
+	}
+
+	public void setTargetAlt(float value) {
+		this.targetAlt = value;
+	}
+
+	public void setAmountToMove(float value) {
+		this.amountToMove = value;
+	}
+
+	public void setUnknown01(byte value) {
+		this.unknown01 = value;
+	}
+}
diff --git a/src/engine/net/client/msg/ChangeGuildLeaderMsg.java b/src/engine/net/client/msg/ChangeGuildLeaderMsg.java
new file mode 100644
index 00000000..3491b9d6
--- /dev/null
+++ b/src/engine/net/client/msg/ChangeGuildLeaderMsg.java
@@ -0,0 +1,72 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+import engine.Enum.GameObjectType;
+import engine.exception.SerializationException;
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+
+
+public class ChangeGuildLeaderMsg extends ClientNetMsg {
+
+	private int targetID;
+    
+ 
+    /**
+     * This constructor is used by NetMsgFactory. It attempts to deserialize the
+     * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+     * past the limit) then this constructor Throws that Exception to the
+     * caller.
+     */
+    public ChangeGuildLeaderMsg(AbstractConnection origin, ByteBufferReader reader)  {
+        super(Protocol.CHANGEGUILDLEADER, origin, reader);
+    }
+
+    /**
+     * Deserializes the subclass specific items from the supplied NetMsgReader.
+     */
+    @Override
+    protected void _deserialize(ByteBufferReader reader)  {
+       reader.getInt();
+       reader.getInt();
+       reader.getInt();
+       reader.getInt();
+       targetID = reader.getInt();
+       reader.getInt();
+       reader.get();
+    }
+
+    /**
+     * Serializes the subclass specific items to the supplied NetMsgWriter.
+     */
+    @Override
+    protected void _serialize(ByteBufferWriter writer) throws SerializationException {
+    	writer.putInt(0);
+    	writer.putInt(0);
+    	writer.putInt(0);
+    	writer.putInt(GameObjectType.PlayerCharacter.ordinal());
+    	writer.putInt(targetID);
+    	writer.put((byte)100);
+    	writer.putInt(0);
+    	
+    	
+    }
+
+	public int getTargetID() {
+		return targetID;
+	}
+
+	public void setTargetID(int targetID) {
+		this.targetID = targetID;
+	}
+}
diff --git a/src/engine/net/client/msg/ChatFilterMsg.java b/src/engine/net/client/msg/ChatFilterMsg.java
new file mode 100644
index 00000000..593fb262
--- /dev/null
+++ b/src/engine/net/client/msg/ChatFilterMsg.java
@@ -0,0 +1,73 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+
+
+public class ChatFilterMsg extends ClientNetMsg {
+
+    private int channel;
+    private byte mute;
+
+    /**
+     * This is the general purpose constructor.
+     *
+     * @param channel
+     */
+    public ChatFilterMsg(int channel) {
+        super(Protocol.CHANNELMUTE);
+        this.channel = channel;
+    }
+
+    /**
+     * This constructor is used by NetMsgFactory. It attempts to deserialize the
+     * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+     * past the limit) then this constructor Throws that Exception to the
+     * caller.
+     */
+    public ChatFilterMsg(AbstractConnection origin, ByteBufferReader reader)  {
+        super(Protocol.CHANNELMUTE, origin, reader);
+    }
+
+    /**
+     * Serializes the subclass specific items to the supplied NetMsgWriter.
+     */
+    @Override
+    protected void _serialize(ByteBufferWriter writer) {
+        writer.putInt(this.channel);
+        writer.put(this.mute);
+    }
+
+    /**
+     * Deserializes the subclass specific items from the supplied NetMsgReader.
+     */
+    @Override
+    protected void _deserialize(ByteBufferReader reader)  {
+        this.channel = reader.getInt();
+        this.mute = reader.get();
+    }
+
+    public int getChannel() {
+        return this.channel;
+    }
+
+	public byte getMute() {
+		return mute;
+	}
+
+	public void setMute(byte mute) {
+		this.mute = mute;
+	}
+}
diff --git a/src/engine/net/client/msg/CityAssetMsg.java b/src/engine/net/client/msg/CityAssetMsg.java
new file mode 100644
index 00000000..515794a1
--- /dev/null
+++ b/src/engine/net/client/msg/CityAssetMsg.java
@@ -0,0 +1,219 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+import engine.Enum;
+import engine.Enum.GameObjectType;
+import engine.Enum.ProtectionState;
+import engine.exception.SerializationException;
+import engine.gameManager.BuildingManager;
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.objects.Building;
+import engine.objects.City;
+import engine.objects.Zone;
+import org.pmw.tinylog.Logger;
+
+import java.util.HashSet;
+import java.util.Set;
+
+public class CityAssetMsg extends ClientNetMsg {
+
+	Set<Building> allCityAssets;
+	Set<Building> canProtectAssets;
+	private int type;
+	private int buildingID;
+	private int size;
+	private int pad = 0;
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public CityAssetMsg(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.CITYASSET, origin, reader);
+	}
+
+	public CityAssetMsg() {
+		super(Protocol.CITYASSET);
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		this.type = reader.getInt();
+		reader.getInt();
+		this.buildingID = reader.getInt();
+		reader.getInt();
+
+	}
+
+	@Override
+	protected int getPowerOfTwoBufferSize() {
+		// Larger size for historically larger opcodes
+		return (12);
+	}
+
+
+	// Precache and configure this message before we serialize it
+
+	public void configure() {
+
+		Building tol;
+		Zone zone;
+		City city;
+
+		canProtectAssets = new HashSet<>();
+
+		tol = BuildingManager.getBuildingFromCache(this.buildingID);
+
+		if (tol == null) {
+			Logger.debug("TOL is null");
+			return;
+		}
+
+		zone = tol.getParentZone();
+
+		if (zone == null) {
+			Logger.debug("Zone is null");
+			return;
+		}
+
+		city = City.getCity(zone.getPlayerCityUUID());
+
+		if (city == null) {
+			Logger.debug( "Failed to load city data for Tree of life.");
+			return;
+		}
+
+		allCityAssets = zone.zoneBuildingSet;
+
+		for (Building building : allCityAssets) {
+
+			if (building.getBlueprint() == null)
+				continue;
+
+			// Protected assets do not show up on list
+
+			if (building.assetIsProtected() == true)
+				continue;
+
+			if (building.getProtectionState() == ProtectionState.PENDING)
+				continue;
+
+			// Shouldn't need this but just in case
+
+			if (building.getBlueprint().isWallPiece())
+				continue;
+
+			if (building.getBlueprint().getBuildingGroup().equals(Enum.BuildingGroup.TOL))
+				continue;
+
+			if (building.getBlueprint().getBuildingGroup().equals(Enum.BuildingGroup.BANESTONE))
+				continue;
+
+			if (building.getBlueprint().getBuildingGroup().equals(Enum.BuildingGroup.SHRINE))
+				continue;
+
+			if (!city.isLocationOnCityGrid(building.getLoc()))
+				continue;
+
+			canProtectAssets.add(building);
+
+		}
+
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) throws SerializationException {
+
+		String buildingName;
+
+		writer.putInt(0);
+		writer.putInt(0);
+
+		writer.putInt(GameObjectType.Building.ordinal());
+		writer.putInt(this.buildingID);
+
+		writer.putInt(canProtectAssets.size());
+
+		for (Building cityBuilding : canProtectAssets) {
+
+			buildingName = cityBuilding.getName();
+
+			if (buildingName.isEmpty() && cityBuilding.getBlueprint() != null) {
+				buildingName = cityBuilding.getBlueprint().getName();
+			}
+
+			writer.putInt(cityBuilding.getObjectType().ordinal());
+			writer.putInt(cityBuilding.getObjectUUID());
+
+			writer.putString(buildingName);
+			writer.putString(cityBuilding.getGuild().getName());
+			writer.putInt(20);//  \/ Temp \/
+			writer.putInt(cityBuilding.getRank());
+
+			if (cityBuilding.getBlueprint() != null) {
+				writer.putInt(cityBuilding.getBlueprint().getIcon());
+			}
+			else {
+				writer.putInt(0);
+			}
+			writer.putInt(7);  //TODO identify these Guild tags??
+			writer.putInt(17);
+			writer.putInt(14);
+			writer.putInt(14);
+			writer.putInt(98);// /\ Temp /\
+
+		}
+	}
+
+	public int getPad() {
+		return pad;
+	}
+
+	public void setPad(int value) {
+		this.pad = value;
+	}
+
+	public int getType() {
+		return type;
+	}
+
+	public void setType(int type) {
+		this.type = type;
+	}
+
+	public int getSize() {
+		return size;
+	}
+
+	public void setSize(int size) {
+		this.size = size;
+	}
+
+	public int getBuildingID() {
+		return buildingID;
+	}
+
+	public void setBuildingID(int buildingID) {
+		this.buildingID = buildingID;
+	}
+
+}
diff --git a/src/engine/net/client/msg/CityChoiceMsg.java b/src/engine/net/client/msg/CityChoiceMsg.java
new file mode 100644
index 00000000..5c43439c
--- /dev/null
+++ b/src/engine/net/client/msg/CityChoiceMsg.java
@@ -0,0 +1,107 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+
+import engine.net.AbstractConnection;
+import engine.net.AbstractNetMsg;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.objects.PlayerCharacter;
+
+
+public class CityChoiceMsg extends ClientNetMsg {
+
+	private PlayerCharacter pc;
+	private boolean isTeleport;
+	private int msgType;
+	private int cityID;
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public CityChoiceMsg(PlayerCharacter pc, boolean isTeleport) {
+		super(Protocol.CITYCHOICE);
+		this.pc = pc;
+		this.isTeleport = isTeleport;
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public CityChoiceMsg(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.CITYCHOICE, origin, reader);
+	}
+
+	/**
+	 * Copy constructor
+	 */
+	public CityChoiceMsg(CityChoiceMsg msg) {
+		super(Protocol.CITYCHOICE);
+		this.pc = msg.pc;
+		this.isTeleport = msg.isTeleport;
+	}
+
+	/**
+	 * @see AbstractNetMsg#getPowerOfTwoBufferSize()
+	 */
+	@Override
+	protected int getPowerOfTwoBufferSize() {
+		// Larger size for historically larger opcodes
+		return (12);
+	}
+	/**
+	 * Deserializes the subclass specific items from the supplied ByteBufferReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		//Do we even want to try this?
+		this.msgType = reader.getInt();
+		reader.getInt();
+		this.cityID = reader.getInt();
+		reader.getInt();
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied ByteBufferWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+	
+	}
+
+	public PlayerCharacter pc() {
+		return this.pc;
+	}
+
+	public boolean isTeleport() {
+		return this.isTeleport;
+	}
+
+	public void setPC(PlayerCharacter pc) {
+		this.pc = pc;
+	}
+
+	public void setIsTeleport(boolean value) {
+		this.isTeleport = value;
+	}
+
+	public int getMsgType() {
+		return msgType;
+	}
+
+	public int getCityID() {
+		return cityID;
+	}
+}
diff --git a/src/engine/net/client/msg/CityZoneMsg.java b/src/engine/net/client/msg/CityZoneMsg.java
new file mode 100644
index 00000000..41ea295e
--- /dev/null
+++ b/src/engine/net/client/msg/CityZoneMsg.java
@@ -0,0 +1,156 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+import engine.Enum.GameObjectType;
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.objects.Zone;
+
+public class CityZoneMsg extends ClientNetMsg {
+
+	private int messageType; //1 or 2
+	private int zoneType;
+	private int zoneID;
+	private float locX;
+	private float locY;
+	private float locZ;
+	private String name;
+	private float radiusX;
+	private float radiusZ;
+	private int unknown01 = 0;
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+
+	public CityZoneMsg(int messageType, float locX, float locY, float locZ, String name, Zone zone, float radiusX, float radiusZ) {
+		super(Protocol.CITYZONE);
+		this.messageType = messageType; //only 1 or 2 used on message type
+		this.zoneType = GameObjectType.Zone.ordinal();
+		this.zoneID = zone.getObjectUUID();
+		this.locX = locX;
+		this.locY = locY;
+		this.locZ = locZ;
+		this.name = name;
+		this.radiusX = radiusX;
+		this.radiusZ = radiusZ;
+		this.unknown01 = 0;
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the ByteBuffer into a message. If a BufferUnderflow occurs (based on reading past the limit) then this constructor Throws that Exception to the caller.
+	 */
+	public CityZoneMsg(AbstractConnection origin, ByteBufferReader reader)
+			 {
+		super(Protocol.CITYZONE, origin, reader);
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+
+
+		writer.putInt(this.messageType);
+
+
+		//MSGTYPE 3 SERIALIZATION
+		if (this.messageType == 3){
+			writer.putInt(1);
+			writer.putInt(this.zoneType);
+			writer.putInt(this.zoneID);
+			writer.putString("RuinedCity");
+			writer.putString("Ruined");
+			writer.putFloat(this.locX);
+			writer.putFloat(this.locY);
+			writer.putFloat(this.locZ);
+			writer.putInt(0);
+			return;
+		}
+
+
+
+		//END SERIALIZIONTYPE 3
+
+		//	writer.putInt(this.messageType);
+		if (this.messageType == 1){
+			writer.putInt(this.zoneType);
+			writer.putInt(this.zoneID);
+		}
+
+		writer.putFloat(this.locX);
+		writer.putFloat(this.locY);
+		writer.putFloat(this.locZ);
+		writer.putString(this.name);
+		if (this.messageType == 1) {
+			writer.putFloat(this.radiusX);
+			writer.putFloat(this.radiusZ);
+		}
+		writer.putInt(this.unknown01);
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		//		this.locX = reader.getFloat();
+		//		this.locY = reader.getFloat();
+		//		this.locZ = reader.getFloat();
+		//		this.name = reader.getString();
+		//		this.unknown01 = reader.getInt();
+	}
+
+	public float getLocX() {
+		return this.locX;
+	}
+
+	public float getLocY() {
+		return this.locY;
+	}
+
+	public float getLocZ() {
+		return this.locZ;
+	}
+
+	public String getName() {
+		return this.name;
+	}
+
+	public int getUnknown01() {
+		return this.unknown01;
+	}
+
+	public void setLocX(float value) {
+		this.locX = value;
+	}
+
+	public void setLocY(float value) {
+		this.locY = value;
+	}
+
+	public void setLocZ(float value) {
+		this.locZ = value;
+	}
+
+	public void setName(String value) {
+		this.name = value;
+	}
+
+	public void setUnknown01(int value) {
+		this.unknown01 = value;
+	}
+
+
+}
diff --git a/src/engine/net/client/msg/ClaimAssetMsg.java b/src/engine/net/client/msg/ClaimAssetMsg.java
new file mode 100644
index 00000000..16b77bfe
--- /dev/null
+++ b/src/engine/net/client/msg/ClaimAssetMsg.java
@@ -0,0 +1,71 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+
+import engine.exception.SerializationException;
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+
+
+public class ClaimAssetMsg extends ClientNetMsg {
+	private int pad;
+	private int objectType;
+	private int objectUUID;
+
+	public ClaimAssetMsg(int objectType, int objectUUID) {
+		super(Protocol.CLAIMASSET);
+		this.pad = 0;
+		this.objectType = objectType;
+		this.objectUUID = objectUUID;
+	}
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public ClaimAssetMsg(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.CLAIMASSET, origin, reader);
+	}
+	/**
+	 * Deserializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		this.pad = reader.getInt();
+		this.objectType = reader.getInt();
+		this.objectUUID = reader.getInt();
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) throws SerializationException {
+		writer.putInt(this.pad);
+		writer.putInt(this.objectType);
+		writer.putInt(this.objectUUID);
+	}
+
+	public int getObjectType() {
+		return objectType;
+	}
+
+	public void setObjectType(int value) {
+		this.objectType = value;
+	}
+
+	public int getUUID() {
+		return objectUUID;
+	}
+}
diff --git a/src/engine/net/client/msg/ClaimGuildTreeMsg.java b/src/engine/net/client/msg/ClaimGuildTreeMsg.java
new file mode 100644
index 00000000..7661b6ef
--- /dev/null
+++ b/src/engine/net/client/msg/ClaimGuildTreeMsg.java
@@ -0,0 +1,376 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+
+
+/**
+ * Open manage city asset window
+ *
+ * @author Eighty
+ */
+public class ClaimGuildTreeMsg extends ClientNetMsg {
+
+	// 2 = manage this asset.  20 = manage entire city
+
+	private int messageType;
+
+	private int targetType;
+	private int targetID;
+
+	private int charter;
+	private int bgc1;
+	private int bgc2;
+	private int symbolColor;
+	private int bgDesign;
+	private int symbol;
+	private int unknown07;
+	private int unknown08;
+	private int unknown09;
+	private int unknown10;
+	private int unknown11;
+
+	private String CityName;
+	private String OwnerName;
+	private String GuildName;
+	private int unknown12;
+	private byte UnkByte01;
+	private int unknown13;
+	private int unknown14;
+	private int unknown15;
+	private int unknown16;
+	private int unknown17;
+	private int unknown18;
+
+	private byte UnkByte02;
+	private byte UnkByte03;
+	private byte UnkByte04;
+	private byte UnkByte05;
+
+	private int unknown19; //Arraylist motto length?
+	private String motto; //motto Length 60 max?
+	public static final int RENAME_TREE = 2;
+	public static final int OPEN_CITY = 4;
+	public static final int CLOSE_CITY = 5;
+	private String treeName;
+
+//	private int unknown01;
+
+
+
+
+
+
+
+
+
+	/**
+	 * This is the general purpose constructor
+	 */
+	public ClaimGuildTreeMsg() {
+		super(Protocol.CLAIMGUILDTREE);
+	 this.messageType = 0;
+		this.targetType=0;
+		this.targetID = 0;
+
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the ByteBuffer into a message. If a BufferUnderflow occurs (based on reading past the limit) then this constructor Throws that Exception to the caller.
+	 */
+	public ClaimGuildTreeMsg(AbstractConnection origin, ByteBufferReader reader)
+			 {
+		super(Protocol.CLAIMGUILDTREE, origin, reader);
+	}
+
+	/**
+	 * Deserializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)
+			 {
+		this.messageType = reader.getInt();
+		switch (this.messageType){
+		case OPEN_CITY:
+		case CLOSE_CITY:
+			targetType = reader.getInt();
+			targetID = reader.getInt();
+			reader.getInt();
+			reader.getInt();
+			reader.getInt();
+			break;
+		case RENAME_TREE:
+			targetType = reader.getInt();
+			targetID = reader.getInt();
+			reader.getInt();
+			reader.getInt();
+			reader.getInt();
+			this.treeName = reader.getString();
+			break;
+		default:
+			targetType = reader.getInt();
+			targetID = reader.getInt();
+			reader.getInt();
+			reader.getInt();
+			this.treeName = reader.getString();
+			break;
+		}
+		
+	}
+
+	/**
+	 * Serializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer){
+		writer.putInt(this.messageType);
+		writer.putInt(this.targetType);
+		writer.putInt(this.targetID);
+		writer.putInt(0);
+		writer.putInt(0);
+		writer.putInt(0);
+		if (this.messageType == RENAME_TREE)
+		writer.putString(this.treeName);
+		}
+
+	/**
+	 * @return the charter
+	 */
+	public int getcharter() {
+		return charter;
+	}
+
+	
+	
+
+	public int getbgc1() {
+		return bgc1;
+	}
+	public int getbgc2() {
+		return bgc2;
+	}
+	public int getsymbolColor() {
+		return symbolColor;
+	}
+	public int getbgDesign() {
+		return bgDesign;
+	}
+	public int getsymbol() {
+		return symbol;
+	}
+	public int getUnknown07() {
+		return unknown07;
+	}
+	public int getUnknown08() {
+		return unknown08;
+	}
+	public int getUnknown09() {
+		return unknown09;
+	}
+	public int getUnknown10() {
+		return unknown10;
+	}
+	public int getUnknown11() {
+		return unknown11;
+	}
+	public int getUnknown12() {
+		return unknown12;
+	}
+	public int getUnknown13() {
+		return unknown13;
+	}
+	public int getUnknown14() {
+		return unknown14;
+	}
+	public int getUnknown15() {
+		return unknown15;
+	}
+	public int getUnknown16() {
+		return unknown16;
+	}
+	public int getUnknown17() {
+		return unknown17;
+	}
+	public int getUnknown18() {
+		return unknown18;
+	}
+	public int getUnknown19() {
+		return unknown19;
+	}
+
+
+
+
+
+
+
+
+
+	public String getOwnerName() {
+		return OwnerName;
+	}
+
+	public String getCityName() {
+		return CityName;
+	}
+
+	public String getGuildName() {
+		return GuildName;
+	}
+	public void setcharter(int charter) {
+		this.charter = charter;
+	}
+	public void setbgc1 (int bgc1) {
+		this.bgc1 = bgc1;
+	}
+	public void setbgc2 (int bgc2) {
+		this.bgc2 = bgc2;
+	}
+	public void setsymbolColor (int symbolColor) {
+		this.symbolColor = symbolColor;
+	}
+	public void setbgDesign (int bgDesign) {
+		this.bgDesign = bgDesign;
+	}
+	public void setsymbol (int symbol) {
+		this.symbol = symbol;
+	}
+	public void setUnknown07 (int unknown07) {
+		this.unknown07 = unknown07;
+	}
+	public void setUnknown08 (int unknown08) {
+		this.unknown08 = unknown08;
+	}
+	public void setUnknown09 (int unknown09) {
+		this.unknown09 = unknown09;
+	}
+	public void setUnknown10 (int unknown10) {
+		this.unknown10 = unknown10;
+	}
+	public void setUnknown11 (int unknown11) {
+		this.unknown11 = unknown11;
+	}
+	public void setUnknown12 (int unknown12) {
+		this.unknown12 = unknown12;
+	}
+	public void setUnknown13 (int unknown13) {
+		this.unknown13 = unknown13;
+	}
+	public void setUnknown14 (int unknown14) {
+		this.unknown14 = unknown14;
+	}
+	public void setUnknown15 (int unknown15) {
+		this.unknown15 = unknown15;
+	}
+	public void setUnknown16 (int unknown16) {
+		this.unknown16 = unknown16;
+	}
+	public void setUnknown17 (int unknown17) {
+		this.unknown17 = unknown17;
+	}
+	public void setUnknown18 (int unknown18) {
+		this.unknown18 = unknown18;
+	}
+	public void setUnknown19 (int unknown19) {
+		this.unknown19 = unknown19;
+	}
+
+
+
+
+	public void setUnkByte01 (byte UnkByte01) {
+		this.UnkByte01 = UnkByte01;
+	}
+	public void setUnkByte02 (byte UnkByte02) {
+		this.UnkByte02 = UnkByte02;
+	}
+	public void setUnkByte03 (byte UnkByte03) {
+		this.UnkByte03 = UnkByte03;
+	}
+	public void setUnkByte04 (byte UnkByte04) {
+		this.UnkByte04 = UnkByte04;
+	}
+
+
+	public void setOwnerName(String OwnerName) {
+		this.OwnerName = OwnerName;
+	}
+
+	public void setCityName(String CityName) {
+		this.CityName = CityName;
+	}
+
+	public void setGuildName(String GuildName) {
+		this.GuildName = GuildName;
+	}
+
+
+
+	public void setMotto(String motto) {
+		this.motto = motto;
+	}
+
+	public String getMotto() {
+		return motto;
+	}
+
+
+
+	public void setUnkByte05(byte unkByte05) {
+		UnkByte05 = unkByte05;
+	}
+
+	public byte getUnkByte05() {
+		return UnkByte05;
+	}
+
+	public void setMessageType(int value) {
+		this.messageType = value;
+	}
+
+	public int getMessageType() {
+		return messageType;
+	}
+
+	public int getTargetID() {
+		return targetID;
+	}
+
+	public void setTargetID(int targetID) {
+		this.targetID = targetID;
+	}
+
+	public int getTargetType() {
+		return targetType;
+	}
+
+	public void setTargetType(int targetType) {
+		this.targetType = targetType;
+	}
+	
+
+	public String getTreeName() {
+		return treeName;
+	}
+
+}
+
+//Debug Info
+//Run: Failed to make object TEMPLATE:135700 INSTANCE:1717987027141... (t=50.46) (r=7/4/2011 11:56:39)
+//C:\ArcanePrime\Main_Branch\Shadowbane\Source\ArcObjectLoader.cpp(466):ERROR: ArcObjectLoader::Run: Failed to make object TEMPLATE:108760 INSTANCE:1717987027161... (t=50.46) (r=7/4/2011 11:56:39)
+//C:\ArcanePrime\Main_Branch\Shadowbane\Source\ArcObjectLoader.cpp(466):ERROR: ArcObjectLoader::Run: Failed to make object TEMPLATE:108760 INSTANCE:1717987027177... (t=50.67) (r=7/4/2011 11:56:39)
+//C:\ArcanePrime\Main_Branch\Shadowbane\Source\ArcObjectLoader.cpp(466):ERROR: ArcObjectLoader::Run: Failed to make object TEMPLATE:60040 INSTANCE:1717987027344... (t=50.87) (r=7/4/2011 11:56:39)
+//C:\ArcanePrime\Main_Branch\Shadowbane\Source\ArcObjectLoader.cpp(466):ERROR: ArcObjectLoader::Run: Failed to make object TEMPLATE:3 INSTANCE:1717987027164... (t=50.88) (r=7/4/2011 11:56:39)
+
diff --git a/src/engine/net/client/msg/ClientNetMsg.java b/src/engine/net/client/msg/ClientNetMsg.java
new file mode 100644
index 00000000..22611e05
--- /dev/null
+++ b/src/engine/net/client/msg/ClientNetMsg.java
@@ -0,0 +1,73 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+
+import engine.exception.SerializationException;
+import engine.net.AbstractConnection;
+import engine.net.AbstractNetMsg;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+
+public abstract class ClientNetMsg extends AbstractNetMsg {
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	protected ClientNetMsg(Protocol protocolMsg) {
+		super(protocolMsg);
+	}
+
+	protected ClientNetMsg(Protocol protocolMsg, ClientNetMsg msg) {
+		super(protocolMsg, msg);
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	protected ClientNetMsg(Protocol protocolMsg, AbstractConnection origin,
+                           ByteBufferReader reader)  {
+		super(protocolMsg, origin, reader);
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected abstract void _deserialize(ByteBufferReader reader)
+			;
+
+	/**
+	 * Serializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected abstract void _serialize(ByteBufferWriter writer)
+			throws SerializationException;
+
+	/*
+	 * return the header size of 4 for ClientMsgs
+	 */
+	@Override
+	protected int getHeaderSize() {
+		return 4;
+	}
+
+	/*
+	 * Write in the header for ClientMsgs
+	 */
+	@Override
+	protected void writeHeaderAt(int startPos, ByteBufferWriter writer) {
+		writer.putIntAt(this.getProtocolMsg().opcode, startPos);
+	}
+}
diff --git a/src/engine/net/client/msg/CloseTradeWindowMsg.java b/src/engine/net/client/msg/CloseTradeWindowMsg.java
new file mode 100644
index 00000000..d93d0f12
--- /dev/null
+++ b/src/engine/net/client/msg/CloseTradeWindowMsg.java
@@ -0,0 +1,88 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+
+import engine.exception.SerializationException;
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.objects.AbstractGameObject;
+
+/**
+ * Commit to trade
+ *
+ * @author Eighty
+ */
+public class CloseTradeWindowMsg extends ClientNetMsg {
+
+	private int playerType;
+	private int playerID;
+	private int unknown01;
+
+	/**
+	 * This is the general purpose constructor
+	 */
+	public CloseTradeWindowMsg(AbstractGameObject player, int unknown01) {
+		super(Protocol.TRADECLOSE);
+		this.playerType = player.getObjectType().ordinal();
+		this.playerID = player.getObjectUUID();
+		this.unknown01 = unknown01;
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the ByteBuffer into a message. If a BufferUnderflow occurs (based on reading past the limit) then this constructor Throws that Exception to the caller.
+	 */
+	public CloseTradeWindowMsg(AbstractConnection origin, ByteBufferReader reader)
+			 {
+		super(Protocol.TRADECLOSE, origin, reader);
+	}
+
+	/**
+	 * Deserializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)
+			 {
+		playerType = reader.getInt();
+		playerID = reader.getInt();
+		unknown01 = reader.getInt();
+	}
+
+	/**
+	 * Serializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer)
+			throws SerializationException {
+		writer.putInt(playerType);
+		writer.putInt(playerID);
+
+		writer.putInt(unknown01);
+	}
+
+	/**
+	 * @return the unknown01
+	 */
+	public int getUnknown01() {
+		return unknown01;
+	}
+
+	/**
+	 * @param unknown01 the unknown01 to set
+	 */
+	public void setUnknown01(int unknown01) {
+		this.unknown01 = unknown01;
+	}
+
+
+
+}
diff --git a/src/engine/net/client/msg/CommitToTradeMsg.java b/src/engine/net/client/msg/CommitToTradeMsg.java
new file mode 100644
index 00000000..cb46281e
--- /dev/null
+++ b/src/engine/net/client/msg/CommitToTradeMsg.java
@@ -0,0 +1,95 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+
+import engine.exception.SerializationException;
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+
+
+/**
+ * Commit to trade
+ *
+ * @author Eighty
+ */
+public class CommitToTradeMsg extends ClientNetMsg {
+
+	private int unknown01;
+	private long playerCompID;
+
+	/**
+	 * This is the general purpose constructor
+	 */
+	public CommitToTradeMsg(int unknown01, long playerCompID) {
+		super(Protocol.TRADECONFIRM);
+		this.unknown01 = unknown01;
+		this.playerCompID = playerCompID;
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the ByteBuffer into a message. If a BufferUnderflow occurs (based on reading past the limit) then this constructor Throws that Exception to the caller.
+	 */
+	public CommitToTradeMsg(AbstractConnection origin, ByteBufferReader reader)
+			 {
+		super(Protocol.TRADECONFIRM, origin, reader);
+	}
+
+	/**
+	 * Deserializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)
+			 {
+		unknown01 = reader.getInt();
+		playerCompID = reader.getLong();
+	}
+
+	/**
+	 * Serializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer)
+			throws SerializationException {
+		writer.putInt(unknown01);
+		writer.putLong(playerCompID);
+	}
+
+	/**
+	 * @return the unknown01
+	 */
+	public int getUnknown01() {
+		return unknown01;
+	}
+
+	/**
+	 * @param unknown01 the unknown01 to set
+	 */
+	public void setUnknown01(int unknown01) {
+		this.unknown01 = unknown01;
+	}
+
+	/**
+	 * @return the playerCompID
+	 */
+	public long getPlayerCompID() {
+		return playerCompID;
+	}
+
+	/**
+	 * @param playerCompID the playerCompID to set
+	 */
+	public void setPlayerCompID(long playerCompID) {
+		this.playerCompID = playerCompID;
+	}
+
+}
diff --git a/src/engine/net/client/msg/ConfirmPromoteMsg.java b/src/engine/net/client/msg/ConfirmPromoteMsg.java
new file mode 100644
index 00000000..7532ff8c
--- /dev/null
+++ b/src/engine/net/client/msg/ConfirmPromoteMsg.java
@@ -0,0 +1,55 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+
+
+public class ConfirmPromoteMsg extends ClientNetMsg {
+
+
+
+    /**
+     * This is the general purpose constructor.
+     *
+     * @param channel
+     */
+    public ConfirmPromoteMsg(int channel) {
+        super(Protocol.CONFIRMPROMOTE);
+    }
+
+    /**
+     * This constructor is used by NetMsgFactory. It attempts to deserialize the
+     * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+     * past the limit) then this constructor Throws that Exception to the
+     * caller.
+     */
+    public ConfirmPromoteMsg(AbstractConnection origin, ByteBufferReader reader)  {
+        super(Protocol.CONFIRMPROMOTE, origin, reader);
+    }
+
+    /**
+     * Serializes the subclass specific items to the supplied NetMsgWriter.
+     */
+    @Override
+    protected void _serialize(ByteBufferWriter writer) {
+    }
+
+    /**
+     * Deserializes the subclass specific items from the supplied NetMsgReader.
+     */
+    @Override
+    protected void _deserialize(ByteBufferReader reader)  {
+    }
+}
diff --git a/src/engine/net/client/msg/DeclineFriendMsg.java b/src/engine/net/client/msg/DeclineFriendMsg.java
new file mode 100644
index 00000000..e0d2c6ce
--- /dev/null
+++ b/src/engine/net/client/msg/DeclineFriendMsg.java
@@ -0,0 +1,65 @@
+/*
+HashSet<Integer> playerFriendSet = PlayerFriendsMap.get(playerUID);
+			playerFriendSet.add(friendUID); * Copyright 2013 MagicBane Emulator Project
+ * All Rights Reserved
+ */
+package engine.net.client.msg;
+
+
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.objects.PlayerCharacter;
+
+
+public class DeclineFriendMsg extends ClientNetMsg {
+
+	public String sourceName;
+	public String friendName;
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public DeclineFriendMsg(PlayerCharacter pc) {
+		super(Protocol.FRIENDDECLINE);
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public DeclineFriendMsg(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.FRIENDDECLINE, origin, reader);
+	}
+
+	/**
+	 * Copy constructor
+	 */
+	public DeclineFriendMsg(DeclineFriendMsg msg) {
+		super(Protocol.FRIENDDECLINE);
+	}
+
+	
+	
+	/**
+	 * Deserializes the subclass specific items from the supplied ByteBufferReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		//Do we even want to try this?
+		this.sourceName = reader.getString(); //This is source name.. this friends list must never been updated since launch, not using IDS.
+		this.friendName = reader.getString();
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied ByteBufferWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+	writer.putString(this.sourceName);
+	writer.putString(this.friendName);
+	}
+}
diff --git a/src/engine/net/client/msg/DeleteItemMsg.java b/src/engine/net/client/msg/DeleteItemMsg.java
new file mode 100644
index 00000000..ca273e19
--- /dev/null
+++ b/src/engine/net/client/msg/DeleteItemMsg.java
@@ -0,0 +1,79 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+
+import engine.exception.SerializationException;
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+
+public class DeleteItemMsg extends ClientNetMsg {
+
+	private int type;
+	private int objectUUID;
+	private int unknown1, unknown2;
+
+	public DeleteItemMsg(int type, int objectUUID) {
+		super(Protocol.DELETEOBJECT);
+		this.type = type;
+		this.objectUUID = objectUUID;
+		this.unknown1 = 0;
+		this.unknown2 = 0;
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public DeleteItemMsg(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.DELETEOBJECT, origin, reader);
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		this.type = reader.getInt();
+		this.objectUUID = reader.getInt();
+		this.unknown1 = reader.getInt();
+		this.unknown2 = reader.getInt();
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) throws SerializationException {
+		writer.putInt(this.type);
+		writer.putInt(this.objectUUID);
+		writer.putInt(this.unknown1);
+		writer.putInt(this.unknown2);
+	}
+
+	/**
+	 * @return the type
+	 */
+	public int getType() {
+		return type;
+	}
+
+	/**
+	 * @return the objectUUID
+	 */
+	public int getUUID() {
+		return objectUUID;
+	}
+
+}
diff --git a/src/engine/net/client/msg/DestroyBuildingMsg.java b/src/engine/net/client/msg/DestroyBuildingMsg.java
new file mode 100644
index 00000000..844b2371
--- /dev/null
+++ b/src/engine/net/client/msg/DestroyBuildingMsg.java
@@ -0,0 +1,72 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+
+import engine.exception.SerializationException;
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+
+public class DestroyBuildingMsg extends ClientNetMsg {
+	private int pad = 0;
+	private int objectType;
+	private int objectUUID;
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public DestroyBuildingMsg(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.DESTROYBUILDING, origin, reader);
+	}
+	/**
+	 * Deserializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		this.pad = reader.getInt();
+		this.objectType = reader.getInt();
+		this.objectUUID = reader.getInt();;
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) throws SerializationException {
+		writer.putInt(this.pad);
+		writer.putInt(this.objectType);
+		writer.putInt(this.objectUUID);
+	}
+
+	public int getObjectType() {
+		return objectType;
+	}
+
+	public void setObjectType(int value) {
+		this.objectType = value;
+	}
+
+	public void setPad(int value) {
+		this.pad = value;
+	}
+
+	public int getUUID() {
+		return objectUUID;
+	}
+
+	public int getPad() {
+		return pad;
+	}
+}
diff --git a/src/engine/net/client/msg/DoorTryOpenMsg.java b/src/engine/net/client/msg/DoorTryOpenMsg.java
new file mode 100644
index 00000000..6fb0fc36
--- /dev/null
+++ b/src/engine/net/client/msg/DoorTryOpenMsg.java
@@ -0,0 +1,158 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+
+
+
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+
+
+
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+
+
+
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+
+package engine.net.client.msg;
+
+import engine.Enum.GameObjectType;
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+
+
+public class DoorTryOpenMsg extends ClientNetMsg {
+
+	private int unk1;
+	private int doorID;
+	private int buildingUUID;
+	private int playerUUID;
+	private byte toggle;
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public DoorTryOpenMsg(int doorID, int targetID, int senderID, byte toggle) {
+		super(Protocol.DOORTRYOPEN);
+		this.unk1 = 0;
+		this.doorID = doorID;
+		this.buildingUUID = targetID;
+		this.playerUUID = senderID;
+		this.toggle = toggle;
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public DoorTryOpenMsg(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.DOORTRYOPEN, origin, reader);
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+		if (this.unk1 == 2) {
+			writer.putInt(2);
+			for (int i=0;i<6;i++)
+				writer.putInt(0);
+			writer.put((byte)0);
+		} else {
+			writer.putInt(0); //0 or 2. If 2 all other variables are 0
+			writer.putInt(0);
+			writer.putInt(doorID);
+                        writer.putInt(GameObjectType.Building.ordinal());
+			writer.putInt(buildingUUID);
+                        writer.putInt(GameObjectType.PlayerCharacter.ordinal());
+			writer.putInt(playerUUID);
+			writer.put(toggle);
+		}
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		this.unk1 = reader.getInt();
+		reader.getInt();
+		this.doorID = reader.getInt();
+                reader.getInt(); // type padding
+		this.buildingUUID = reader.getInt();
+                reader.getInt(); // type padding
+		this.playerUUID = reader.getInt();
+		this.toggle = reader.get();
+	}
+
+	/**
+	 * @return the unknown1
+	 */
+	public int getDoorID() {
+		return doorID;
+	}
+
+	/**
+	 * @return the buildingUUID
+	 */
+	public int getBuildingUUID() {
+		return buildingUUID;
+	}
+
+	/**
+	 * @return the playerUUID
+	 */
+	public int playerUUID() {
+		return playerUUID;
+	}
+
+	/**
+	 * @return the toggle
+	 */
+	public byte getToggle() {
+		return toggle;
+	}
+
+	public int getUnk1() {
+		return this.unk1;
+	}
+
+	public void setUnk1(int value) {
+		this.unk1 = value;
+	}
+}
diff --git a/src/engine/net/client/msg/DropGoldMsg.java b/src/engine/net/client/msg/DropGoldMsg.java
new file mode 100644
index 00000000..f16b8c41
--- /dev/null
+++ b/src/engine/net/client/msg/DropGoldMsg.java
@@ -0,0 +1,146 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+
+public class DropGoldMsg extends ClientNetMsg {
+
+	private int unknown01;
+	private int unknown02;
+	private int unknown03;
+	private short unknown04;
+	private byte unknown05;
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public DropGoldMsg() {
+		super(Protocol.DROPGOLD);
+		this.unknown01 = 0x40A5BDB0;
+		this.unknown02 = 0x342AA9F0;
+		this.unknown03 = 0;
+		this.unknown04 = (short) 0;
+		this.unknown05 = (byte) 0;
+
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public DropGoldMsg(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.DROPGOLD, origin, reader);
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		reader.getInt();
+		reader.getInt();
+		reader.getInt();
+		reader.getInt();
+		
+		reader.getInt();
+		reader.get();
+		
+	}
+
+	/**
+	 * @return the unknown01
+	 */
+	public int getUnknown01() {
+		return unknown01;
+	}
+
+	/**
+	 * @param unknown01
+	 *            the unknown01 to set
+	 */
+	public void setUnknown01(int unknown01) {
+		this.unknown01 = unknown01;
+	}
+
+	/**
+	 * @return the unknown02
+	 */
+	public int getUnknown02() {
+		return unknown02;
+	}
+
+	/**
+	 * @param unknown02
+	 *            the unknown02 to set
+	 */
+	public void setUnknown02(int unknown02) {
+		this.unknown02 = unknown02;
+	}
+
+	/**
+	 * @return the unknown03
+	 */
+	public int getUnknown03() {
+		return unknown03;
+	}
+
+	/**
+	 * @param unknown03
+	 *            the unknown03 to set
+	 */
+	public void setUnknown03(int unknown03) {
+		this.unknown03 = unknown03;
+	}
+
+	/**
+	 * @return the unknown04
+	 */
+	public short getUnknown04() {
+		return unknown04;
+	}
+
+	/**
+	 * @param unknown04
+	 *            the unknown04 to set
+	 */
+	public void setUnknown04(short unknown04) {
+		this.unknown04 = unknown04;
+	}
+
+	/**
+	 * @return the unknown05
+	 */
+	public byte getUnknown05() {
+		return unknown05;
+	}
+
+	/**
+	 * @param unknown05
+	 *            the unknown05 to set
+	 */
+	public void setUnknown05(byte unknown05) {
+		this.unknown05 = unknown05;
+	}
+
+}
diff --git a/src/engine/net/client/msg/EnterWorldReceivedMsg.java b/src/engine/net/client/msg/EnterWorldReceivedMsg.java
new file mode 100644
index 00000000..2845378c
--- /dev/null
+++ b/src/engine/net/client/msg/EnterWorldReceivedMsg.java
@@ -0,0 +1,58 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+
+
+public class EnterWorldReceivedMsg extends ClientNetMsg {
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public EnterWorldReceivedMsg() {
+		super(Protocol.READYTOENTER);
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public EnterWorldReceivedMsg(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.READYTOENTER, origin, reader);
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+		return;
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		return;
+	}
+
+	@Override
+	protected int getPowerOfTwoBufferSize() {
+		return 18; //65536 bytes for enter world.
+	}
+}
diff --git a/src/engine/net/client/msg/ErrorPopupMsg.java b/src/engine/net/client/msg/ErrorPopupMsg.java
new file mode 100644
index 00000000..f9bac8f8
--- /dev/null
+++ b/src/engine/net/client/msg/ErrorPopupMsg.java
@@ -0,0 +1,316 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+import engine.Enum;
+import engine.net.*;
+import engine.net.client.Protocol;
+import engine.objects.PlayerCharacter;
+
+public class ErrorPopupMsg extends ClientNetMsg {
+
+	//1: Sorry, but that individual is not a banker
+	//2: Sorry, but you must be closer to the banker to access your account
+	//3: Sorry, but you have insufficient funds to access your acount
+	//4: The shop is closed
+	//5: You must come closer to shop
+	//6: You do not have enough money to purchase that
+	//7: You cannot carry that item
+	//8: The banker cannot carry that item
+	//9: You do not have that much gold to drop
+	//10: That item cannot be dropped
+	//11: You cannot drop what you do not have
+	//12: Sorry, but this container is locked
+	//13: Sorry, but this container is barred
+	//14: You must come closer to me
+	//15: You no longer have that item to sell
+	//16: I won't buy that kind of item
+	//17: I cannot afford that item
+	//18: You can't really afford that
+	//19: This item is gone from inventory
+	//20: Your resurection has been declined
+	//21: I cannot carry that weight
+	//22: You just dropped that item on the ground
+	//23: This corpse has no experience to return
+	//24: This player is not in world
+	//25: This player is not online
+	//26: You selected an invalid location
+	//27: You are dead. Try again when you are not so...well, dead
+	//28: Your target is dead and cannot be summoned
+	//29: Your summons has been declined
+	//30: That person cannot carry that item
+	//31: An unexpected error has occurred and the trade is being canceled
+	//32: You must choose a promotion class before gaining your next level. Speak to a class trainer
+	//33: Promotion failed
+	//34: Come back when you've gained more experience
+	//35: This hireling failed to buy the item
+	//36: I don't buy items
+	//37: I don't sell items
+	//38: You appear to be in a building normally
+	//39: There does not appear to be a building where you are
+	//40: I don't swear guilds
+	//41: I server no sovereign
+	//42: Your guild is not errant
+	//43: Members of your guild are too high in level
+	//44: You cannot afford this service
+	//45: Failure to swear guild
+	//46: Cannot swear under ruins
+	//47: Your guild is the wrong type to swear to this guild
+	//48: Hireling could not hire
+	//49: I do not hire
+	//50: This pet is gone
+	//51: You have successfully promoted to a new class
+	//52: You have successfully added a new discipline
+	//53: You no longer meet the level requirement to stay in this guild
+	//54: That item is too advanced
+	//55: All production slots are taken
+	//56: That enchantment is too advanced
+	//57: That formula is too advanced
+	//58: The formula is beyond the means of this facility
+	//59: This hireling does not have this formula
+	//60: Hireling does not possess that item!
+	//61: Hireling does not work with such items
+	//62: You may only trade with items in your inventory
+	//63: You must be within your building to do that
+	//64: You are too far from the building to do that
+	//65: I cannot enthrall creatures
+	//66: I cannot repledge you
+	//67: I cannot teleport you
+	//68: You have no valid thralls
+	//69: You have a thrall that I do not understand
+	//70: I cannot afford your thrall
+	//71: There are no cities to which you can repledge
+	//72: There are no cities to which you can teleport
+	//73: You are too low level to repledge
+	//74: You are too low level to teleport
+	//75: You must leave your current guild before you can repledge
+	//76: Failure to repledge
+	//77: Failure to teleport
+	//78: You do not meet the qualifications to join that city
+	//79: There are too many furniture items in this asset
+	//80: Attempting to add furniture to bad location
+	//81: This deed codes for a bad furniture prop
+	//82: Object is not an appropriate furniture deed
+	//83: Unable to find corresponding furniture on asset
+	//84: The chose game world is not valid <- will kick out of world
+	//85: The game world is temporarily unavailable <- will kick out of world
+	//86: The runegate is unnaffected by this power
+	//87: You are not powerful enough to activate this gate
+	//88: This runegate is already on
+	//89: You cannot unbanish this one until the timestamp expires
+	//90: You cannot trade while either you or the target is invisible
+	//91: You cannot trade while in combat mode
+	//92: That person is already engaged in a trade
+	//93: You must be closer to trade
+	//94: The trade was successful
+	//95: The trade was not successful
+	//96: The trade has failed because one of you would exceed your gold limit
+	//97: You must be closer to open that
+	//98: You cannot loot while flying
+	//99: Your target is not dead
+	//100: You cannot loot a trainer
+	//101: You cannot loot a shopkeeper
+	//102: You cannot loot a banker
+	//103: You cannot add this individual to the condemn list
+	//104: You cannot add the owner's guild to the condemn list
+	//105: You cannot add the owner's nation to the condemn list
+	//106: Failure to add to the condemn list
+	//107: Unable to find desired group
+	//108: Group is at maximum membership
+	//109: Failed to add item to hireling
+	//110: Failer to remove item from hireling
+	//111: This item cannot be removed from inventory
+	//112: Your account has no characters
+	//113: Failure to start support
+	//114: Cannot add another support
+	//115: This type of asset cannot receive protection
+	//116: This asset already has protection upon it
+	//117: Failure to remove support
+	//118: Failure to complete support
+	//119: Failure to reject support
+	//120: This asset is not a banecircle
+	//121: You are not a CSR who can advance banecircle stage
+	//122: Failure to advance banecircle stage
+	//123: You do not have the authority within your guild to modify this banecircle
+	//124: Banecircle cannot advance once in final stage
+	//125: Failure to repair Asset
+	//126: Asset does not require repair
+	//127: No gold in asset strongbox
+	//128: Insufficient funds for even one point of repair
+	//129: You cannot bond where you are killed-on-sight
+	//130: You cannot join where you are killed-on-sight
+	//131: You do not meet the level required for this SWORN guild
+	//132: You are already a member of this guild
+	//133: Your banishment from this guild has not yet been lifted
+	//134: Your QUIT status from this guild has not yet expired
+	//135: Character is considered BANISHED by guild leadership
+	//136: Your class is not allowed to teleport here
+	//137: You have no affiliation with this tree
+	//138: You can never join this type of tree
+	//139: You do not meet the safehold level requirement
+	//140: Ruined trees are invalid
+	//141: Unclaimed trees are invalid
+	//142: You are the wrong race for this city
+	//143: You are the wrong class for this city
+	//144: You are the wrong sex for this city
+	//145: You are too low level for this city
+	//146: You do not meet the level requirements for this city
+	//147: Tree must be rank 5 to open city
+	//148: Unable to find a matching petition to complete guild creation
+	//149: Guild name fails profanity check
+	//150: Guild motto fails profanity check
+	//151: Guild name is not unique
+	//152: Guild crest is not unique
+	//153: Guild crest is reserved
+	//154: All three crest colors cannot be the same
+	//155: Please choose another name
+	//156: You cannot bank and trade at the same time
+	//157: You must not move or engage in combat for 10 seconds before stuck will work
+	//158: Your gold has been dropped on the ground
+	//159: Merchant cannot purchase item without exceeding his reserve
+	//160: Rune succesfully applied
+	//161: You cannot apply that rune
+	//162: You rely too heavily on that rune to remove it
+	//163: This shrine does not take offerings of that type
+	//164: This hireling cannot grant boons
+	//165: This hireling cannot display the leaderboard
+	//166: There is no more favor in this shrine to loot
+	//167: There are no more resources in this warehouse to loot
+	//168: This boon is only for guild members belonging to this shrine
+	//169: You do not meet the race/class requirements for this boon
+	//170: This shrine is no longer capable of granting boons
+	//171: This asset cannot be destroyed during times of war
+	//172: This shrine has no favor
+	//173: You must be the leader of a guild to receive a blessing
+	//174: This siege spire cannot be toggled yet. Please try again later
+	//175: You cannot teleport into that zone at the moment
+	//176: Only guild leaders can claim a territory
+	//177: Your nation has already reached the maximum number of capitals
+	//178: This territory is already claimed
+	//179: Only landed guilds may claim a territory
+	//180: This territory cannot be ruled by anyone
+	//181: Your tree must be rank 7 before claiming a territory
+	//182: This realm is in turmoil and cannot be claimed yet
+	//183: You cannot rule a guild under a different faction then your parent guild
+	//184: Insufficient gold or resources to upgrade to capital
+	//185: You must seek the blessing of the three sages before you can rule
+	//186: Your tree is not inside a territory!
+	//187: This realm is in turmoil and cannot yet be claimed!
+	//188: You must have a warehouse to become a capital
+	//189: You are not the owner of this building
+	//190: This building cannot be upgraded further
+	//191: You don't have the required funds
+	//192: This building is already upgrading
+	//193: Production denied: This building must be protected to gain access to warehouse resources..
+	//194: The operation failed because you reached your gold limit
+	//195: That player is currently busy completing the last trade. Try again in a few moments
+	//196: You are currently busy completing the last trade. Try again in a few moments
+	//197: You cannot join a guild whose nation has a guild that is currently involved in a siege
+	//198: You cannot repledge while you are involved in a siege
+	//199: You already have this boon
+	//200: Your vault cannot contain that item
+	//201: You can't put that much gold there
+	//202: You can't carry that much gold
+	//203: You don't have that much gold to transfer
+	//204: That item is not in the vault
+	//205: That item is not in the inventory
+	//206: This building can hold no more gold
+	private int message;
+	private String custom = "";
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public ErrorPopupMsg(int message) {
+		super(Protocol.STANDARDALERT);
+		this.message = message;
+	}
+
+	public ErrorPopupMsg(int message, String custom) {
+		super(Protocol.STANDARDALERT);
+		this.message = message;
+		this.custom = custom;
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public ErrorPopupMsg(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.STANDARDALERT, origin, reader);
+	}
+
+	/**
+	 * Copy constructor
+	 */
+	public ErrorPopupMsg(ErrorPopupMsg msg) {
+		super(Protocol.STANDARDALERT);
+		this.message = msg.message;
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		this.message = reader.getInt();
+		reader.getInt();
+		reader.getInt();
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+		writer.putInt(this.message);
+		writer.putString(this.custom);
+		writer.putInt(0);
+	}
+
+	/**
+	 * @return unknown01
+	 */
+
+	// Popup Window with no title and arbitrary text.
+	// Find an Enum for generic ERROR or way to set perhaps?
+
+	public static void sendErrorMsg(PlayerCharacter player, String errorMessage) {
+
+		if (player == null)
+			return;
+
+
+		ErrorPopupMsg popupMessage;
+		Dispatch errorDispatch;
+
+		popupMessage = new ErrorPopupMsg(300, errorMessage);
+
+		errorDispatch = Dispatch.borrow(player, popupMessage);
+		DispatchMessage.dispatchMsgDispatch(errorDispatch, Enum.DispatchChannel.SECONDARY);
+
+	}
+
+	public static void sendErrorPopup(PlayerCharacter player, int popupID) {
+
+		ErrorPopupMsg errorPopup;
+		Dispatch errorDispatch;
+
+		if (player == null)
+			return;
+
+		errorPopup = new ErrorPopupMsg(popupID);
+
+		errorDispatch = Dispatch.borrow(player, errorPopup);
+		DispatchMessage.dispatchMsgDispatch(errorDispatch, Enum.DispatchChannel.SECONDARY);
+	}
+}
diff --git a/src/engine/net/client/msg/FriendRequestMsg.java b/src/engine/net/client/msg/FriendRequestMsg.java
new file mode 100644
index 00000000..6c2c1a2f
--- /dev/null
+++ b/src/engine/net/client/msg/FriendRequestMsg.java
@@ -0,0 +1,65 @@
+/*
+HashSet<Integer> playerFriendSet = PlayerFriendsMap.get(playerUID);
+			playerFriendSet.add(friendUID); * Copyright 2013 MagicBane Emulator Project
+ * All Rights Reserved
+ */
+package engine.net.client.msg;
+
+
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.objects.PlayerCharacter;
+
+
+public class FriendRequestMsg extends ClientNetMsg {
+
+	public String sourceName;
+	public String friendName;
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public FriendRequestMsg(PlayerCharacter pc) {
+		super(Protocol.PLAYERFRIENDS);
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public FriendRequestMsg(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.PLAYERFRIENDS, origin, reader);
+	}
+
+	/**
+	 * Copy constructor
+	 */
+	public FriendRequestMsg(FriendRequestMsg msg) {
+		super(Protocol.PLAYERFRIENDS);
+	}
+
+	
+	
+	/**
+	 * Deserializes the subclass specific items from the supplied ByteBufferReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		//Do we even want to try this?
+		this.sourceName = reader.getString(); //This is source name.. this friends list must never been updated since launch, not using IDS.
+		this.friendName = reader.getString();
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied ByteBufferWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+	writer.putString(this.sourceName);
+	writer.putString(this.friendName);
+	}
+}
diff --git a/src/engine/net/client/msg/FurnitureMsg.java b/src/engine/net/client/msg/FurnitureMsg.java
new file mode 100644
index 00000000..e496e75b
--- /dev/null
+++ b/src/engine/net/client/msg/FurnitureMsg.java
@@ -0,0 +1,294 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+import engine.Enum.GameObjectType;
+import engine.exception.SerializationException;
+import engine.math.Vector3fImmutable;
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+
+public class FurnitureMsg extends ClientNetMsg {
+
+
+	private int type;
+	private int buildingID;
+	private int size;
+	private int pad = 0;
+	private int itemID;
+	private Vector3fImmutable furnitureLoc;
+	private int floor;
+	private float rot;
+	private float w;
+
+
+
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public FurnitureMsg(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.FURNITURE, origin, reader);
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		this.type = reader.getInt();
+		reader.getInt();
+		this.buildingID = reader.getInt();
+		if (this.type == 3){
+			reader.getInt();
+			reader.getInt();
+			reader.getInt();
+            this.itemID = reader.getInt();
+            this.furnitureLoc = reader.getVector3fImmutable();
+            reader.getInt();
+			reader.getInt();
+			reader.getInt();
+			reader.getInt();
+			return;
+
+
+		}
+		reader.getInt();
+		reader.getInt();
+		reader.getInt();
+		reader.getInt();
+
+
+	}
+
+	@Override
+	protected int getPowerOfTwoBufferSize() {
+		// Larger size for historically larger opcodes
+		return (15);
+	}
+
+
+	// Precache and configure this message before we serialize it
+
+	public void configure() {
+
+
+
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) throws SerializationException {
+		if (this.type == 3 || this.type == 4){
+			writer.putInt(this.type);
+			writer.putInt(GameObjectType.Building.ordinal());
+			writer.putInt(this.buildingID);
+			writer.putInt(0);
+			writer.putInt(0);
+			writer.putInt(GameObjectType.Item.ordinal());
+			writer.putInt(62280);
+			writer.putVector3f(this.furnitureLoc);
+			writer.putInt(0);
+			writer.putInt(0);
+			writer.putInt(0);
+			writer.putInt(0);
+			return;
+		}
+		
+		
+		writer.putInt(2); //Type
+		writer.putInt(GameObjectType.Building.ordinal());
+		writer.putInt(this.buildingID);
+		writer.putInt(GameObjectType.Building.ordinal());
+		writer.putInt(this.buildingID);
+		writer.put((byte)1);
+		writer.putInt(1);
+		writer.putInt(GameObjectType.Item.ordinal());
+		writer.putInt(62280);
+		writer.putInt(0);
+	
+		writer.putInt(1);
+	
+		writer.putInt(0);
+		writer.putInt(362003);
+		writer.putInt(GameObjectType.Item.ordinal());
+		writer.putInt(62280);
+		
+		writer.putInt(GameObjectType.Building.ordinal());
+		writer.putInt(this.buildingID);
+		
+		writer.putInt(0);
+		writer.putInt(0);
+				
+				writer.putInt(0);
+				writer.putInt(0);
+				writer.putInt(0);
+				writer.putInt(0);
+				
+				writer.put((byte)0);
+				
+			//	writer.putInt(0);
+		//writer.putInt(0);
+		
+//		writer.putInt(1);
+//		writer.putInt(GameObjectType.Building.ordinal());
+//				writer.putInt(62281);
+//				writer.putInt(0);
+//				writer.putInt(362003);
+//				writer.putInt(0);
+//				writer.putInt(0);
+//				writer.putInt(0);
+//				writer.putInt(0);
+//				writer.putInt(0);
+//				writer.putInt(0);
+//				writer.putInt(0);
+//				writer.putInt(0);
+//				writer.put((byte)1);
+//				writer.putInt(1);
+//				writer.putInt(GameObjectType.Building.ordinal());
+//						writer.putInt(62281);
+//						writer.putInt(0);
+//						writer.putInt(362003);
+//						writer.putInt(0);
+//						writer.putInt(0);
+//						writer.putInt(0);
+//						writer.putInt(0);
+//						writer.putInt(0);
+//						writer.putInt(0);
+//						writer.putInt(0);
+//						writer.putInt(0);
+//						writer.put((byte)0);
+
+		
+
+	
+		//		else{
+		//			writer.putInt(building.getFurnitureList().size());
+		//			for (int furnitureID: building.getFurnitureList()){
+		//				Building furniture = Building.getBuildingFromCache(furnitureID);
+		//				writer.putInt(GameObjectType.Building.ordinal());
+		//				writer.putInt(furniture.getObjectUUID());
+		//				writer.putInt(0);
+		//				writer.putInt(0);
+		//				writer.putInt(0);
+		//				writer.putInt(0);
+		//				writer.putInt(0);
+		//				writer.putInt(0);
+		//				writer.putInt(0);
+		//				writer.putInt(0);
+		//				writer.putInt(0);
+		//				writer.putInt(0);
+		//				writer.put((byte)0);
+		//			}
+
+		//}
+
+		//		else{
+		//			writer.putInt(building.getFurnitureList().size());
+		//			for (int furnitureID: building.getFurnitureList()){
+		//				Building furniture = Building.getBuildingFromCache(furnitureID);
+		//				writer.putInt(GameObjectType.Building.ordinal());
+		//				writer.putInt(furniture.getObjectUUID());
+		//				writer.putInt(0);
+		//				writer.putInt(0);
+		//				writer.putInt(0);
+		//				writer.putInt(0);
+		//				writer.putInt(0);
+		//				writer.putInt(0);
+		//				writer.putInt(0);
+		//				writer.putInt(0);
+		//				writer.putInt(0);
+		//				writer.putInt(0);
+		//				writer.put((byte)0);
+		//			}
+		//
+		//		}
+	}
+
+	public int getPad() {
+		return pad;
+	}
+
+	public void setPad(int value) {
+		this.pad = value;
+	}
+
+	public int getType() {
+		return type;
+	}
+
+	public void setType(int type) {
+		this.type = type;
+	}
+
+	public int getSize() {
+		return size;
+	}
+
+	public void setSize(int size) {
+		this.size = size;
+	}
+
+	public int getBuildingID() {
+		return buildingID;
+	}
+
+	public void setBuildingID(int buildingID) {
+		this.buildingID = buildingID;
+	}
+
+	public int getItemID() {
+		return itemID;
+	}
+
+	public void setItemID(int itemID) {
+		this.itemID = itemID;
+	}
+
+	public Vector3fImmutable getFurnitureLoc() {
+		return furnitureLoc;
+	}
+
+	public void setFurnitureLoc(Vector3fImmutable furnitureLoc) {
+		this.furnitureLoc = furnitureLoc;
+	}
+
+	public int getFloor() {
+		return floor;
+	}
+
+	public void setFloor(int floor) {
+		this.floor = floor;
+	}
+
+	public float getRot() {
+		return rot;
+	}
+
+	public void setRot(float rot) {
+		this.rot = rot;
+	}
+
+	public float getw() {
+		return w;
+	}
+
+	public void setw(float w) {
+		this.w = w;
+	}
+
+}
diff --git a/src/engine/net/client/msg/GrantExperienceMsg.java b/src/engine/net/client/msg/GrantExperienceMsg.java
new file mode 100644
index 00000000..d9799a37
--- /dev/null
+++ b/src/engine/net/client/msg/GrantExperienceMsg.java
@@ -0,0 +1,100 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.objects.PlayerCharacter;
+
+public class GrantExperienceMsg extends ClientNetMsg {
+
+    private int xpGranted;
+    private int objectType;
+    private int objectID;
+
+    /**
+     * This is the general purpose constructor.
+     */
+    public GrantExperienceMsg(PlayerCharacter pc, int xpGranted) {
+        super(Protocol.EXPERIENCE);
+        this.xpGranted = xpGranted;
+        this.objectType = pc.getObjectType().ordinal();
+        this.objectID = pc.getObjectUUID();
+    }
+
+    /**
+     * This constructor is used by NetMsgFactory. It attempts to deserialize the
+     * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+     * past the limit) then this constructor Throws that Exception to the
+     * caller.
+     */
+    public GrantExperienceMsg(AbstractConnection origin, ByteBufferReader reader)  {
+        super(Protocol.EXPERIENCE, origin, reader);
+    }
+
+    /**
+     * Copy constructor
+     */
+    public GrantExperienceMsg(GrantExperienceMsg msg) {
+        super(Protocol.EXPERIENCE);
+        this.xpGranted = msg.xpGranted;
+        this.objectType = msg.objectType;
+        this.objectID = msg.objectID;
+    }
+
+    /**
+     * Deserializes the subclass specific items from the supplied
+     * ByteBufferReader.
+     */
+    @Override
+    protected void _deserialize(ByteBufferReader reader)  {
+        reader.get();
+        this.xpGranted = reader.getInt();
+        this.objectType = reader.getInt();
+        this.objectID = reader.getInt();
+    }
+
+    /**
+     * Serializes the subclass specific items to the supplied ByteBufferWriter.
+     */
+    @Override
+    protected void _serialize(ByteBufferWriter writer) {
+        writer.putInt(this.xpGranted);
+        writer.putInt(this.objectType);
+        writer.putInt(this.objectID);
+    }
+
+    public int getXPGranted() {
+        return this.xpGranted;
+    }
+
+    public int getObjectType() {
+        return this.objectType;
+    }
+
+    public int getObjectID() {
+        return this.objectID;
+    }
+
+    public void setXPGranted(int value) {
+        this.xpGranted = value;
+    }
+
+    public void setObjectType(int value) {
+        this.objectType = value;
+    }
+
+    public void setObjectID(int value) {
+        this.objectID = value;
+    }
+}
diff --git a/src/engine/net/client/msg/GuildTreeStatusMsg.java b/src/engine/net/client/msg/GuildTreeStatusMsg.java
new file mode 100644
index 00000000..9a6c2666
--- /dev/null
+++ b/src/engine/net/client/msg/GuildTreeStatusMsg.java
@@ -0,0 +1,195 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.objects.*;
+
+import java.time.LocalDateTime;
+
+public class GuildTreeStatusMsg extends ClientNetMsg {
+
+    // 2 = manage this asset.  20 = manage entire city
+    private int targetType;
+    private int targetID;
+    private String CityName;
+    private String OwnerName;
+    private String GuildName;
+
+    private String motto; //motto Length 60 max?
+    private Building treeOfLife;
+    private PlayerCharacter player;
+    private City city;
+    private Zone cityZone;
+    private GuildTag cityGuildTag;
+    private GuildTag cityNationTag;
+    private java.time.LocalDateTime cityDate;
+    private boolean canAccess = false;
+    private byte canBind = 0;
+    private int accessType = 0;
+
+    /**
+     * This is the general purpose constructor
+     */
+    public GuildTreeStatusMsg() {
+        super(Protocol.GUILDTREESTATUS);
+
+        this.targetType = 0;
+        this.targetID = 0;
+        this.OwnerName = "";
+        this.CityName = "";
+        this.GuildName = "";
+        this.cityGuildTag = null;
+        this.cityNationTag = null;
+    }
+
+    public GuildTreeStatusMsg(Building treeOfLife, PlayerCharacter sourcePlayer) {
+        super(Protocol.GUILDTREESTATUS);
+        this.treeOfLife = treeOfLife;
+        this.player = sourcePlayer;
+    }
+
+    /**
+     * This constructor is used by NetMsgFactory. It attempts to deserialize the
+     * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+     * past the limit) then this constructor Throws that Exception to the
+     * caller.
+     */
+    public GuildTreeStatusMsg(AbstractConnection origin, ByteBufferReader reader)
+             {
+        super(Protocol.GUILDTREESTATUS, origin, reader);
+    }
+
+    /**
+     * Deserializes the subclass specific items to the supplied NetMsgWriter.
+     */
+    @Override
+    protected void _deserialize(ByteBufferReader reader)
+             {
+
+        targetType = reader.getInt();
+        targetID = reader.getInt();
+
+        for (int i = 0; i < 3; i++) {
+            reader.monitorInt(0, "GuildTreeStatusMSG");
+        }
+    }
+
+    public void configure() {
+
+        this.targetType = treeOfLife.getObjectType().ordinal();
+        this.targetID = treeOfLife.getObjectUUID();
+        this.OwnerName = treeOfLife.getOwner() != null ? treeOfLife.getOwnerName() : "Abandoned";
+        this.CityName = treeOfLife.getCityName();
+        this.GuildName = treeOfLife.getGuildName();
+
+        this.cityGuildTag = treeOfLife.getGuild().getGuildTag();
+        this.cityNationTag = this.treeOfLife.getGuild().getNation().getGuildTag();
+
+        canAccess = this.canModify();
+        canBind = 0;
+        
+        if (player.getGuild() != null && this.treeOfLife.getGuild() != null && !this.treeOfLife.getGuild().isErrant()
+                && player.getGuild().getNation() == this.treeOfLife.getGuild().getNation())
+            canBind = 1;
+
+
+        if (this.treeOfLife.getGuild() != null && this.treeOfLife.getGuild().getOwnedCity() == null)
+            accessType = 9;
+
+        //accessType not 9 not null city
+        if (accessType != 9)
+            if (this.treeOfLife.getGuild().getOwnedCity().isForceRename() && canAccess )
+                accessType = 10;
+            else accessType = 8;
+
+        cityZone = this.treeOfLife.getParentZone();
+        city = null;
+
+        if (cityZone != null)
+        	if (cityZone.isPlayerCity())
+            city = City.GetCityFromCache(cityZone.getPlayerCityUUID());
+        	else
+        		if (this.treeOfLife != null && this.treeOfLife.getGuild() != null)
+        			city = this.treeOfLife.getGuild().getOwnedCity();
+        
+
+        if (city == null)
+            CityName = "None";
+        else
+            CityName = city.getCityName();
+
+        if (city == null)
+            cityDate = LocalDateTime.now();
+        else
+            cityDate = city.established;
+
+    }
+
+    private boolean canModify() {
+
+        return this.player.getGuild() == this.treeOfLife.getGuild() && GuildStatusController.isInnerCouncil(player.getGuildStatus());
+    }
+
+    /**
+     * Serializes the subclass specific items from the supplied NetMsgReader.
+     */
+    @Override
+    protected void _serialize(ByteBufferWriter writer) {
+
+        writer.putInt(targetType);
+        writer.putInt(targetID);
+
+        if (canAccess)
+            writer.putInt(accessType);
+      
+        else
+            writer.putInt(9);
+
+        GuildTag._serializeForDisplay(cityGuildTag,writer);
+        GuildTag._serializeForDisplay(cityNationTag,writer);
+
+        writer.putString(CityName);
+        writer.putString(GuildName);
+        writer.putString(OwnerName);
+
+        writer.putLocalDateTime(cityDate);
+        writer.putInt(0);
+        writer.putInt(0);
+        if (city == null)
+        	writer.putInt(0);
+        else
+        writer.putInt(city.isOpen() ? 1 : 0); //check mark for open city
+        writer.putInt(0);
+        writer.putInt(0);
+        writer.put(canBind);
+        writer.put((byte) 0);
+
+        if (canAccess)
+            writer.put((byte) 1);
+        else
+            writer.put((byte) 0);
+
+        writer.put((byte) 0);
+
+        writer.putInt(1);
+        writer.putString(city != null ? city.getDescription() : "None");
+        writer.putInt(0);
+    }
+
+    public int getTargetID() {
+        return targetID;
+    }
+
+}
\ No newline at end of file
diff --git a/src/engine/net/client/msg/HirelingServiceMsg.java b/src/engine/net/client/msg/HirelingServiceMsg.java
new file mode 100644
index 00000000..f60636ef
--- /dev/null
+++ b/src/engine/net/client/msg/HirelingServiceMsg.java
@@ -0,0 +1,122 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+
+import engine.Enum.GameObjectType;
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+
+
+public class HirelingServiceMsg extends ClientNetMsg {
+
+
+	public int npcID;
+	public int buildingID;
+	public int messageType;
+	public int repairCost;
+	
+	public static final int SETREPAIRCOST = 2;
+
+    /**
+     * This is the general purpose constructor.
+     *
+     * @param channel
+     */
+    public HirelingServiceMsg(int channel) {
+        super(Protocol.HIRELINGSERVICE);
+    }
+
+    /**
+     * This constructor is used by NetMsgFactory. It attempts to deserialize the
+     * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+     * past the limit) then this constructor Throws that Exception to the
+     * caller.
+     */
+    public HirelingServiceMsg(AbstractConnection origin, ByteBufferReader reader)  {
+        super(Protocol.HIRELINGSERVICE, origin, reader);
+    }
+
+    /**
+     * Serializes the subclass specific items to the supplied NetMsgWriter.
+     */
+    @Override
+    protected void _serialize(ByteBufferWriter writer) {
+    	writer.putInt(this.messageType);
+    	switch (this.messageType){
+    	case SETREPAIRCOST:
+    		this.writeSetRepairCost(writer);
+    		break;
+    	}
+    }
+
+    /**
+     * Deserializes the subclass specific items from the supplied NetMsgReader.
+     */
+    @Override
+    protected void _deserialize(ByteBufferReader reader)  {
+    	this.messageType = reader.getInt();
+    	
+    	switch (this.messageType){
+    	case SETREPAIRCOST:
+    		this.readSetRepairCost(reader);
+    		break;
+    		
+    	}
+    }
+    
+    private void readSetRepairCost(ByteBufferReader reader){
+    	reader.getInt(); //building type;
+    	this.buildingID = reader.getInt();
+    	reader.getInt();
+    	this.npcID = reader.getInt();
+    	reader.getInt();
+    	reader.getInt(); //3
+    	this.repairCost = reader.getInt();
+    	reader.getInt();
+    	reader.getInt();
+    	
+    }
+    
+    private void writeSetRepairCost(ByteBufferWriter writer){
+    	writer.putInt(GameObjectType.Building.ordinal());
+    	writer.putInt(this.buildingID);
+    	writer.putInt(GameObjectType.NPC.ordinal());
+    	writer.putInt(this.npcID);
+    	writer.putInt(0);
+    	writer.putInt(3);
+    	writer.putInt(this.repairCost);
+    	writer.putInt(0);
+    	writer.putInt(0);
+    }
+    
+    private void writeTest(ByteBufferWriter writer){
+    	writer.putInt(GameObjectType.Building.ordinal());
+    	writer.putInt(this.buildingID);
+    	writer.putInt(GameObjectType.NPC.ordinal());
+    	writer.putInt(this.npcID);
+    	writer.putInt(3);
+    	writer.putInt(1);
+    	writer.putInt(1);
+    	writer.putInt(GameObjectType.Building.ordinal());
+    	writer.putInt(this.buildingID);
+    	writer.putInt(GameObjectType.NPC.ordinal());
+    	writer.putInt(this.npcID);
+    	writer.putInt(0);
+    	writer.putInt(0);
+    	writer.putInt(3);
+    	writer.putInt(1);
+    	
+    	writer.putInt(0);
+    
+    }
+}
diff --git a/src/engine/net/client/msg/HotzoneChangeMsg.java b/src/engine/net/client/msg/HotzoneChangeMsg.java
new file mode 100644
index 00000000..c39f218d
--- /dev/null
+++ b/src/engine/net/client/msg/HotzoneChangeMsg.java
@@ -0,0 +1,80 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+
+import engine.math.FastMath;
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+
+
+public class HotzoneChangeMsg extends ClientNetMsg {
+
+    private int zoneType;
+    private int zoneID;
+
+    /**
+     * This is the general purpose constructor.
+     */
+    public HotzoneChangeMsg(int zoneType, int zoneID) {
+        super(Protocol.ARCHOTZONECHANGE);
+        this.zoneType = zoneType;
+        this.zoneID = zoneID;
+    }
+
+    /**
+     * This constructor is used by NetMsgFactory. It attempts to deserialize the
+     * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+     * past the limit) then this constructor Throws that Exception to the
+     * caller.
+     */
+    public HotzoneChangeMsg(AbstractConnection origin, ByteBufferReader reader)
+             {
+        super(Protocol.ARCHOTZONECHANGE, origin, reader);
+    }
+
+    /**
+     * Serializes the subclass specific items to the supplied NetMsgWriter.
+     */
+    @Override
+    protected void _serialize(ByteBufferWriter writer) {
+        writer.putInt(this.zoneType);
+        writer.putInt(this.zoneID);
+        writer.putInt(FastMath.secondsUntilNextHour());
+    }
+
+    /**
+     * Deserializes the subclass specific items from the supplied NetMsgReader.
+     */
+    @Override
+    protected void _deserialize(ByteBufferReader reader)  {
+        this.zoneType = reader.getInt();
+        this.zoneID = reader.getInt();
+        reader.getInt();
+    }
+
+    public int getZoneType() {
+        return this.zoneType;
+    }
+
+    public int getZoneID() {
+        return this.zoneID;
+    }
+
+    public void setZoneType(int value) {
+        this.zoneType = value;
+    }
+
+    public void setZoneID(int value) {
+        this.zoneID = value;
+    }
+}
diff --git a/src/engine/net/client/msg/IgnoreListMsg.java b/src/engine/net/client/msg/IgnoreListMsg.java
new file mode 100644
index 00000000..c7f75a03
--- /dev/null
+++ b/src/engine/net/client/msg/IgnoreListMsg.java
@@ -0,0 +1,110 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+
+import java.util.ArrayList;
+
+public class IgnoreListMsg extends ClientNetMsg {
+
+    private final ArrayList<String> names = new ArrayList<>();
+    private int playerType;
+    private int playerID;
+    private String playerName;
+
+    /**
+     * This is the general purpose constructor.
+     */
+    public IgnoreListMsg() {
+        super(Protocol.ARCIGNORELISTUPDATE);
+    }
+
+    /**
+     * This constructor is used by NetMsgFactory. It attempts to deserialize the
+     * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+     * past the limit) then this constructor Throws that Exception to the
+     * caller.
+     */
+    public IgnoreListMsg(AbstractConnection origin, ByteBufferReader reader)  {
+        super(Protocol.ARCIGNORELISTUPDATE, origin, reader);
+    }
+
+    /**
+     * Serializes the subclass specific items to the supplied NetMsgWriter.
+     */
+    @Override
+    protected void _serialize(ByteBufferWriter writer) {
+        writer.putInt(this.names.size());
+        for (String name : this.names) {
+            writer.putString(name);
+        }
+        writer.putInt(this.playerType);
+        writer.putInt(this.playerID);
+        writer.putString(this.playerName);
+        writer.put((byte) 0);
+    }
+
+    /**
+     * Deserializes the subclass specific items from the supplied NetMsgReader.
+     */
+    @Override
+    protected void _deserialize(ByteBufferReader reader)  {
+        int size = reader.getInt();
+        for (int i = 0; i < size; i++) {
+            this.names.add(reader.getString());
+        }
+        this.playerType = reader.getInt();
+        this.playerID = reader.getInt();
+        this.playerName = reader.getString();
+        reader.get();
+    }
+
+    public ArrayList<String> getNames() {
+        return this.names;
+    }
+
+    public void addName(String value) {
+        this.names.add(value);
+    }
+
+    public void removeName(String value) {
+        this.names.remove(value);
+    }
+
+    public int getPlayerType() {
+        return this.playerType;
+    }
+
+    public void setPlayerType(int value) {
+        this.playerType = value;
+    }
+
+    public int getPlayerID() {
+        return this.playerID;
+    }
+
+    public void setPlayerID(int value) {
+        this.playerID = value;
+    }
+
+    public String getName() {
+        return this.playerName;
+    }
+
+    public void setName(String value) {
+        this.playerName = value;
+    }
+
+}
diff --git a/src/engine/net/client/msg/IgnoreMsg.java b/src/engine/net/client/msg/IgnoreMsg.java
new file mode 100644
index 00000000..c154df9c
--- /dev/null
+++ b/src/engine/net/client/msg/IgnoreMsg.java
@@ -0,0 +1,181 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+
+import engine.gameManager.ChatManager;
+import engine.gameManager.DbManager;
+import engine.gameManager.SessionManager;
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.ClientConnection;
+import engine.net.client.Protocol;
+import engine.objects.Account;
+import engine.objects.PlayerCharacter;
+
+
+public class IgnoreMsg extends ClientNetMsg {
+
+    private int unknown1;
+    private int unknown2;
+    private String nameToIgnore;
+
+    /**
+     * This is the general purpose constructor.
+     */
+    public IgnoreMsg() {
+        super(Protocol.IGNORE);
+    }
+
+    /**
+     * This constructor is used by NetMsgFactory. It attempts to deserialize the
+     * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+     * past the limit) then this constructor Throws that Exception to the
+     * caller.
+     */
+    public IgnoreMsg(AbstractConnection origin, ByteBufferReader reader)  {
+        super(Protocol.IGNORE, origin, reader);
+    }
+
+    /**
+     * Serializes the subclass specific items to the supplied NetMsgWriter.
+     */
+    @Override
+    protected void _serialize(ByteBufferWriter writer) {
+        writer.putInt(unknown1);
+        writer.putInt(unknown2);
+        writer.putUnicodeString(nameToIgnore);
+    }
+
+    /**
+     * Deserializes the subclass specific items from the supplied NetMsgReader.
+     */
+    @Override
+    protected void _deserialize(ByteBufferReader reader)  {
+        unknown1 = reader.getInt();
+        unknown2 = reader.getInt();
+        nameToIgnore = reader.getUnicodeString();
+    }
+
+    public void handleRequest(ClientConnection origin) {
+
+        PlayerCharacter pcSource = SessionManager.getPlayerCharacter(origin);
+
+        if (nameToIgnore.isEmpty()) { // list ignored players
+            String[] ignoredPlayers = pcSource.getIgnoredPlayerNames();
+            String crlf = "\r\n";
+            String out = "Ignored players (" + ignoredPlayers.length + "):";
+            for (String name : ignoredPlayers) {
+                out += crlf + name;
+            }
+            ChatManager.chatSystemInfo(pcSource, out);
+            return;
+        }
+
+        //FIX THIS, USE OUR CACHE!
+        PlayerCharacter pcToIgnore = PlayerCharacter.getByFirstName(nameToIgnore);
+
+        if (pcSource == null) {
+            return;
+        }
+        if (pcToIgnore == null || pcToIgnore.getAccount() == null) {
+            ChatManager.chatSystemError(pcSource, "Character name " + nameToIgnore + " does not exist and cannot be ignored.");
+            return;
+        }
+        if (pcToIgnore.getObjectUUID() == pcSource.getObjectUUID()) {
+            ChatManager.chatSystemError(pcSource, "Try as you might, you are unable to ignore yourself!");
+            return;
+        }
+        String fn = pcToIgnore.getFirstName();
+
+        Account ac = pcSource.getAccount();
+
+        if (ac == null)
+            return;
+
+        if (pcSource.isIgnoringPlayer(pcToIgnore)) {
+
+            if (ac != null) {
+                if (!DbManager.PlayerCharacterQueries.SET_IGNORE_LIST(ac.getObjectUUID(), pcToIgnore.getObjectUUID(), false, pcToIgnore.getFirstName())) {
+                    ChatManager.chatSystemError(pcSource, "Unable to update database ignore list.");
+                }
+            } else {
+                ChatManager.chatSystemError(pcSource, "Unable to update database ignore list.");
+            }
+
+            pcSource.removeIgnoredPlayer(pcToIgnore.getAccount());
+            ChatManager.chatSystemInfo(pcSource, "Character " + fn + " is no longer ignored.");
+        } else {
+            if (!PlayerCharacter.isIgnorable()) {
+                ChatManager.chatSystemError(pcSource, "This character cannot be ignored.");
+                return;
+            }
+            if (PlayerCharacter.isIgnoreListFull()) {
+                ChatManager.chatSystemError(pcSource, "Your ignore list is already full.");
+                return;
+            }
+
+            if (ac != null) {
+                if (!DbManager.PlayerCharacterQueries.SET_IGNORE_LIST(ac.getObjectUUID(), pcToIgnore.getObjectUUID(), true, pcToIgnore.getFirstName())) {
+                    ChatManager.chatSystemError(pcSource, "Unable to update database ignore list. This ignore will not persist past server down.");
+                }
+            } else {
+                ChatManager.chatSystemError(pcSource, "Unable to update database ignore list.");
+            }
+
+            pcSource.addIgnoredPlayer(pcToIgnore.getAccount(), pcToIgnore.getFirstName());
+            ChatManager.chatSystemInfo(pcSource, "Character " + fn + " is now being ignored.");
+        }
+    }
+
+    /**
+     * @return the unknown1
+     */
+    public int getUnknown1() {
+        return unknown1;
+    }
+
+    /**
+     * @param unknown1 the unknown1 to set
+     */
+    public void setUnknown1(int unknown1) {
+        this.unknown1 = unknown1;
+    }
+
+    /**
+     * @return the unknown2
+     */
+    public int getUnknown2() {
+        return unknown2;
+    }
+
+    /**
+     * @param unknown2 the unknown2 to set
+     */
+    public void setUnknown2(int unknown2) {
+        this.unknown2 = unknown2;
+    }
+
+    /**
+     * @return the nameToIgnore
+     */
+    public String getNameToIgnore() {
+        return nameToIgnore;
+    }
+
+    /**
+     * @param nameToIgnore the nameToIgnore to set
+     */
+    public void setNameToIgnore(String nameToIgnore) {
+        this.nameToIgnore = nameToIgnore;
+    }
+
+}
diff --git a/src/engine/net/client/msg/InvalidTradeRequestMsg.java b/src/engine/net/client/msg/InvalidTradeRequestMsg.java
new file mode 100644
index 00000000..ade68c9a
--- /dev/null
+++ b/src/engine/net/client/msg/InvalidTradeRequestMsg.java
@@ -0,0 +1,98 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+
+import engine.exception.SerializationException;
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.objects.AbstractGameObject;
+
+/**
+ * Invalid trade request.
+ * Attempt to trade with player who is already trading.
+ */
+
+public class InvalidTradeRequestMsg extends ClientNetMsg {
+
+	private int unknown01;
+	private int busyType;
+	private int busyID;
+	private int requesterType;
+	private int requesterID; 
+
+	/**
+	 * This is the general purpose constructor
+	 */
+	public InvalidTradeRequestMsg(int unknown01, AbstractGameObject busy, AbstractGameObject requester) {
+		super(Protocol.ARCREQUESTTRADEBUSY);
+		this.busyType = busy.getObjectType().ordinal();
+		this.busyID = busy.getObjectUUID();
+		this.requesterType = requester.getObjectType().ordinal();
+		this.requesterID = requester.getObjectUUID();
+		this.unknown01 = unknown01;
+	
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the ByteBuffer into a message. If a BufferUnderflow occurs (based on reading past the limit) then this constructor Throws that Exception to the caller.
+	 */
+	public InvalidTradeRequestMsg(AbstractConnection origin, ByteBufferReader reader)
+			 {
+		super(Protocol.ARCREQUESTTRADEBUSY, origin, reader);
+	}
+
+	/**
+	 * Deserializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)
+			 {
+		unknown01 = reader.getInt();
+		busyType = reader.getInt();
+		busyID = reader.getInt();
+		requesterType = reader.getInt();
+		requesterID = reader.getInt();
+	}
+
+	/**
+	 * Serializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer)
+			throws SerializationException {
+		writer.putInt(unknown01);
+		writer.putInt(busyType);
+		writer.putInt(busyID);
+		writer.putInt(requesterType);
+		writer.putInt(requesterID);
+	}
+
+	/**
+	 * @return the unknown01
+	 */
+	public int getUnknown01() {
+		return unknown01;
+	}
+
+	/**
+	 * @param unknown01 the unknown01 to set
+	 */
+	public void setUnknown01(int unknown01) {
+		this.unknown01 = unknown01;
+	}
+
+	public int getRequesterID() {
+		return requesterID;
+	}
+
+}
diff --git a/src/engine/net/client/msg/ItemEffectMsg.java b/src/engine/net/client/msg/ItemEffectMsg.java
new file mode 100644
index 00000000..c5348447
--- /dev/null
+++ b/src/engine/net/client/msg/ItemEffectMsg.java
@@ -0,0 +1,263 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.objects.AbstractWorldObject;
+
+public class ItemEffectMsg extends ClientNetMsg {
+
+	protected int numTrains;
+	protected int effectID;
+	protected int sourceType;
+	protected int sourceID;
+	protected int targetType;
+	protected int targetID;
+
+	protected int unknown02;
+	protected int unknown03;
+	protected int duration;
+	protected int unknown05;
+	protected byte unknown06;
+	protected int ItemType;
+	protected int powerTypeID;
+	protected String powerUsedName;
+	protected int itemID;
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public ItemEffectMsg() {
+		super(Protocol.POWERACTION);
+		this.numTrains = 0;
+		this.effectID = 0;
+		this.sourceType = 0;
+		this.sourceID = 0;
+		this.targetType = 0;
+		this.targetID = 0;
+
+		this.unknown02 = 0;
+		this.unknown03 = 0;
+		this.duration = 0;
+		this.unknown05 = 0;
+		this.unknown06 = (byte) 0;
+
+		this.ItemType = 0;
+		this.itemID = 0;
+		this.powerUsedName = "";
+	}
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public ItemEffectMsg(AbstractWorldObject source, AbstractWorldObject target, int numTrains, int effectID, int duration,
+			int powerUsedID, String powerUsedName) {
+		super(Protocol.POWERACTION);
+		this.numTrains = numTrains;
+		this.effectID = effectID;
+
+		if (source != null) {
+			this.sourceType = source.getObjectType().ordinal();
+			this.sourceID = source.getObjectUUID();
+		} else {
+			this.sourceType = 0;
+			this.sourceID = 0;
+		}
+
+		if (target != null) {
+			this.targetType = target.getObjectType().ordinal();
+			this.targetID = target.getObjectUUID();
+		} else {
+			this.targetType = 0;
+			this.targetID = 0;
+		}
+		this.unknown02 = 0; //0 = apply, 2= remove
+		this.unknown03 = 0;
+		this.duration = duration;
+		this.unknown05 = 0; //1 = remove item effect
+		this.unknown06 = (byte) 0; //1 = remove item effect
+		this.ItemType = powerUsedID;
+		this.itemID = 0;
+		this.powerUsedName = powerUsedName;
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public ItemEffectMsg(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.POWERACTION, origin, reader);
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+		writer.putInt(this.numTrains);
+		writer.putInt(this.effectID);
+
+		writer.putInt(this.sourceType);
+		writer.putInt(this.sourceID);
+		writer.putInt(this.targetType);
+		writer.putInt(this.targetID);
+
+		writer.putInt(this.unknown02);
+		writer.putInt(this.unknown03);
+		writer.putInt(this.duration);
+		writer.putInt(this.unknown05);
+		writer.put(this.unknown06);
+
+		writer.putInt(this.ItemType);
+		writer.putInt(this.itemID);
+
+		writer.putString(this.powerUsedName);
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		this.numTrains = reader.getInt();
+		this.effectID = reader.getInt();
+
+		this.sourceType = reader.getInt();
+		this.sourceID = reader.getInt();
+		this.targetType = reader.getInt();
+		this.targetID = reader.getInt();
+
+		this.unknown02 = reader.getInt();
+		this.unknown03 = reader.getInt();
+		this.duration = reader.getInt();
+		this.unknown05 = reader.getInt();
+		this.unknown06 = reader.get();
+
+		this.ItemType = reader.getInt();
+		this.itemID = reader.getInt();
+
+		this.powerUsedName = reader.getString();
+	}
+
+	public int getNumTrains() {
+		return this.numTrains;
+	}
+
+	public int getEffectID() {
+		return this.effectID;
+	}
+
+	public int getSourceType() {
+		return this.sourceType;
+	}
+
+	public int getSourceID() {
+		return this.sourceID;
+	}
+
+	public int getTargetType() {
+		return this.targetType;
+	}
+
+	public int getTargetID() {
+		return this.targetID;
+	}
+
+	public int getUnknown02() {
+		return this.unknown02;
+	}
+
+	public int getUnknown03() {
+		return this.unknown03;
+	}
+
+	public int getDuration() {
+		return this.duration;
+	}
+
+	public int getUnknown05() {
+		return this.unknown05;
+	}
+
+	public byte getUnknown06() {
+		return this.unknown06;
+	}
+
+	public int getItemType() {
+		return this.ItemType;
+	}
+	public int getItemID() {
+		return this.itemID;
+	}
+
+	public String getPowerUsedName() {
+		return this.powerUsedName;
+	}
+
+	public void setNumTrains(int value) {
+		this.numTrains = value;
+	}
+
+	public void setEffectID(int value) {
+		this.effectID = value;
+	}
+
+	public void setSourceType(int value) {
+		this.sourceType = value;
+	}
+
+	public void setSourceID(int value) {
+		this.sourceID = value;
+	}
+
+	public void setTargetType(int value) {
+		this.targetType = value;
+	}
+
+	public void setTargetID(int value) {
+		this.targetID = value;
+	}
+
+	public void setUnknown02(int value) {
+		this.unknown02 = value;
+	}
+
+	public void setUnknown03(int value) {
+		this.unknown03 = value;
+	}
+
+	public void setDuration(int value) {
+		this.duration = value;
+	}
+
+	public void setUnknown05(int value) {
+		this.unknown05 = value;
+	}
+
+	public void setUnknown06(byte value) {
+		this.unknown06 = value;
+	}
+
+	public void setItemType(int value) {
+		this.ItemType = value;
+	}
+	public void setItemID(int value){
+		this.itemID = value;
+	}
+
+	public void setPowerUsedName(String value) {
+		this.powerUsedName = value;
+	}
+}
diff --git a/src/engine/net/client/msg/ItemHealthUpdateMsg.java b/src/engine/net/client/msg/ItemHealthUpdateMsg.java
new file mode 100644
index 00000000..fc7a5443
--- /dev/null
+++ b/src/engine/net/client/msg/ItemHealthUpdateMsg.java
@@ -0,0 +1,76 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+
+
+public class ItemHealthUpdateMsg extends ClientNetMsg {
+
+	private int slot;
+	private float newDurability;
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public ItemHealthUpdateMsg(int slot, float newDurability) {
+		super(Protocol.ITEMHEALTHUPDATE);
+		this.slot = slot;
+		this.newDurability = newDurability;
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public ItemHealthUpdateMsg(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.ITEMHEALTHUPDATE, origin, reader);
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+		writer.putInt(this.slot);
+		writer.putFloat(this.newDurability);
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		this.slot = reader.getInt();
+		this.newDurability = reader.getFloat();
+	}
+
+	public int getSlot() {
+		return this.slot;
+	}
+
+	public void setSlot(int value) {
+		this.slot = value;
+	}
+
+	public float getNewDurability() {
+		return this.newDurability;
+	}
+
+	public void setNewDurability(float value) {
+		this.newDurability = value;
+	}
+}
diff --git a/src/engine/net/client/msg/ItemProductionMsg.java b/src/engine/net/client/msg/ItemProductionMsg.java
new file mode 100644
index 00000000..96d6570d
--- /dev/null
+++ b/src/engine/net/client/msg/ItemProductionMsg.java
@@ -0,0 +1,567 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+import engine.Enum.GameObjectType;
+import engine.gameManager.BuildingManager;
+import engine.gameManager.PowersManager;
+import engine.net.AbstractConnection;
+import engine.net.AbstractNetMsg;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.objects.Building;
+import engine.objects.Item;
+import engine.objects.MobLoot;
+import engine.objects.NPC;
+import engine.powers.EffectsBase;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+public class ItemProductionMsg extends ClientNetMsg {
+
+	private static final int ACTION_PRODUCE = 1;
+	private static final int ACTION_JUNK = 2;
+	private static final int ACTION_RECYCLE = 3;
+	private static final int ACTION_COMPLETE = 4;
+	private static final int ACTION_DEPOSIT = 6;
+	private static final int ACTION_SETPRICE = 5;
+	private static final int ACTION_TAKE = 7;
+	private static final int ACTION_CONFIRM_SETPRICE = 9;
+	private static final int ACTION_CONFIRM_DEPOSIT = 10;
+	private static final int ACTION_CONFIRM_TAKE = 11;     // Unsure. Sent by client
+	private static final int ACTION_CONFIRM_PRODUCE = 8;
+
+	private ArrayList<Long> ItemList;
+	private int size;
+	private int buildingUUID;
+	private int unknown01;
+	private int itemUUID;
+	private int itemType;
+	public void setItemUUID(int itemUUID) {
+		this.itemUUID = itemUUID;
+	}
+
+	private int totalProduction;
+	private int unknown03;
+	private int pToken;
+	private int sToken;
+	private String name;
+	private int actionType;
+	private int npcUUID;
+	private boolean add;
+	private int itemPrice;
+	private boolean isMultiple;
+
+	private HashMap<Integer,Integer> itemIDtoTypeMap;;
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+
+	public ItemProductionMsg() {
+		super(Protocol.ITEMPRODUCTION);
+        this.actionType = 0;
+        this.size = 0;
+		this.buildingUUID = 0;
+		this.unknown01 = 0;
+		this.itemUUID = 0;
+		this.totalProduction = 0;
+		this.unknown03 = 0;
+		this.pToken = 0;
+		this.sToken = 0;
+		this.name = "";
+		this.itemPrice = 0;
+		this.itemType = 0;
+
+	}
+
+	public ItemProductionMsg(Building building, NPC vendor, Item item, int actionType, boolean add) {
+		super(Protocol.ITEMPRODUCTION);
+		this.actionType = actionType;
+		this.size = 0;
+		this.buildingUUID = building.getObjectUUID();
+		this.npcUUID = vendor.getObjectUUID();
+		this.itemType = item.getObjectType().ordinal();
+		this.itemUUID = item.getObjectUUID();
+		this.unknown01 = 0;
+		this.totalProduction = 0;
+		this.unknown03 = 0;
+		this.pToken = 0;
+		this.sToken = 0;
+		this.name = "";
+		this.add = add;
+		this.itemPrice = item.getValue();
+
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public ItemProductionMsg(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.ITEMPRODUCTION, origin, reader);
+
+	}
+
+	/**
+	 * @see AbstractNetMsg#getPowerOfTwoBufferSize()
+	 */
+	@Override
+	protected int getPowerOfTwoBufferSize() {
+		//Larger size for historically larger opcodes
+		return (16); // 2^16 == 64k
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+		Building building = BuildingManager.getBuildingFromCache(this.buildingUUID);
+		if (building == null)
+			return;
+		// Common Header
+
+		writer.putInt(this.actionType);
+		writer.putInt(GameObjectType.Building.ordinal());
+		writer.putInt(this.buildingUUID);
+		writer.putInt(GameObjectType.NPC.ordinal());
+		writer.putInt(this.npcUUID);
+
+		switch (this.actionType) {
+
+		case ACTION_CONFIRM_DEPOSIT:
+			writer.putInt(0); // Not item ordinal?
+			writer.putInt(0); // Not item uuid?
+			writer.putInt(1);
+			writer.putInt(0);
+
+			if (!add) {
+				writer.put((byte) 1);
+				Item item = Item.getFromCache(this.itemUUID);
+				if (item != null)
+					Item.serializeForClientMsgWithoutSlot(item,writer);
+				writer.putInt(building.getStrongboxValue());
+				writer.putInt(0);
+				writer.putInt(0);
+				writer.put((byte) 0);
+				break;
+			}
+			writer.putInt(0);
+			writer.putInt(0);
+			writer.putInt(0);
+			writer.put((byte) 1);
+			Item item;
+			if (this.itemType == GameObjectType.Item.ordinal())
+				item = Item.getFromCache(this.itemUUID);
+			else
+				item = MobLoot.getFromCache(this.itemUUID);
+			if (item != null)
+				Item.serializeForClientMsgWithoutSlot(item,writer);
+			writer.putInt(building.getStrongboxValue());
+			writer.putInt(0);
+			writer.putInt(0);
+			writer.put((byte) 0);
+			break;
+		case ACTION_CONFIRM_TAKE:
+			writer.putInt(this.itemType);
+			writer.putInt(this.itemUUID);
+			writer.putInt(1);
+			writer.putInt(0);
+			writer.putInt(0);
+			writer.putInt(0);
+			writer.putInt(0);
+			writer.putInt(0);
+			writer.putInt(0);
+			writer.put((byte) 0);
+			break;
+		case ACTION_SETPRICE:
+			writer.putInt(this.itemType);
+			writer.putInt(this.itemUUID);
+			writer.putInt(1);
+			writer.putInt(0);
+			writer.putInt(0);
+			writer.putInt(0);
+			writer.putInt(0);
+			writer.putInt(this.itemPrice); // new price
+			writer.putInt(0);
+			writer.putInt(0);
+			writer.put((byte)0);
+			break;
+		case ACTION_CONFIRM_SETPRICE:
+			writer.putInt(this.itemType);
+			writer.putInt(this.itemUUID);
+			writer.putInt(1);
+			writer.putInt(0);
+			writer.putInt(0);
+			writer.putInt(0);
+			writer.putInt(0);
+			writer.put((byte)1);
+			writer.putInt(building.getStrongboxValue()); // new price
+			writer.putInt(0);
+			writer.putInt(0);
+			//writer.put((byte) 0);
+			break;
+		case ACTION_DEPOSIT:
+			writer.putInt(this.itemType);
+			writer.putInt(this.itemUUID);
+			writer.putInt(1);
+			writer.putInt(0);
+			writer.putInt(0);
+			writer.putInt(0);
+			writer.putInt(0);
+			writer.putInt(0);
+			writer.putInt(0);
+			writer.put((byte) 0);
+			break;
+		case ACTION_TAKE:
+		case ACTION_RECYCLE:
+
+			writer.putInt(0);
+			writer.putInt(0);
+			writer.putInt(1);
+			writer.putInt(0);
+			writer.putInt(0);
+			writer.putInt(0);
+			writer.putInt(0);
+			writer.put((byte) 1);
+			writer.putInt(building.getStrongboxValue());
+
+			if (this.itemIDtoTypeMap != null){
+				writer.putInt(this.itemIDtoTypeMap.size());
+
+				for (int itemID : this.itemIDtoTypeMap.keySet()) {
+					writer.putInt(this.itemIDtoTypeMap.get(itemID));
+					writer.putInt(itemID);
+				}
+
+			}else
+				writer.putInt(0);
+
+			writer.putInt(0);
+
+			break;
+		case ACTION_CONFIRM_PRODUCE:
+			writer.putInt(0);
+			writer.putInt(0);
+			writer.putInt(1);
+			MobLoot toRoll = MobLoot.getFromCache(this.itemUUID);
+			writer.putInt(-1497023830);
+			if (toRoll != null && toRoll.getPrefix() != null && !toRoll.getPrefix().isEmpty()){
+				EffectsBase eb = PowersManager.getEffectByIDString(toRoll.getPrefix());
+				if (eb == null)
+					this.pToken = 0;
+				else
+					this.pToken = eb.getToken();
+			}
+
+			if (toRoll != null && toRoll.getSuffix() != null && !toRoll.getSuffix().isEmpty()){
+				EffectsBase eb = PowersManager.getEffectByIDString(toRoll.getSuffix());
+				if (eb == null)
+					this.sToken = 0;
+				else
+					this.sToken = eb.getToken();
+			}
+			if (toRoll.isRandom() == false || (toRoll != null && toRoll.isComplete())){
+				writer.putInt(this.pToken);
+				writer.putInt(this.sToken);
+			}else{
+				writer.putInt(0);
+				writer.putInt(0);
+			}
+
+			writer.putString(toRoll.getCustomName());;
+			writer.putInt(GameObjectType.MobLoot.ordinal());
+			writer.putInt(this.itemUUID);
+			writer.putInt(0); //items left to produce?
+			if (toRoll != null){
+				writer.putInt(toRoll.getItemBaseID());
+				writer.putInt(toRoll.getValue());
+			}
+			else{
+				writer.putInt(0);
+				writer.putInt(0);
+			}
+
+			NPC vendor = NPC.getFromCache(this.npcUUID);
+			if (vendor != null){
+				if (toRoll.isComplete()){
+					writer.putInt(0);
+					writer.putInt(0);
+				}else{
+					long timeLeft = toRoll.getDateToUpgrade() - System.currentTimeMillis();
+
+					timeLeft /=1000;
+					writer.putInt((int)timeLeft);
+					writer.putInt(vendor.getRollingTimeInSeconds(toRoll.getItemBaseID()));
+				}
+
+			}else{
+				writer.putInt(0);
+				writer.putInt(0);
+			}
+			if (toRoll.isComplete())
+				writer.putInt(0);
+			else
+				writer.putInt(1);
+			writer.put((byte)0);
+
+			if (toRoll != null && toRoll.isComplete())
+				writer.put((byte)1);
+			else
+				writer.put((byte)0);
+			writer.put((byte)0);
+			writer.put((byte)1);
+			writer.putInt(vendor.getBuilding().getStrongboxValue());
+
+			writer.putInt(0);
+			writer.putInt(0);
+			//writer.putInt(0); //error popup
+
+			break;
+		case ACTION_COMPLETE:
+			writer.putInt(this.itemType);
+			writer.putInt(this.itemUUID);
+			writer.putInt(this.totalProduction);
+			writer.putInt(this.unknown03);
+			writer.putInt(this.pToken);
+			writer.putInt(this.sToken);
+			writer.putInt(0);
+			writer.putInt(0);
+			writer.putInt(0);
+			writer.put((byte)0);
+			break;
+		case ACTION_JUNK:
+			writer.putInt(this.itemType);
+			writer.putInt(this.itemUUID);
+			writer.putInt(this.totalProduction);
+			writer.putInt(this.unknown03);
+			writer.putInt(this.pToken);
+			writer.putInt(this.sToken);
+			writer.putString(this.name);
+			writer.putInt(0);
+			writer.putInt(0);
+			writer.putShort((short)0);
+			writer.put((byte)0);
+			break;
+		default:
+			writer.putInt(0);
+			writer.putInt(1);
+			writer.putInt(0);
+			writer.putInt(0);
+			break;
+		}
+
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+
+		// Common header
+
+		this.actionType = reader.getInt();
+		reader.getInt(); // Building type padding
+		this.buildingUUID = reader.getInt();
+		reader.getInt(); // NPC type padding
+		this.npcUUID = reader.getInt();
+
+		switch (this.actionType) {
+		case ACTION_SETPRICE:
+			this.itemType = reader.getInt();
+			this.itemUUID = reader.getInt();
+			reader.getInt();
+			reader.getInt();
+			reader.getInt();
+			reader.getInt();
+			reader.getInt();
+			this.itemPrice = reader.getInt();
+			reader.getInt();
+			reader.getInt();
+			reader.get();
+			break;
+		case ACTION_RECYCLE:
+		case ACTION_TAKE:
+			reader.getInt();
+			reader.getInt();
+			reader.getInt();
+			reader.getInt();
+			reader.getInt();
+			reader.getInt();
+			reader.getInt();
+			reader.get();
+			this.size = reader.getInt();
+			HashMap<Integer,Integer> tempIDs = new HashMap<>();
+			for (int i = 0; i < this.size; i++) {
+				int type = reader.getInt(); // Item type padding
+				this.itemUUID = reader.getInt();
+				tempIDs.put(this.itemUUID, type);
+			}
+			reader.getInt();
+			this.itemIDtoTypeMap = tempIDs;
+			break;
+		case ACTION_DEPOSIT:
+			this.itemType = reader.getInt();
+			this.itemUUID = reader.getInt();
+			reader.getInt();
+			reader.getInt();
+			reader.getInt();
+			reader.getInt();
+			reader.getInt();
+			reader.getInt();
+			reader.getInt();
+			reader.get();
+			break;
+		case ACTION_JUNK:
+			this.itemType = reader.getInt();
+			this.itemUUID = reader.getInt();
+			this.totalProduction =reader.getInt();
+			this.unknown03 = reader.getInt();
+			this.pToken = reader.getInt();
+			this.sToken = reader.getInt();
+			this.name = reader.getString();
+			reader.getInt();
+			reader.getInt();
+			reader.get();
+			break;
+		default:
+			this.itemType = reader.getInt();
+			this.itemUUID = reader.getInt();
+			this.totalProduction =reader.getInt();
+			this.unknown03 = reader.getInt();
+			this.pToken = reader.getInt();
+			this.sToken = reader.getInt();
+			this.name = reader.getString();
+			this.isMultiple = reader.getInt() != 0 ? true:false;
+			reader.getInt();
+
+			if (this.actionType == ACTION_COMPLETE || this.actionType == ACTION_JUNK)
+				reader.get();
+			else
+				reader.getShort();
+			break;
+
+		}
+	}
+
+	public int getItemType() {
+		return itemType;
+	}
+
+	// TODO fix ArrayList Accessability.
+	public ArrayList<Long> getItemList() {
+		return this.ItemList;
+	}
+
+	public void addItem(Long item) {
+		this.ItemList.add(item);
+	}
+
+	public int getUnknown01() {
+		return unknown01;
+	}
+
+	public int getTotalProduction() {
+		return totalProduction;
+	}
+
+	public int getUnknown03() {
+		return unknown03;
+	}
+
+	public void setUnknown01(int unknown01) {
+		this.unknown01 = unknown01;
+	}
+
+	public void setTotalProduction(int unknown02) {
+		this.totalProduction = unknown02;
+	}
+
+	public void setUnknown03(int unknown03) {
+		this.unknown03 = unknown03;
+	}
+
+	public void setItemList(ArrayList<Long> value) {
+		this.ItemList = value;
+	}
+
+	public int getItemUUID() {
+		return itemUUID;
+	}
+
+	public void setpToken(int pToken) {
+		this.pToken = pToken;
+	}
+
+	public int getpToken() {
+		return pToken;
+	}
+
+	public void setsToken(int sToken) {
+		this.sToken = sToken;
+	}
+
+	public int getsToken() {
+		return sToken;
+	}
+
+	public void setName(String name) {
+		this.name = name;
+	}
+
+	public String getName() {
+		return name;
+	}
+
+	public int getSize() {
+		return size;
+	}
+
+	public void setSize(int size) {
+		this.size = size;
+	}
+
+	public int getActionType() {
+		return actionType;
+	}
+
+	public final void setActionType(int actionType) {
+		this.actionType = actionType;
+	}
+
+	public int getNpcUUID() {
+		return npcUUID;
+	}
+
+	public HashMap<Integer,Integer> getItemIDtoTypeMap() {
+		return itemIDtoTypeMap;
+	}
+
+	/**
+	 * @return the itemPrice
+	 */
+	public int getItemPrice() {
+		return itemPrice;
+	}
+
+	public boolean isMultiple() {
+		return isMultiple;
+	}
+
+	public void setMultiple(boolean isMultiple) {
+		this.isMultiple = isMultiple;
+	}
+}
diff --git a/src/engine/net/client/msg/KeepAliveServerClientMsg.java b/src/engine/net/client/msg/KeepAliveServerClientMsg.java
new file mode 100644
index 00000000..17c35351
--- /dev/null
+++ b/src/engine/net/client/msg/KeepAliveServerClientMsg.java
@@ -0,0 +1,86 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+
+
+public class KeepAliveServerClientMsg extends ClientNetMsg {
+
+	private int firstData; // First 4 bytes
+	private int secondData; // Second 4 bytes
+	
+	private double timeLoggedIn;
+	private byte endData; // Last byte
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public KeepAliveServerClientMsg(int firstData, int secondData, byte endData) {
+		super(Protocol.KEEPALIVESERVERCLIENT);
+		this.firstData = firstData;
+		this.secondData = secondData;
+		this.endData = endData;
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public KeepAliveServerClientMsg(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.KEEPALIVESERVERCLIENT, origin, reader);
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+		writer.putDouble(this.timeLoggedIn);
+		writer.put((byte)0);
+		//writer.putDouble(1000);
+		
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+	this.timeLoggedIn = reader.getDouble();
+		this.endData = reader.get();
+	}
+
+	public int getFirstData() {
+		return firstData;
+	}
+
+	public int getSecondData() {
+		return secondData;
+	}
+
+	public byte getEndData() {
+		return endData;
+	}
+
+	public double getTimeLoggedIn() {
+		return timeLoggedIn;
+	}
+
+	public void setTimeLoggedIn(double timeLoggedIn) {
+		this.timeLoggedIn = timeLoggedIn;
+	}
+}
diff --git a/src/engine/net/client/msg/LeaderboardMessage.java b/src/engine/net/client/msg/LeaderboardMessage.java
new file mode 100644
index 00000000..7dc992a0
--- /dev/null
+++ b/src/engine/net/client/msg/LeaderboardMessage.java
@@ -0,0 +1,103 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+import engine.Enum.ShrineType;
+import engine.exception.SerializationException;
+import engine.gameManager.BuildingManager;
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.objects.Building;
+import engine.objects.Guild;
+import engine.objects.GuildTag;
+import engine.objects.Shrine;
+
+public class LeaderboardMessage extends ClientNetMsg {
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public LeaderboardMessage(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.LEADERBOARD, origin, reader);
+	}
+
+	public LeaderboardMessage() {
+		super(Protocol.LEADERBOARD);
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) throws SerializationException {
+
+		writer.putInt(ShrineType.values().length);//??
+
+		for (ShrineType shrineType : ShrineType.values()) {
+			writer.putInt(shrineType.ordinal());
+			writer.putInt(shrineType.getShrinesCopy().size());
+			int i = 0;
+			for (Shrine shrine : shrineType.getShrinesCopy()) {
+				i++;
+				writer.putInt(shrine.getFavors());
+				Building shrineBuilding = BuildingManager.getBuilding(shrine.getBuildingID());
+				if (shrineBuilding != null) {
+					Guild shrineGuild = shrineBuilding.getGuild();
+					if (shrineGuild != null) {
+						writer.putInt(shrineGuild.getObjectType().ordinal());
+						writer.putInt(shrineGuild.getObjectUUID());
+
+						GuildTag._serializeForDisplay(shrineGuild.getGuildTag(),writer);
+						writer.putString(shrineGuild.getName());
+					} else {
+						writer.putLong(0);
+						writer.putInt(16);
+						writer.putInt(16);
+						writer.putInt(16);
+						writer.putInt(0);
+						writer.putInt(0);
+						writer.putString("");
+					}
+				}else{
+					writer.putInt(0);
+					writer.putInt(0);
+					writer.putInt(0);
+					writer.putInt(0);
+					writer.putInt(0);
+					writer.putInt(0);
+					writer.putInt(0);
+					writer.putInt(0);
+					writer.putInt(0);
+
+				}
+			}
+			writer.putString(shrineType.name());
+		}
+
+	}
+	@Override
+	protected int getPowerOfTwoBufferSize() {
+		return (14); // 2^14 == 16384
+	}
+
+}
diff --git a/src/engine/net/client/msg/LeaveWorldMsg.java b/src/engine/net/client/msg/LeaveWorldMsg.java
new file mode 100644
index 00000000..62ab8bbc
--- /dev/null
+++ b/src/engine/net/client/msg/LeaveWorldMsg.java
@@ -0,0 +1,52 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+
+
+public class LeaveWorldMsg extends ClientNetMsg {
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public LeaveWorldMsg() {
+		super(Protocol.LEAVEREQUEST);
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public LeaveWorldMsg(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.LEAVEREQUEST, origin, reader);
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+	}
+
+}
diff --git a/src/engine/net/client/msg/LoadCharacterMsg.java b/src/engine/net/client/msg/LoadCharacterMsg.java
new file mode 100644
index 00000000..0471da38
--- /dev/null
+++ b/src/engine/net/client/msg/LoadCharacterMsg.java
@@ -0,0 +1,169 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+import engine.Enum.GameObjectType;
+import engine.exception.SerializationException;
+import engine.net.AbstractConnection;
+import engine.net.AbstractNetMsg;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.objects.*;
+
+public class LoadCharacterMsg extends ClientNetMsg {
+
+	private AbstractCharacter absChar;
+	private Corpse corpse;
+	private boolean hideNonAscii;
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public LoadCharacterMsg(AbstractCharacter ch, boolean laln) {
+		super(Protocol.LOADCHARACTER);
+		this.absChar = ch;
+		this.corpse = null;
+		this.hideNonAscii = laln;
+	}
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public LoadCharacterMsg(Corpse corpse, boolean laln) {
+		super(Protocol.LOADCHARACTER);
+		this.corpse = corpse;
+		this.absChar = null;
+		this.hideNonAscii = laln;
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public LoadCharacterMsg(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.LOADCHARACTER, origin, reader);
+	}
+
+	/**
+	 * @see AbstractNetMsg#getPowerOfTwoBufferSize()
+	 */
+	@Override
+	protected int getPowerOfTwoBufferSize() {
+		//Larger size for historically larger opcodes
+		return (17); // 2^17 == 131,072
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) throws SerializationException {
+
+		if (absChar != null && absChar.getObjectType() == GameObjectType.NPC) {
+			NPC npc = (NPC)absChar;
+
+
+
+			if (npc.getBuilding() != null){
+				writer.putInt(npc.getBuildingLevel());
+				writer.putInt(npc.getBuildingFloor());
+			}else{
+				writer.putInt(-1);
+				writer.putInt(-1);
+			}
+
+
+		} else if (absChar != null) {
+		
+		if (absChar.getObjectType().equals(GameObjectType.PlayerCharacter)){
+			Regions region = absChar.getRegion();
+			
+			if (region == null){
+				writer.putInt(-1);
+				writer.putInt(-1);
+			}else{
+				Building regionBuilding = Regions.GetBuildingForRegion(region);
+				if (regionBuilding == null){
+					writer.putInt(-1);
+					writer.putInt(-1);
+				}else{
+					writer.putInt(region.getLevel());
+					writer.putInt(region.getRoom());
+				}
+			}
+			//TODO below is Mob Region Serialization, not implemented. default to -1, which is ground.
+		}else{
+			writer.putInt(-1);
+			writer.putInt(-1);
+		}
+			
+			
+			
+
+		} else if (corpse != null){
+			writer.putInt(-1);
+			writer.putInt(-1);
+		}
+		if (corpse != null) {
+			writer.putInt(Float.floatToIntBits(corpse.getLoc().getX()));
+			writer.putInt(Float.floatToIntBits(corpse.getLoc().getY()));
+			writer.putInt(Float.floatToIntBits(corpse.getLoc().getZ()));
+			writer.put((byte) 0);
+		} else if (absChar != null) {
+
+			writer.putFloat(absChar.getLoc().getX());
+			writer.putFloat(absChar.getLoc().getY());
+			writer.putFloat(absChar.getLoc().getZ());
+
+			if (absChar.isMoving()) {
+				writer.put((byte) 1);
+				writer.putFloat(absChar.getEndLoc().x);
+				writer.putFloat(absChar.getEndLoc().y);
+				writer.putFloat(absChar.getEndLoc().z);
+			} else
+				writer.put((byte) 0);
+		} else {
+			writer.put((byte) 0);
+		}
+
+		if (corpse != null)
+			Corpse._serializeForClientMsg(corpse, writer, this.hideNonAscii);
+		else if (absChar != null)
+			AbstractCharacter.serializeForClientMsgOtherPlayer(this.absChar,writer, this.hideNonAscii);
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		int unknown1 = reader.getInt();
+		int unknown2 = reader.getInt();
+		int unknown3 = reader.getInt();
+		int unknown4 = reader.getInt();
+		int unknown5 = reader.getInt();
+		// TODO finish deserialization impl
+	}
+
+	public AbstractCharacter getChar() {
+		return this.absChar;
+	}
+
+	public void setChar(AbstractCharacter value) {
+		this.absChar = value;
+	}
+
+	public void setCorpse(Corpse value) {
+		this.corpse = value;
+	}
+}
diff --git a/src/engine/net/client/msg/LoadStructureMsg.java b/src/engine/net/client/msg/LoadStructureMsg.java
new file mode 100644
index 00000000..6584ef83
--- /dev/null
+++ b/src/engine/net/client/msg/LoadStructureMsg.java
@@ -0,0 +1,104 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+
+import engine.net.AbstractConnection;
+import engine.net.AbstractNetMsg;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.objects.Building;
+
+import java.util.ArrayList;
+
+
+public class LoadStructureMsg extends ClientNetMsg {
+
+	private ArrayList<Building> structureList;
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public LoadStructureMsg() {
+		this(new ArrayList<>());
+	}
+
+	/**
+	 * This is the general purpose constructor.
+	 *
+	 * @param structures
+	 */
+	public LoadStructureMsg(ArrayList<Building> structures) {
+		super(Protocol.LOADSTRUCTURE);
+		this.structureList = structures;
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public LoadStructureMsg(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.LOADSTRUCTURE, origin, reader);
+	}
+
+	/**
+	 * @see AbstractNetMsg#getPowerOfTwoBufferSize()
+	 */
+	@Override
+	protected int getPowerOfTwoBufferSize() {
+		//Larger size for historically larger opcodes
+		return (18); // 2^16 == 64k
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+		for (int i = 0; i < 4; i++)
+			writer.putInt(0);
+
+		writer.putInt(this.structureList.size());
+		
+		for (Building building:this.structureList){
+			Building._serializeForClientMsg(building, writer);
+		}
+		writer.putInt(0);
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		reader.getInt();
+		reader.getInt();
+		reader.getInt();
+		reader.getInt();
+		int size = reader.getInt();
+		// TODO finish Deserialize impl
+	}
+
+	// TODO fix ArrayList Accessability.
+	public ArrayList<Building> getStructureList() {
+		return this.structureList;
+	}
+
+	public void addObject(Building obj) {
+		this.structureList.add(obj);
+	}
+
+	public int size() {
+		return this.structureList.size();
+	}
+}
diff --git a/src/engine/net/client/msg/LockUnlockDoorMsg.java b/src/engine/net/client/msg/LockUnlockDoorMsg.java
new file mode 100644
index 00000000..b99f5740
--- /dev/null
+++ b/src/engine/net/client/msg/LockUnlockDoorMsg.java
@@ -0,0 +1,113 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+
+
+public class LockUnlockDoorMsg extends ClientNetMsg {
+
+	private int doorID;
+	private long targetID;
+	private int unk1;
+	private int unk2;
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public LockUnlockDoorMsg() {
+		super(Protocol.LOCKUNLOCKDOOR);
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public LockUnlockDoorMsg(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.LOCKUNLOCKDOOR, origin, reader);
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+		writer.putInt(0);
+		writer.putInt(doorID);
+		writer.putLong(targetID);
+		writer.putInt(unk1);
+		writer.putInt(unk2);
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		reader.getInt();
+		this.doorID = reader.getInt();
+		this.targetID = reader.getLong();
+		this.unk1 = reader.getInt();
+		this.unk2 = reader.getInt();
+	}
+
+	/**
+	 * @return the unknown1
+	 */
+	public int getDoorID() {
+		return doorID;
+	}
+
+	/**
+	 * @param unknown1
+	 *            the unknown1 to set
+	 */
+	public void setDoorID(int doorID) {
+		this.doorID = doorID;
+	}
+
+	/**
+	 * @return the targetID
+	 */
+	public long getTargetID() {
+		return targetID;
+	}
+
+	/**
+	 * @param targetID
+	 *            the targetID to set
+	 */
+	public void setTargetID(long targetID) {
+		this.targetID = targetID;
+	}
+
+	public int getUnk1() {
+		return this.unk1;
+	}
+
+	public void setUnk1(int value) {
+		this.unk1 = value;
+	}
+
+	public int getUnk2() {
+		return this.unk2;
+	}
+
+	public void setUnk2(int value) {
+		this.unk2 = value;
+	}
+
+}
diff --git a/src/engine/net/client/msg/LoginToGameServerMsg.java b/src/engine/net/client/msg/LoginToGameServerMsg.java
new file mode 100644
index 00000000..a25f8fc3
--- /dev/null
+++ b/src/engine/net/client/msg/LoginToGameServerMsg.java
@@ -0,0 +1,106 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+
+public class LoginToGameServerMsg extends ClientNetMsg {
+
+	private String secKey;
+	private int Unknown01;
+	private int Unknown02;
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public LoginToGameServerMsg() {
+		super(Protocol.LOGINTOGAMESERVER);
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public LoginToGameServerMsg(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.LOGINTOGAMESERVER, origin, reader);
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+		writer.putHexString(this.secKey);
+		writer.putInt(this.Unknown01);
+		writer.putInt(this.Unknown02);
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		this.secKey = reader.getHexString();
+
+		this.Unknown01 = reader.monitorInt(1065353216, "ValidateGameServerMsg 01");
+		this.Unknown02 = reader.monitorInt(1065353216, "ValidateGameServerMsg 02");
+	}
+
+	/**
+	 * @return the secKey
+	 */
+	public String getSecKey() {
+		return secKey;
+	}
+
+	/**
+	 * @param secKey
+	 *            the secKey to set
+	 */
+	public void setSecKey(String secKey) {
+		this.secKey = secKey;
+	}
+
+	/**
+	 * @return the unknown01
+	 */
+	public int getUnknown01() {
+		return Unknown01;
+	}
+
+	/**
+	 * @param unknown01
+	 *            the unknown01 to set
+	 */
+	public void setUnknown01(int unknown01) {
+		Unknown01 = unknown01;
+	}
+
+	/**
+	 * @return the unknown02
+	 */
+	public int getUnknown02() {
+		return Unknown02;
+	}
+
+	/**
+	 * @param unknown02
+	 *            the unknown02 to set
+	 */
+	public void setUnknown02(int unknown02) {
+		Unknown02 = unknown02;
+	}
+}
diff --git a/src/engine/net/client/msg/LootMsg.java b/src/engine/net/client/msg/LootMsg.java
new file mode 100644
index 00000000..ec81e181
--- /dev/null
+++ b/src/engine/net/client/msg/LootMsg.java
@@ -0,0 +1,267 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+
+import engine.exception.SerializationException;
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.objects.Item;
+import engine.objects.MobEquipment;
+
+public class LootMsg extends ClientNetMsg {
+
+	private Item item;
+	private int sourceType1;
+	private int sourceID1;
+	private int targetType;
+	private int targetID;
+	private int sourceType2;
+	private int sourceID2;
+
+	private int unknown01;
+	private int unknown02;
+	private int unknown03;
+	private int unknown04;
+	private int unknown05;
+	private byte unknown06 = (byte) 0;
+	private int unknown07;
+	private int unknown08;
+
+	private MobEquipment mobEquipment = null;
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public LootMsg(int sourceType, int sourceID, int targetType, int targetID, Item item) {
+		super(Protocol.MOVEOBJECTTOCONTAINER);
+		this.sourceType1 = sourceType;
+		this.sourceID1 = sourceID;
+		this.targetType = targetType;
+		this.targetID = targetID;
+		this.sourceType2 = sourceType;
+		this.sourceID2 = sourceID;
+		this.item = item;
+		this.unknown01 = 0;
+		this.unknown02 = 0;
+		this.unknown03 = 0;
+		this.unknown04 = 0;
+		this.unknown05 = 0;
+		this.unknown07 = 0;
+		this.unknown08 = 0;
+	}
+
+	//for MobEquipment
+
+	public LootMsg(int sourceType, int sourceID, int targetType, int targetID, MobEquipment mobEquipment) {
+		super(Protocol.MOVEOBJECTTOCONTAINER);
+		this.sourceType1 = sourceType;
+		this.sourceID1 = sourceID;
+		this.targetType = targetType;
+		this.targetID = targetID;
+		this.sourceType2 = sourceType;
+		this.sourceID2 = sourceID;
+		this.item = null;
+		this.mobEquipment = mobEquipment;
+
+		this.unknown01 = 0;
+		this.unknown02 = 0;
+		this.unknown03 = 0;
+		this.unknown04 = 0;
+		this.unknown05 = 0;
+		this.unknown07 = 0;
+		this.unknown08 = 0;
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public LootMsg(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.MOVEOBJECTTOCONTAINER, origin, reader);
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+
+		if (this.item != null)
+			Item.serializeForClientMsgWithoutSlot(this.item,writer);
+		else if (this.mobEquipment != null)
+			try {
+				MobEquipment._serializeForClientMsg(this.mobEquipment,writer, false);
+			} catch (SerializationException e) {
+				// TODO Auto-generated catch block
+				e.printStackTrace();
+			}
+		writer.putInt(this.sourceType1);
+		writer.putInt(this.sourceID1);
+		writer.putInt(this.targetType);
+		writer.putInt(this.targetID);
+		writer.putInt(this.sourceType2);
+		writer.putInt(this.sourceID2);
+		writer.putInt(this.unknown01);
+		writer.putInt(this.unknown02);
+		writer.putInt(this.unknown03);
+		writer.putInt(this.unknown04);
+		writer.putInt(this.unknown05);
+		writer.put(this.unknown06);
+		writer.putInt(this.unknown07);
+		writer.putInt(this.unknown08);
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		this.item = Item.deserializeFromClientMsg(reader, false);
+		this.sourceType1 = reader.getInt();
+		this.sourceID1 = reader.getInt();
+		this.targetType = reader.getInt();
+		this.targetID = reader.getInt();
+		this.sourceType2 = reader.getInt();
+		this.sourceID2 = reader.getInt();
+		this.unknown01 = reader.getInt();
+		this.unknown02 = reader.getInt();
+		this.unknown03 = reader.getInt();
+		this.unknown04 = reader.getInt();
+		this.unknown05 = reader.getInt();
+		this.unknown06 = reader.get();
+		this.unknown07 = reader.getInt();
+		this.unknown08 = reader.getInt();
+	}
+
+	public int getSourceType1() {
+		return this.sourceType1;
+	}
+
+	public int getSourceID1() {
+		return this.sourceID1;
+	}
+
+	public int getTargetType() {
+		return this.targetType;
+	}
+
+	public int getTargetID() {
+		return this.targetID;
+	}
+
+	public int getSourceType2() {
+		return this.sourceType2;
+	}
+
+	public int getSourceID2() {
+		return this.sourceID2;
+	}
+
+	public int getUnknown01() {
+		return this.unknown01;
+	}
+
+	public int getUnknown02() {
+		return this.unknown02;
+	}
+
+	public int getUnknown03() {
+		return this.unknown03;
+	}
+
+	public int getUnknown04() {
+		return this.unknown04;
+	}
+
+	public int getUnknown05() {
+		return this.unknown05;
+	}
+
+	public byte getUnknown06() {
+		return this.unknown06;
+	}
+
+	public int getUnknown07() {
+		return this.unknown07;
+	}
+
+	public int getUnknown08() {
+		return this.unknown08;
+	}
+
+	public Item getItem() {
+		return this.item;
+	}
+
+	public void setUnknown01(int value) {
+		this.unknown01 = value;
+	}
+
+	public void setUnknown02(int value) {
+		this.unknown02 = value;
+	}
+
+	public void setUnknown03(int value) {
+		this.unknown03 = value;
+	}
+
+	public void setUnknown04(int value) {
+		this.unknown04 = value;
+	}
+
+	public void setUnknown05(int value) {
+		this.unknown05 = value;
+	}
+
+	public void setUnknown06(byte value) {
+		this.unknown06 = value;
+	}
+
+	public void setUnknown07(int value) {
+		this.unknown07 = value;
+	}
+
+	public void setUnknown08(int value) {
+		this.unknown08 = value;
+	}
+
+	public void setSourceType1(int value) {
+		this.sourceType1 = value;
+	}
+
+	public void setSourceID1(int value) {
+		this.sourceID1 = value;
+	}
+
+	public void setTargetType(int value) {
+		this.targetType = value;
+	}
+
+	public void setTargetID(int value) {
+		this.targetID = value;
+	}
+
+	public void setSourceType2(int value) {
+		this.sourceType2 = value;
+	}
+
+	public void setSourceID2(int value) {
+		this.sourceID2 = value;
+	}
+
+	public void setItem(Item value) {
+		this.item = value;
+	}
+}
diff --git a/src/engine/net/client/msg/LootWindowRequestMsg.java b/src/engine/net/client/msg/LootWindowRequestMsg.java
new file mode 100644
index 00000000..5f92be1e
--- /dev/null
+++ b/src/engine/net/client/msg/LootWindowRequestMsg.java
@@ -0,0 +1,112 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+
+public class LootWindowRequestMsg extends ClientNetMsg {
+
+	private int sourceType;
+	private int sourceID;
+	private int targetType;
+	private int targetID;
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public LootWindowRequestMsg(int sourceType, int sourceID, int targetType, int targetID) {
+		super(Protocol.REQUESTCONTENTS);
+		this.sourceType = sourceType;
+		this.sourceID = sourceID;
+		this.targetType = targetType;
+		this.targetID = targetID;
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public LootWindowRequestMsg(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.REQUESTCONTENTS, origin, reader);
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+		writer.putInt(this.sourceType);
+		writer.putInt(this.sourceID);
+		writer.putInt(this.targetType);
+		writer.putInt(this.targetID);
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		this.sourceType = reader.getInt();
+		this.sourceID = reader.getInt();
+		this.targetType = reader.getInt();
+		this.targetID = reader.getInt();
+	}
+
+	/**
+	 * @return the sourceType
+	 */
+	public int getSourceType() {
+		return sourceType;
+	}
+
+	/**
+	 * @return the sourceID
+	 */
+	public int getSourceID() {
+		return sourceID;
+	}
+
+	/**
+	 * @return the targetType
+	 */
+	public int getTargetType() {
+		return targetType;
+	}
+
+	/**
+	 * @return the targetID
+	 */
+	public int getTargetID() {
+		return targetID;
+	}
+
+	public void setSourceType(int value) {
+		this.sourceType = value;
+	}
+
+	public void setSourceID(int value) {
+		this.sourceID = value;
+	}
+
+	public void setTargetType(int value) {
+		this.targetType = value;
+	}
+
+	public void setTargetID(int value) {
+		this.targetID = value;
+	}
+
+}
diff --git a/src/engine/net/client/msg/LootWindowResponseMsg.java b/src/engine/net/client/msg/LootWindowResponseMsg.java
new file mode 100644
index 00000000..408ae77c
--- /dev/null
+++ b/src/engine/net/client/msg/LootWindowResponseMsg.java
@@ -0,0 +1,105 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.objects.Item;
+
+import java.util.ArrayList;
+
+
+public class LootWindowResponseMsg extends ClientNetMsg {
+
+	private int targetType;
+	private int targetID;
+	private ArrayList<Item> inventory;
+	private int unknown01 = 45;
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public LootWindowResponseMsg(int targetType, int targetID, ArrayList<Item> inventory) {
+		super(Protocol.WEIGHTINVENTORY);
+		this.targetType = targetType;
+		this.targetID = targetID;
+		this.inventory = inventory;
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public LootWindowResponseMsg(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.WEIGHTINVENTORY, origin, reader);
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+		writer.put((byte) 1);
+		Item.putList(writer, this.inventory, false, this.targetID);
+		writer.putInt(this.unknown01);
+		writer.putInt(this.targetType);
+		writer.putInt(this.targetID);
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+	}
+
+	public int getUnknown01() {
+		return this.unknown01;
+	}
+
+	public int getTargetType() {
+		return targetType;
+	}
+
+	public int getTargetID() {
+		return targetID;
+	}
+
+	public ArrayList<Item> getInventory() {
+		return this.inventory;
+	}
+
+	public void setUnknown01(int value) {
+		this.unknown01 = value;
+	}
+
+	public void setTargetType(int value) {
+		this.targetType = value;
+	}
+
+	public void setTargetID(int value) {
+		this.targetID = value;
+	}
+
+	public void setInventory(ArrayList<Item> value) {
+		this.inventory = value;
+	}
+
+	@Override
+	protected int getPowerOfTwoBufferSize() {
+		// Larger size for historically larger opcodes
+		return 17; // 2^15 == 32,768
+	}
+}
diff --git a/src/engine/net/client/msg/ManageCityAssetsMsg.java b/src/engine/net/client/msg/ManageCityAssetsMsg.java
new file mode 100644
index 00000000..85ce6dae
--- /dev/null
+++ b/src/engine/net/client/msg/ManageCityAssetsMsg.java
@@ -0,0 +1,866 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+import engine.Enum.*;
+import engine.gameManager.BuildingManager;
+import engine.gameManager.ZoneManager;
+import engine.math.Vector3fImmutable;
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.objects.*;
+import org.joda.time.DateTime;
+import org.joda.time.Period;
+
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * Open manage city asset window
+ */
+public class ManageCityAssetsMsg extends ClientNetMsg {
+
+	//messageType
+	//C->S 2: S->C: 0, 3, 4, 6
+	//C->S 15: S->C: 15
+	//C->S 14: S->C: 14
+	//C->S ?: S->C: 10, 11, 16
+
+	//C->S	2 = manage this asset
+	//		20 = manage entire city
+
+	//S->C,	0 = error message
+	//		3 = manage asset
+	//		4 = no access / building info
+
+	public int actionType;
+	private int targetID;
+	private int targetType;
+	private int targetType1;
+	private int targetType2;
+	private int targetType3;
+	private int targetID1;
+	private int targetID2;
+	private int targetID3;
+	public String assetName;
+	private String AssetName1;
+	public String CityName;
+	private int rank;
+	private int symbol;
+	public int upgradeCost;
+	private int unknown04;
+	private int unknown05;
+	private int unknown06;
+	private int unknown07;
+	private int unknown14;
+	private int unknown15;
+	private int unknown16;
+	private int unknown17;
+	private int unknown54;
+	private int preName01;
+
+	private byte UnkByte03;
+	private byte UnkByte04;
+	private int strongbox;
+
+	private int baneHour;
+	private PlayerCharacter assetManager;
+	private Building asset;
+	public byte labelProtected;
+	public byte labelSiege;
+	public byte labelCeaseFire;
+	public byte buttonTransfer;
+	public byte buttonDestroy;
+	public byte buttonAbandon;
+	public byte buttonUpgrade;
+
+	/**
+	 * This is the general purpose constructor
+	 */
+	public ManageCityAssetsMsg() {
+		super(Protocol.MANAGECITYASSETS);
+		this.actionType = 0;
+		this.targetType = 0;
+		this.targetID = 0;
+		this.preName01 = 0;
+		this.assetName = "";
+		this.CityName = "";
+		this.rank = 0;
+		this.symbol = 0;
+		this.unknown04 = 0;
+		this.unknown06 = 0;
+		this.unknown07 = 0;
+		this.unknown14 = 0;
+		this.unknown15 = 0;
+		this.unknown16 = 0;
+		this.unknown17 = 0;
+
+		this.strongbox = 0;
+
+		this.targetType1 = 0;
+		this.targetType2 = 0;
+		this.targetType3 = 0;
+
+		this.targetID1 = 0;
+		this.targetID2 = 0;
+		this.targetID3 = 0;
+		this.UnkByte03 = 0;
+		this.UnkByte04 = 0;
+		this.AssetName1 = "";
+		this.unknown54 = 0;
+		this.strongbox = 0;
+		this.upgradeCost = 0;
+
+		this.labelProtected = 0;
+		this.labelSiege = 0;
+		this.labelCeaseFire = 0;
+		this.buttonTransfer = 0;
+		this.buttonDestroy = 0;
+		this.buttonAbandon = 0;
+		this.buttonUpgrade = 0;
+
+	}
+
+	public ManageCityAssetsMsg(PlayerCharacter pc, Building asset) {
+		super(Protocol.MANAGECITYASSETS);
+		this.assetManager = pc;
+		this.asset = asset;
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the ByteBuffer into a message. If a BufferUnderflow occurs (based on reading past the limit) then this constructor Throws that Exception to the caller.
+	 */
+	public ManageCityAssetsMsg(AbstractConnection origin, ByteBufferReader reader)
+			 {
+		super(Protocol.MANAGECITYASSETS, origin, reader);
+	}
+
+	public int getTargetID() {
+		return targetID;
+	}
+
+	protected int getPowerOfTwoBufferSize() {
+		return (20); // 2^10 == 1024
+	}
+	public void setTargetID(int targetID) {
+		this.targetID = targetID;
+	}
+
+	public int getTargetType() {
+		return targetType;
+	}
+
+	public void setTargetType(int targetType) {
+		this.targetType = targetType;
+	}
+
+	public void setTargetType3(int targetType3) {
+		this.targetType3 = targetType3;
+	}
+
+	public void setTargetID3(int targetID3) {
+		this.targetID3 = targetID3;
+	}
+
+	/**
+	 * Deserializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)
+			 {
+				 actionType = reader.getInt();
+		targetType = reader.getInt();
+		targetID = reader.getInt();
+				 if (this.actionType == 20) {
+			reader.getInt();
+			this.baneHour = reader.getInt();
+			reader.getInt();
+			reader.getInt();
+			reader.getInt();
+			reader.getInt();
+			reader.getInt();
+
+		} else if (this.actionType == 5) { //rename building.
+			reader.getInt();
+			assetName = reader.getString();
+			for (int i = 0; i < 5; i++)
+				reader.getInt();
+		} else if (this.actionType == 2) {
+			reader.getInt();
+			this.strongbox = reader.getInt();
+			reader.getInt();
+			reader.getInt();
+			reader.getInt();
+			reader.getInt();
+			
+
+		}else{
+			for (int i = 0; i < 6; i++)
+				reader.getInt();
+		}
+	}
+
+	/**
+	 * Serializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+		writer.putInt(this.actionType);
+
+		if (this.actionType == 2) {
+			writer.putInt(asset.getObjectType().ordinal());
+			writer.putInt(asset.getObjectUUID());
+			writer.putInt(0);
+			writer.putInt(asset.reserve);
+			writer.putInt(0);
+			return;
+		}
+
+		if (this.actionType == 13) {
+			writer.putInt(asset.getObjectType().ordinal());
+			writer.putInt(asset.getObjectUUID());
+			writer.putInt(0);
+			writer.putInt(0);
+			writer.putInt(asset.getHirelings().size());
+			for (AbstractCharacter hireling : asset.getHirelings().keySet()){
+				if (!hireling.getObjectType().equals(GameObjectType.NPC))
+					writer.putString(hireling.getName());
+				else{
+					NPC npc = (NPC)hireling;
+					if (!npc.getNameOverride().isEmpty()){
+						writer.putString(npc.getNameOverride());
+					}else
+
+						if (npc.getContract() != null) {
+							if (npc.getContract().isTrainer()) {
+								writer.putString(npc.getName() + ", " + npc.getContract().getName());
+							} else {
+								writer.putString(npc.getName() + " " + npc.getContract().getName());
+							}
+						} else {
+							writer.putString(npc.getName());
+						}
+				}
+			}
+			
+			writer.putInt(0);
+			writer.putInt(0);
+			writer.putInt(0);
+		}
+
+		//Bane window
+
+		if (this.actionType == 11) {
+			writer.putInt(asset.getObjectType().ordinal());
+			writer.putInt(asset.getObjectUUID());
+			for (int a = 0;a<5;a++)
+				writer.putInt(0);
+
+			writer.putInt(asset.getHirelings().size());
+
+			for (AbstractCharacter npcHire : asset.getHirelings().keySet()) {
+				writer.putInt(npcHire.getObjectType().ordinal());
+				writer.putInt(npcHire.getObjectUUID());
+				if (npcHire.getObjectType() == GameObjectType.NPC)
+					writer.putString(((NPC)npcHire).getContract().getName());
+				else
+					writer.putString("Guard Captain");
+				writer.putString(npcHire.getName());
+				writer.putInt(1);
+				writer.putInt(Blueprint.getNpcMaintCost(npcHire.getRank()));
+				if (npcHire.getObjectType() == GameObjectType.NPC)
+					writer.putInt(((NPC)npcHire).getContract().getIconID()); // Was 60
+				else if (npcHire.getObjectType() == GameObjectType.Mob){
+					writer.putInt(((Mob)npcHire).getContract().getIconID()); // Was 60
+				}
+				else
+					writer.putInt(5);
+				writer.put((byte) 0);
+				writer.put((byte) 0);
+				writer.put((byte) 1);
+				writer.put((byte) 0);
+				writer.put((byte) 0);
+			}
+			return;
+		}
+
+		if (this.actionType == 15) {
+			writer.putInt(1);
+			writer.putInt(1);
+			City city = null;
+			Zone playerZone = ZoneManager.findSmallestZone(assetManager.getLoc());
+			Set<Building> buildings = ZoneManager.findSmallestZone(assetManager.getLoc()).zoneBuildingSet;
+			
+			Building tol = null;
+			if (playerZone.getPlayerCityUUID() != 0)
+				city = City.GetCityFromCache(playerZone.getPlayerCityUUID());
+			
+			if (city != null)
+				tol = city.getTOL();
+			
+			
+
+			writer.putInt(0); // 1 + String = custom message, cant control assets.
+			writer.putInt(0);
+			writer.putInt(0); //array
+
+			writer.putInt(buildings.size());
+
+			int i = 0;
+			for (Building building: buildings){
+
+				i++;
+				writer.putString(building.getName()); //ARRAY
+				writer.putInt(building.getObjectType().ordinal()); //?
+				writer.putInt(building.getObjectUUID()); //?
+
+				writer.putInt(4);
+				writer.putInt(4);
+
+				writer.put((byte)0);
+				writer.put((byte)0);
+				writer.put((byte)1);
+				writer.put((byte)1);
+				
+				//max distance to bypass clientside check.
+				float maxDistance = 2000;
+				
+				
+				writer.putFloat(maxDistance);
+
+				writer.putInt(0);
+				writer.putInt(0);
+				writer.putInt(0);
+
+				if (building.getPatrolPoints() != null){
+					writer.putInt(building.getPatrolPoints().size());
+					for (Vector3fImmutable patrolPoint: building.getPatrolPoints()){
+						writer.putVector3f(patrolPoint);
+					}
+				}else{
+					writer.putInt(0);
+				}
+				writer.putInt(0); //Sentry Point
+			
+				if (building.getBlueprint() != null && building.getBlueprint().getBuildingGroup() == BuildingGroup.BARRACK){
+					writer.putInt(1); //Tab left Random Town? //Opens up 16 Bytes
+					writer.putInt(4);
+					writer.putInt(0);
+					writer.putInt(0);
+					writer.putInt(4);
+				}else
+					writer.putInt(0);
+				writer.putInt(0); //array with size 32 bytes. // Adds information of building
+			}
+			writer.putInt(0); //ARRAY
+			writer.putInt(0);
+		}
+
+		if (this.actionType == 18) {
+			Zone zone = asset.getParentZone();
+
+			if (zone == null)
+				return;
+
+			City banedCity = City.getCity(zone.getPlayerCityUUID());
+
+			if (banedCity == null)
+				return;
+
+			Bane bane = banedCity.getBane();
+
+			if (bane == null)
+				return;
+
+			Guild attackerGuild = bane.getOwner().getGuild();
+
+			if (attackerGuild == null)
+				return;
+
+			writer.putInt(asset.getObjectType().ordinal());
+			writer.putInt(asset.getObjectUUID());
+
+			writer.putInt(0);
+			writer.putString(attackerGuild.getName());
+			writer.putString(Guild.GetGL(attackerGuild).getName());
+			writer.putInt(bane.getSiegePhase().ordinal()); //1 challenge //2 standoff //3 war
+			writer.put((byte) 0);
+
+			if (!bane.isAccepted() && this.assetManager.getGuild() == banedCity.getGuild() && GuildStatusController.isInnerCouncil(this.assetManager.getGuildStatus()))
+				writer.put((byte) 1); //canSetTime
+			else
+				writer.put((byte) 0);
+
+			DateTime placedOn = bane.getLiveDate();
+
+			if (placedOn == null)
+				placedOn = new DateTime(DateTime.now());
+
+			//set Calander to date of bane live.
+			DateTime now = DateTime.now();
+			DateTime defaultTime = new DateTime(bane.getPlacementDate());
+			DateTime playerEnterWorldTime = new DateTime(this.assetManager.getTimeStamp("EnterWorld"));
+			Period period = new Period(playerEnterWorldTime.getMillis(), now.getMillis());
+			int hoursLoggedIn = period.getHours();
+			hoursLoggedIn = hoursLoggedIn < 0 ? 0 : hoursLoggedIn;
+
+			defaultTime = defaultTime.plusDays(2);
+			defaultTime = defaultTime.hourOfDay().setCopy(22);
+			defaultTime = defaultTime.minuteOfHour().setCopy(0);
+			defaultTime = defaultTime.secondOfMinute().setCopy(0);
+
+			long curTime = now.getMillis();
+			long timeLeft = 0;
+
+			if (bane.getLiveDate() != null)
+				timeLeft = bane.getLiveDate().getMillis() - curTime;
+			else
+				timeLeft = defaultTime.getMillis() - curTime + 1000;
+
+			//DO not touch these. They are static formula's until i get the correct converter for SB Time.
+
+			writer.put((byte) placedOn.dayOfMonth().get());
+			writer.put((byte) placedOn.monthOfYear().get());
+			writer.putInt(placedOn.year().get() - 1900);
+			writer.put((byte) 0);
+			writer.put((byte) 0);
+			writer.put((byte) 0);
+
+			if (timeLeft < 0)
+				writer.putInt(0);
+			else
+				writer.putInt((int) timeLeft / 1000); // Time remaing until bane/Seconds
+
+			if (attackerGuild.getGuildState() == GuildState.Sworn)
+				writer.putInt(4); //3 capture/errant,4 capture/sworn, 5 destroy/soveirgn.
+			else
+				writer.putInt(5);
+
+			writer.put((byte) (16 - hoursLoggedIn)); // hour start
+			writer.put((byte) (24 - hoursLoggedIn)); // hour end
+			writer.put((byte) 2);
+			writer.putString(banedCity.getCityName());
+			writer.putString(Guild.GetGL(bane.getOwner().getGuild()) != null ? Guild.GetGL(bane.getOwner().getGuild()).getName() : "No Guild Leader");
+			GuildTag._serializeForDisplay(attackerGuild.getGuildTag(),writer);
+			GuildTag._serializeForDisplay(attackerGuild.getNation().getGuildTag(),writer);
+			writer.putInt(0);
+			writer.putInt(0);
+			writer.putInt(0);
+			writer.putInt(0);
+			writer.putInt(0);
+		}
+
+		if (this.actionType == 3) {
+
+			writer.putInt(targetType);
+			writer.putInt(targetID);
+
+			Guild nation = null;
+			Building building = BuildingManager.getBuildingFromCache(targetID);
+			Guild guild = building.getGuild();
+			Zone zone = ZoneManager.findSmallestZone(building.getLoc());
+
+			writer.putInt(0);//unknown  Might be to allow manager to open or not!
+			writer.putString(building.getName());
+
+			AbstractCharacter buildingOwner =  building.getOwner();
+
+			if (buildingOwner == null)
+				writer.putString("Morloch");
+			else
+				writer.putString(buildingOwner.getName());
+
+			if (zone == null)
+				writer.putString("Forlord");
+			else
+				writer.putString(zone.getName());
+
+				writer.putString(building.getGuild().getName());
+
+			writer.putInt(building.getRank());
+
+			// Maintenance costs include resource if
+			// this structure is an R8 tree
+
+			if (building.getRank() == 8)
+				writer.putInt(5); // Resources included
+			else
+				writer.putInt(1); // Gold only
+
+			writer.putInt(2308551); //Gold
+			if (building.getBlueprint() == null)
+				writer.putInt(0);
+			else
+			writer.putInt(building.getBlueprint().getMaintCost(building.getRank())); //  maint cost
+
+			if (building.getRank() == 8) {
+				writer.putInt(74856115); // Stone
+				writer.putInt(1500); //  maint cost
+				writer.putInt(-1603256692); // Lumber
+				writer.putInt(1500); //  maint cost
+				writer.putInt(-1596311545); // Galvor
+				writer.putInt(5); //  maint cost
+				writer.putInt(1532478436); // Wormwood
+				writer.putInt(5); //  maint cost
+			}
+
+			LocalDateTime maintDate = building.maintDateTime;
+
+			if (maintDate == null)
+				maintDate = LocalDateTime.now();
+			writer.putLocalDateTime(LocalDateTime.now()); // current time
+
+			// utc offset?
+			writer.putInt((int)java.time.Duration.between(LocalDateTime.now(), maintDate).getSeconds()); // Seconds to maint date
+
+			writer.putInt(building.getStrongboxValue());
+			writer.putInt(building.reserve);//reserve Sets the buildings reserve display
+			writer.putInt(0);//prosperity under maintenance (wtf is prosperity?)
+			writer.putInt(10);
+			writer.putFloat((float) .1);
+
+			if (this.buttonUpgrade == 1) {
+				if (building.getBlueprint() == null)
+					this.upgradeCost = Integer.MAX_VALUE;
+				else
+				if (building.getRank() == building.getBlueprint().getMaxRank())
+					this.upgradeCost = Integer.MAX_VALUE;
+				else
+					this.upgradeCost = building.getBlueprint().getRankCost(Math.min(building.getRank() + 1, 7));
+
+				writer.putInt(this.upgradeCost);
+			}
+			else
+				writer.putInt(0);
+
+			LocalDateTime uc = LocalDateTime.now();
+
+			if (building.getDateToUpgrade() != null)
+				uc = building.getDateToUpgrade();
+
+			long timeLeft = uc.atZone(ZoneId.systemDefault())
+					.toInstant().toEpochMilli() - System.currentTimeMillis();
+			long hour = timeLeft / 3600000;
+			long noHour = timeLeft - (hour * 3600000);
+			long minute = noHour / 60000;
+			long noMinute = noHour - (minute * 60000);
+			long second = noMinute / 1000;
+
+			writer.put((byte) 0);//Has to do with repair time. A 1 here puts 23.9 hours in repair time A 2 here is 1.9 days
+			writer.put((byte) 0);//unknown
+			writer.putInt(0); //unknown
+
+			if (LocalDateTime.now().isAfter(uc)) {
+				writer.put((byte) 0);
+				writer.put((byte) 0);
+				writer.put((byte) 0);
+			}
+			else {
+				writer.put((byte) (hour));
+				writer.put((byte) minute);
+				writer.put((byte) second);
+			}
+
+			if (timeLeft < 0)
+				writer.putInt(0);
+			else
+				writer.putInt((int) timeLeft);
+
+			writer.putInt((int) building.getCurrentHitpoints());
+			writer.putInt((int) building.getMaxHitPoints());
+			writer.putInt(BuildingManager.GetRepairCost(building));//sets the repair cost.
+			writer.putInt(0);//unknown
+
+			if (building.getBlueprint() == null)
+				writer.putInt(0);
+			else
+			writer.putInt(building.getBlueprint().getSlotsForRank(building.getRank()));
+			writer.put((byte) 1);//Has to do with removing update timer and putting in cost for upgrade
+
+			writer.put(labelProtected); // 1 sets protection to invulnerable.
+			writer.put(labelSiege);// 1 sets the protection under siege
+			writer.put(labelCeaseFire); //0 with 1 set above sets to under siege // 1 with 1 set above sets protection status to under siege(cease fire)
+
+			writer.put(buttonTransfer);// 1 enables the transfer asset button
+			writer.put(buttonDestroy);// 1 enables the destroy asset button
+			writer.put(buttonAbandon);// 1 here enables the abandon asset button
+			writer.put(buttonUpgrade); //disable upgrade building
+
+			if (building.getBlueprint() == null)
+				writer.putInt(0);
+			else
+			writer.putInt(building.getBlueprint().getIcon()); //Symbol
+
+			if (guild == null) {
+				for (int i = 0; i < 3; i++)
+					writer.putInt(16);
+				for (int i = 0; i < 2; i++)
+					writer.putInt(0);
+			}
+			else {
+				GuildTag._serializeForDisplay(guild.getGuildTag(),writer);
+				nation = guild.getNation();
+			}
+
+			if (nation == null) {
+				for (int i = 0; i < 3; i++)
+					writer.putInt(16);
+				for (int i = 0; i < 2; i++)
+					writer.putInt(0);
+			}
+			else {
+				GuildTag._serializeForDisplay(nation.getGuildTag(),writer);
+			}
+			writer.putInt(0);
+			writer.putInt(0);
+			writer.putInt(0);//1 makes it so manage window does not open.
+
+			if (!building.assetIsProtected() && !building.getProtectionState().equals(ProtectionState.PENDING)){
+				writer.putInt(0);
+			}
+			else{
+				writer.putInt(1); //kos on/off?
+				writer.putInt(3); // was 3
+				if (zone.getPlayerCityUUID() != 0 && asset.assetIsProtected()){
+					writer.putInt(GameObjectType.Building.ordinal());
+					writer.putInt(City.getCity(zone.getPlayerCityUUID()).getTOL().getObjectUUID());
+				}
+				else{
+					writer.putInt(0);
+					writer.putInt(0);
+				}
+
+				writer.putInt(0);
+				writer.putInt(0);
+
+				writer.putInt(targetType3);
+				writer.putInt(targetID3);
+
+
+				if (building.getProtectionState() == ProtectionState.PENDING)
+					writer.put((byte)1); //Accept or decline.
+				else
+					writer.put((byte)0);
+
+				if (building.taxType == TaxType.NONE)
+					writer.put((byte)0); //? ??
+				else if(building.taxDateTime != null)
+					writer.put((byte)1);
+				else
+					writer.put((byte)0);
+
+				writer.putString(""); //tree of life protection tax
+				writer.putInt(0); //??
+				writer.putInt(0); //??
+				if (building.taxType == TaxType.NONE){
+					writer.putInt(0);
+					writer.putInt(0);
+				}else if (building.taxType == TaxType.WEEKLY){
+					writer.putInt(building.taxAmount);
+					writer.putInt(0);
+				}else{
+					writer.putInt(0);
+					writer.putInt(building.taxAmount);
+				}
+
+
+				writer.put(building.enforceKOS ? (byte)1:0); //enforceKOS
+				writer.put((byte) 0); //?
+				writer.putInt(1);
+			}
+
+
+
+			ConcurrentHashMap<AbstractCharacter, Integer> npcList = building.getHirelings();
+			writer.putInt(npcList.size());
+			if (npcList.size() > 0) {
+				for (AbstractCharacter npcHire : npcList.keySet()) {
+					writer.putInt(npcHire.getObjectType().ordinal());
+					if (npcHire.getObjectType() == GameObjectType.Mob)
+						writer.putInt(((Mob)npcHire).getDBID());
+					else
+						writer.putInt(npcHire.getObjectUUID());
+					if (npcHire.getObjectType() == GameObjectType.NPC)
+						writer.putString(((NPC)npcHire).getContract().getName());
+					else
+						writer.putString("Guard Captain");
+					writer.putString(npcHire.getName());
+					writer.putInt(npcHire.getRank());
+					writer.putInt(Blueprint.getNpcMaintCost(npcHire.getRank()));
+					if (npcHire.getObjectType() == GameObjectType.NPC)
+						writer.putInt(((NPC)npcHire).getContract().getIconID()); // Was 60
+					else  if (npcHire.getObjectType() == GameObjectType.Mob)
+						writer.putInt(((Mob)npcHire).getContract().getIconID()); // Was 60
+
+					int contractID = 0;
+
+
+					if (npcHire.getObjectType() == GameObjectType.Mob)
+						contractID = ((Mob)npcHire).getContract().getContractID();
+					else if (npcHire.getObjectType() == GameObjectType.NPC)
+						contractID = ((NPC)npcHire).getContract().getContractID();
+
+					if (contractID ==830){
+						writer.putInt(24580);
+					}
+					else	if (building.getBlueprint() != null && (building.getBlueprint().getBuildingGroup() == BuildingGroup.FORGE ||building.getBlueprint().getBuildingGroup() == BuildingGroup.MAGICSHOP||building.getBlueprint().getBuildingGroup() == BuildingGroup.TAILOR)){
+
+						writer.put((byte)0);
+						writer.put((byte)4);
+						writer.put((byte)128);
+						writer.put((byte)0);
+
+					}else{
+						writer.put((byte)0);
+						if (building.getBlueprint() != null && building.getBlueprint().getBuildingGroup() == BuildingGroup.BARRACK)
+							writer.put((byte)1);
+						else
+							writer.put((byte)0);
+						writer.put((byte)0);
+						writer.put((byte)0);
+					}
+
+
+					if (!npcHire.isAlive()){
+						writer.put((byte) 1); // 1 SHOWs respawning
+						writer.putInt(10); // Seconds in respawn.
+						writer.putInt(20);
+					}
+					else
+						writer.put((byte)0);
+
+				}
+			}
+		}
+		if (this.actionType == 4) {
+			writer.putInt(targetType);
+			writer.putInt(targetID);
+			Building building = BuildingManager.getBuildingFromCache(targetID);
+
+			writer.putInt(preName01);
+			writer.putString(building.getName()); //assetName
+			writer.putString(building.getOwnerName()); //ownerName
+			writer.putString(building.getGuild().getName());//guild name
+			writer.putString(building.getCityName()); //City Name
+			writer.putInt(building.getRank());
+			if (building.getBlueprint() == null)
+				writer.putInt(0);
+			else
+				writer.putInt(building.getBlueprint().getIcon());
+
+			//tags
+			GuildTag._serializeForDisplay(building.getGuild().getGuildTag(), writer);
+			GuildTag._serializeForDisplay(building.getGuild().getNation().getGuildTag(), writer);
+
+			writer.putInt(unknown14);
+			writer.putInt(unknown15);
+			writer.putInt(unknown16);
+			writer.putInt(unknown17);
+			writer.putInt(0); // previously uninitialized unknown18
+		}
+	}
+
+	public int getRank() {
+		return rank;
+	}
+
+	public void setRank(int rank) {
+		this.rank = rank;
+	}
+
+	public int getSymbol() {
+		return symbol;
+	}
+
+	public void setSymbol(int symbol) {
+		this.symbol = symbol;
+	}
+
+	public int getUnknown04() {
+		return unknown04;
+	}
+
+	public void setUnknown04(int unknown04) {
+		this.unknown04 = unknown04;
+	}
+
+	public int getUnknown05() {
+		return unknown05;
+	}
+
+	public void setUnknown05(int unknown05) {
+		this.unknown05 = unknown05;
+	}
+
+	public int getUnknown06() {
+		return unknown06;
+	}
+
+	public void setUnknown06(int unknown06) {
+		this.unknown06 = unknown06;
+	}
+
+	public void setUnknown07(int unknown07) {
+		this.unknown07 = unknown07;
+	}
+
+	public String getAssetName() {
+		return assetName;
+	}
+
+	public void setAssetName(String AssetName) {
+		this.assetName = AssetName;
+	}
+
+	public void setAssetName1(String AssetName1) {
+		this.AssetName1 = AssetName1;
+	}
+
+	public String getCityName() {
+		return CityName;
+	}
+
+	public void setUnknown54(int unknown54) {
+		this.unknown54 = unknown54;
+	}
+
+	public int getStrongbox() {
+		return strongbox;
+	}
+
+	public void setStrongbox(int strongbox) {
+		this.strongbox = strongbox;
+	}
+
+	public int getBaneHour() {
+		return baneHour;
+	}
+
+	public Building getAsset() {
+		return asset;
+	}
+
+	public void setAsset(Building asset) {
+		this.asset = asset;
+	}
+
+}
+
+//Debug Info
+//Run: Failed to make object TEMPLATE:135700 INSTANCE:1717987027141... (t=50.46) (r=7/4/2011 11:56:39)
+//C:\ArcanePrime\Main_Branch\Shadowbane\Source\ArcObjectLoader.cpp(466):ERROR: ArcObjectLoader::Run: Failed to make object TEMPLATE:108760 INSTANCE:1717987027161... (t=50.46) (r=7/4/2011 11:56:39)
+//C:\ArcanePrime\Main_Branch\Shadowbane\Source\ArcObjectLoader.cpp(466):ERROR: ArcObjectLoader::Run: Failed to make object TEMPLATE:108760 INSTANCE:1717987027177... (t=50.67) (r=7/4/2011 11:56:39)
+//C:\ArcanePrime\Main_Branch\Shadowbane\Source\ArcObjectLoader.cpp(466):ERROR: ArcObjectLoader::Run: Failed to make object TEMPLATE:60040 INSTANCE:1717987027344... (t=50.87) (r=7/4/2011 11:56:39)
+//C:\ArcanePrime\Main_Branch\Shadowbane\Source\ArcObjectLoader.cpp(466):ERROR: ArcObjectLoader::Run: Failed to make object TEMPLATE:3 INSTANCE:1717987027164... (t=50.88) (r=7/4/2011 11:56:39)
+
diff --git a/src/engine/net/client/msg/ManageNPCMsg.java b/src/engine/net/client/msg/ManageNPCMsg.java
new file mode 100644
index 00000000..07b995ac
--- /dev/null
+++ b/src/engine/net/client/msg/ManageNPCMsg.java
@@ -0,0 +1,1462 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+import engine.Enum.GameObjectType;
+import engine.Enum.MinionType;
+import engine.Enum.ProtectionState;
+import engine.gameManager.PowersManager;
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.objects.*;
+import engine.powers.EffectsBase;
+import org.joda.time.DateTime;
+import org.joda.time.Period;
+import org.joda.time.Seconds;
+import org.pmw.tinylog.Logger;
+
+import java.util.ArrayList;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * Order NPC
+ */
+public class ManageNPCMsg extends ClientNetMsg {
+
+	private int targetType;
+	private int targetID;
+	private int unknown03;
+	private int unknown04;
+	private int unknown05;
+	private int unknown06;
+
+	private int unknown07;
+	private int unknown08;
+	private int unknown09;
+	private int unknown10;
+	private int unknown11;
+	private int buyNormal;
+	private int buyGuild;
+	private int buyNation;
+	private int sellNormal;
+	private int sellGuild;
+	private int sellNation;
+
+	private String CityName;
+	private String OwnerName;
+	private String GuildName;
+	private int unknown12;
+
+	private int unknown13;
+	private int unknown14;
+	private int unknown15;
+	private int unknown16;
+	private int unknown17;
+	private int unknown18;
+
+	private int messageType;
+
+	private int unknown19; //Arraylist motto length?
+	private String motto; //motto Length 60 max?
+
+	private int unknown01;
+
+	private int buildingID;
+	private int unknown20;
+	private int unknown21;
+	private int unknown22;
+	private int unknown23;
+	private int unknown24;
+	private int unknown25;
+	private int unknown26;
+	private int unknown28;
+	private int unknown30;
+	private int unknown31;
+	private int unknown32;
+	private int unknown33;
+	private int unknown34;
+	private int unknown35;
+	private int unknown36;
+	private int unknown37;
+	private int unknown38;
+	private int unknown39;
+	private int unknown40;
+	private int unknown41;
+	private int unknown42;
+	private int unknown43;
+	private int unknown44;
+	private int unknown45;
+	private int unknown46;
+	private int unknown47;
+	private int unknown48;
+	private int unknown49;
+	private int unknown50;
+	private int unknown51;
+	private int unknown52;
+	private int unknown53;
+	private int unknown54;
+	private int unknown55;
+	private int unknown56;
+	private int unknown57;
+	private int unknown58;
+	private int unknown59;
+	private int unknown60;
+	private int unknown61;
+	private int unknown62;
+	private int unknown63;
+	private int unknown64;
+	private int unknown65;
+	private int unknown66;
+	private int unknown67;
+	private int unknown68;
+	private int unknown69;
+	private int unknown70;
+	private int unknown71;
+	private int unknown72;
+	private int unknown73;
+	private int unknown74;
+	private int unknown75;
+	private int unknown76;
+	private int unknown77;
+	private int unknown78;
+	private int unknown79;
+	private int unknown80;
+	private int unknown81;
+	private int unknown82;
+	private int unknown83;
+
+	/**
+	 * This is the general purpose constructor
+	 */
+	public ManageNPCMsg(AbstractCharacter ac) {
+		super(Protocol.MANAGENPC);
+		this.targetType = ac.getObjectType().ordinal();
+		this.targetID = ac.getObjectUUID();
+		this.buyGuild = 26;  //TODO pull all these from the NPC object
+		this.buyNation = 26;
+		this.buyNormal = 26;
+		this.sellGuild = 100;
+		this.sellNation = 100;
+		this.sellNormal = 100;
+		this.messageType = 1; //This seems to be the "update Hireling window" value flag
+
+		//Unknown defaults...
+		this.unknown20 = 0;
+		this.unknown21 = 0;
+		this.unknown22 = 0;
+		this.unknown23 = 0;
+		this.unknown24 = 0;
+		this.unknown25 = 0;
+		this.unknown26 = 0;
+		this.unknown28 = 0;
+		this.unknown30 = 0;
+		this.unknown31 = 0;
+		this.unknown32 = 0;
+		this.unknown33 = 0;
+		this.unknown34 = 0;
+		this.unknown35 = 0;
+		this.unknown36 = 0;
+		this.unknown37 = 1;//1
+		this.unknown38 = 0;//0
+		this.unknown39 = 0;//0
+		this.unknown40 = 1;//1
+		this.unknown41 = 0;//0
+		this.unknown42 = 1;//1 [Toggles tree icon in protection slots]
+		this.unknown43 = 0;//0
+		this.unknown44 = 1;//1
+		this.unknown45 = 0;//0
+		this.unknown46 = 0;//0
+		this.unknown47 = 0;//0
+		this.unknown48 = 0;//0
+		this.unknown49 = 0;//0
+		this.unknown50 = 0;//0
+		this.unknown51 = 0;//0
+		this.unknown52 = 0;//0
+		this.unknown53 = 0;//0
+		this.unknown54 = 1;//1
+		this.unknown55 = 0;//0
+		this.unknown56 = 3;//3
+		this.unknown57 = 3;//3
+		this.unknown58 = 0;//0
+		this.unknown59 = 5;//5
+		this.unknown60 = 0;//0
+		this.unknown61 = 0;//0
+		this.unknown62 = 0;//0
+		this.unknown63 = 64;//64
+		this.unknown64 = 0;//0
+		this.unknown65 = 0;//0
+		this.unknown66 = 0;//0
+		this.unknown67 = 0;//0
+		this.unknown68 = 1;//1
+		this.unknown69 = 1;//1
+		this.unknown70 = 0;//0
+		this.unknown71 = 1;//1
+		this.unknown72 = 0;
+		this.unknown73 = 0;
+		this.unknown74 = 5;
+		this.unknown75 = 1;
+		this.unknown76 = 2;
+		this.unknown77 = 15;
+		this.unknown78 = 3;
+		this.unknown79 = 18;
+		this.unknown80 = 0;
+		this.unknown81 = 0;
+		this.unknown82 = 0;
+		this.unknown83 = 0;
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public ManageNPCMsg(AbstractConnection origin, ByteBufferReader reader)
+			 {
+		super(Protocol.MANAGENPC, origin, reader);
+	}
+
+	@Override
+	protected int getPowerOfTwoBufferSize() {
+		return (19); // 2^10 == 1024
+	}
+
+	/**
+	 * Deserializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)
+			 {
+		//TODO do we need to do anything here? Does the client ever send this message to the server?
+	}
+
+	/**
+	 * Serializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+
+		Period upgradePeriod;
+		int upgradePeriodInSeconds;
+
+		try{
+			
+	
+		writer.putInt(messageType); //1
+		if (messageType == 5) {
+			writer.putInt(unknown20);//0
+			writer.putInt(targetType);
+			writer.putInt(targetID);
+
+			writer.putInt(GameObjectType.Building.ordinal());
+			writer.putInt(buildingID);
+			writer.putInt(0);
+			writer.putInt(0);
+			writer.putInt(0);
+			writer.putInt(0);
+			writer.putInt(0);
+			writer.putInt(0);
+			writer.putInt(0);
+			writer.putInt(0);
+			writer.putInt(0);
+			writer.putInt(0);
+			writer.putInt(0);
+			writer.putInt(0);
+			writer.putInt(0);
+			writer.putInt(0);
+			writer.putInt(0);
+			writer.putInt(0);
+			writer.putInt(0);
+
+		} else if (messageType == 1) {
+			NPC npc = null;
+			Mob mobA = null;
+
+			if (this.targetType == GameObjectType.NPC.ordinal()){
+
+				npc = NPC.getFromCache(this.targetID);
+
+				if (npc == null) {
+					Logger.error("Missing NPC of ID " + this.targetID);
+					return;
+				}
+
+				Contract contract = null;
+				contract = npc.getContract();
+
+				if (contract == null) {
+					Logger.error("Missing contract for NPC " + this.targetID);
+					return;
+				}
+
+				writer.putInt(0); //anything other than 0 seems to mess up the client
+				writer.putInt(targetType);
+				writer.putInt(targetID);
+				writer.putInt(0); //static....
+				writer.putInt(0);//static....
+				writer.putInt(Blueprint.getNpcMaintCost(npc.getRank()));  // salary
+
+				writer.putInt(npc.getUpgradeCost());
+
+				if (npc.isRanking() && npc.getUpgradeDateTime().isAfter(DateTime.now()))
+					upgradePeriod = new Period(DateTime.now(), npc.getUpgradeDateTime());
+				else
+					upgradePeriod = new Period(0);
+
+				writer.put((byte) upgradePeriod.getDays());    //for timer
+				writer.put((byte) unknown26);//unknown
+				writer.putInt(100); //unknown
+
+				writer.put((byte) upgradePeriod.getHours());    //for timer
+				writer.put((byte) upgradePeriod.getMinutes());  //for timer
+				writer.put((byte) upgradePeriod.getSeconds());  //for timer
+
+				if (npc.isRanking() && npc.getUpgradeDateTime().isAfter(DateTime.now()))
+					upgradePeriodInSeconds = Seconds.secondsBetween(DateTime.now(), npc.getUpgradeDateTime()).getSeconds();
+				else
+					upgradePeriodInSeconds = 0;
+
+				writer.putInt(upgradePeriodInSeconds);
+
+				writer.put((byte) 0);
+				writer.put((byte) (npc.getRank() == 7 ? 0 : 1));  //0 will make the upgrade field show "N/A"
+				writer.put((byte) 0);
+				writer.put((byte) 0);
+				writer.putInt(0);
+				writer.putInt(10000);  //no idea...
+				writer.put((byte) 0);
+				writer.put((byte) 0);
+				writer.put((byte) 0);
+				writer.putInt(0);
+
+				NPCProfits profit = NPC.GetNPCProfits(npc);
+				
+				if (profit == null)
+					profit = NPCProfits.defaultProfits;
+				//adding .000000001 to match client.
+				int buyNormal = (int) ((profit.buyNormal + .000001f) * 100);
+				int buyGuild = (int) ((profit.buyGuild + .000001f) *100);
+				int buyNation = (int) ((profit.buyNation + .000001f) * 100);
+				
+				int sellNormal = (int) ((profit.sellNormal + .000001f) * 100);
+				int sellGuild = (int) ((profit.sellGuild + .000001f) * 100);
+				int sellNation = (int) ((profit.sellNation + .000001f) * 100);
+				
+				writer.putInt(buyNormal);
+				writer.putInt(buyGuild);
+				writer.putInt(buyNation);
+				writer.putInt(sellNormal);
+				writer.putInt(sellGuild);
+				writer.putInt(sellNation);
+
+				if (contract.isRuneMaster()) {
+					writer.putInt(0); //vendor slots
+					writer.putInt(0); //artillery slots
+
+					//figure out number of protection slots based on building rank
+					int runemasterSlots = (2 * npc.getRank()) + 6;
+
+					writer.putInt( runemasterSlots);
+
+					for (int i = 0; i < 13; i++) {
+						writer.putInt(0); //statics
+					}
+					//some unknown list
+					writer.putInt(4); //list count
+					writer.putInt(17);
+					writer.putInt(2);
+					writer.putInt(12);
+					writer.putInt(23);
+
+					writer.putInt(0); //static
+					writer.putInt(0); //static
+
+					//TODO add runemaster list here
+
+					ArrayList<Building> buildingList = npc.getProtectedBuildings();
+
+					writer.putInt(buildingList.size());
+
+					for (Building b : buildingList) {
+						writer.putInt(3);
+						writer.putInt(b.getObjectType().ordinal());
+						writer.putInt(b.getObjectUUID());
+
+						writer.putInt(npc.getParentZone().getObjectType().ordinal());
+						writer.putInt(npc.getParentZone().getObjectUUID());
+
+						writer.putLong(0); //TODO Identify what Comp this is suppose to be.
+						if (b.getProtectionState() == ProtectionState.PENDING)
+							writer.put((byte)1);
+						else
+							writer.put((byte)0);
+						writer.put((byte)0);
+						writer.putString(b.getName());
+						writer.putInt(1);//what?
+						writer.putInt(1);//what?
+						//taxType = b.getTaxType()
+						switch(b.taxType){
+						case NONE:
+							writer.putInt(0);
+							writer.putInt(0);
+							break;
+						case WEEKLY:
+							writer.putInt(b.taxAmount);
+							writer.putInt(0);
+							break;
+						case PROFIT:
+							writer.putInt(0);
+							writer.putInt(b.taxAmount);
+							break;
+
+						}
+                        writer.put(b.enforceKOS ? (byte)1:0); //ENFORCE KOS
+						writer.put((byte)0); //??
+						writer.putInt(1);
+					}
+
+					writer.putInt(0); //artillery captain list
+
+				} else if (contract.isArtilleryCaptain()) {
+					int slots = 1;
+					if (contract.getContractID() == 839)
+						slots = 3;
+
+
+					writer.putInt(0); //vendor slots
+					writer.putInt(slots); //artillery slots
+					writer.putInt(0); //runemaster slots
+
+					for (int i = 0; i < 13; i++) {
+						writer.putInt(0); //statics
+					}
+					//some unknown list
+					writer.putInt(1); //list count
+					writer.putInt(16);
+
+					writer.putInt(0); //static
+					writer.putInt(0); //static
+					writer.putInt(0); //runemaster list
+
+					//artillery captain list
+					ConcurrentHashMap<Mob, Integer> siegeMinions = npc.getSiegeMinionMap();
+					writer.putInt(1 + siegeMinions.size());
+					serializeBulwarkList(writer, 1); //Trebuchet
+					//serializeBulwarkList(writer, 2); //Ballista
+
+					if (siegeMinions != null && siegeMinions.size() > 0)
+
+						for (Mob mob : siegeMinions.keySet()) {
+							this.unknown83 = mob.getObjectUUID();
+							writer.putInt(2);
+							writer.putInt(mob.getObjectType().ordinal());
+							writer.putInt(this.unknown83);
+							writer.putInt(0);
+							writer.putInt(10);
+							writer.putInt(0);
+							writer.putInt(1);
+							writer.putInt(1);
+							writer.put((byte) 0);
+							long curTime = System.currentTimeMillis() / 1000;
+							long upgradeTime = mob.getTimeToSpawnSiege() / 1000;
+							long timeLife = upgradeTime - curTime;
+
+							writer.putInt(900);
+							writer.putInt(900);
+							writer.putInt((int) timeLife); //time remaining?
+							writer.putInt(0);
+							writer.put((byte)0);
+							writer.putString(mob.getName());
+							writer.put((byte) 0);
+						}
+					return;
+
+				}else{
+
+					if (Contract.NoSlots(npc.getContract()))
+						writer.putInt(0);
+					else
+					writer.putInt(npc.getRank()); //vendor slots
+					writer.putInt(0); //artilerist slots
+					writer.putInt(0); //runemaster slots
+
+					writer.putInt(1); //is this static?
+					for (int i = 0; i < 4; i++) {
+						writer.putInt(0); //statics
+					}
+					//Begin Item list for creation.
+					writer.putInt(npc.getCanRoll().size());
+
+					for (Integer ib : npc.getCanRoll()) {
+						ItemBase item = ItemBase.getItemBase(ib);
+						writer.put((byte) 1);
+						writer.putInt(0);
+						writer.putInt(ib); //itemID
+						writer.putInt(item.getBaseValue());
+						writer.putInt(600);
+						writer.put((byte) 1);
+						writer.put((byte) item.getModTable());
+						writer.put((byte) item.getModTable());
+						writer.put((byte) item.getModTable());
+						writer.put((byte) item.getModTable());//EffectItemType
+					}
+					ArrayList<MobLoot> itemList = npc.getRolling();
+
+					if (itemList.isEmpty())
+						writer.putInt(0);
+					else {
+						if (itemList.size() < npc.getRank())
+							writer.putInt(itemList.size());
+						else
+							writer.putInt(npc.getRank());
+						for (Item i : itemList) {
+							if (itemList.indexOf(i) >= npc.getRank())
+								break;
+							ItemBase ib = i.getItemBase();
+							writer.put((byte) 0); // ? Unknown45
+							writer.putInt(i.getObjectType().ordinal());
+							writer.putInt(i.getObjectUUID());
+
+							writer.putInt(0);
+							writer.putInt(i.getItemBaseID());
+							writer.putInt(ib.getBaseValue());
+							long curTime = System.currentTimeMillis() / 1000;
+							long upgradeTime = i.getDateToUpgrade() / 1000;
+							long timeLife = i.getDateToUpgrade() - System.currentTimeMillis();
+
+							timeLife /= 1000;
+							writer.putInt((int) timeLife);
+							writer.putInt(npc.getRollingTimeInSeconds(i.getItemBaseID()));
+							writer.putInt(1);
+							if (i.isComplete())
+								writer.put((byte) 1);
+							else
+								writer.put((byte) 0);
+
+							ArrayList<String> effectsList = i.getEffectNames();
+							EffectsBase prefix = null;
+							EffectsBase suffix = null;
+
+							for (String effectName: effectsList){
+								if (effectName.contains("PRE"))
+									prefix = PowersManager.getEffectByIDString(effectName);
+								if (effectName.contains("SUF"))
+									suffix = PowersManager.getEffectByIDString(effectName);
+
+							}
+
+							if ((prefix == null && suffix == null))
+								writer.putInt(0);
+							else
+								writer.putInt(-1497023830);
+							if ((prefix != null && !i.isRandom()) || (prefix != null && i.isComplete()))
+								writer.putInt(prefix.getToken());
+							else
+								writer.putInt(0);
+							if ((suffix != null && !i.isRandom())  || (suffix != null && i.isComplete()))
+								writer.putInt(suffix.getToken());
+							else
+								writer.putInt(0);
+							writer.putString(i.getCustomName());
+						}
+					}
+
+					writer.putInt(0);
+					writer.putInt(0);
+					writer.putInt(1);
+					writer.putInt(0);
+					writer.putInt(3);
+					writer.putInt(3);
+					writer.putInt(0);
+					writer.putString("Repair items");
+					writer.putString("percent");
+					writer.putInt(npc.getRepairCost()); //cost for repair
+					writer.putInt(0);
+					//ArrayList<Integer> modSuffixList =
+					ArrayList<Integer> modPrefixList = npc.getModTypeTable();
+					Integer mod = modPrefixList.get(0);
+
+					if (mod != 0) {
+						writer.putInt(npc.getModTypeTable().size()); //Effects size
+						for (Integer mtp : npc.getModTypeTable()) {
+
+							Integer imt = modPrefixList.indexOf(mtp);
+							writer.putInt(npc.getItemModTable().get(imt)); //?
+							writer.putInt(0);
+							writer.putInt(0);
+							writer.putFloat(2);
+							writer.putInt(0);
+							writer.putInt(1);
+							writer.putInt(2);
+							writer.putInt(0);
+							writer.putInt(1);
+							writer.put(npc.getItemModTable().get(imt));
+							writer.put(npc.getItemModTable().get(imt));
+							writer.put(npc.getItemModTable().get(imt));
+							writer.put(npc.getItemModTable().get(imt));//writer.putInt(-916801465); effectItemType
+							writer.putInt(mtp); //prefix
+							Integer mts = modPrefixList.indexOf(mtp);
+							writer.putInt(npc.getModSuffixTable().get(mts)); //suffix
+						}
+					} else
+						writer.putInt(0);
+					ArrayList<Item> inventory = npc.getInventory();
+
+
+					writer.putInt(inventory.size()); //placeholder for item cnt
+
+
+
+					for (Item i : inventory) {
+
+						Item.serializeForClientMsgWithoutSlot(i,writer);
+					}
+
+
+					writer.putInt(0);
+					writer.putInt(5);
+					writer.putInt(1);
+					writer.putInt(2);
+					writer.putInt(15);
+					writer.putInt(3);
+					writer.putInt(18);
+
+					writer.putInt(0);
+					writer.putInt(0);
+					writer.putInt(0);
+					writer.putInt(0);
+				}
+
+			}else if (this.targetType == GameObjectType.Mob.ordinal()){
+
+				mobA = Mob.getFromCacheDBID(this.targetID);
+				if (mobA == null) {
+					Logger.error("Missing Mob of ID " + this.targetID);
+					return;
+				}
+
+				if (mobA != null){
+					Contract con = mobA.getContract();
+					if (con == null) {
+						Logger.error("Missing contract for NPC " + this.targetID);
+						return;
+					}
+
+					int maxSlots = 1;
+
+					switch (mobA.getRank()){
+					case 1:
+					case 2:
+						maxSlots = 1;
+						break;
+					case 3:
+						maxSlots = 2;
+						break;
+					case 4:
+					case 5:
+						maxSlots = 3;
+						break;
+					case 6:
+						maxSlots = 4;
+						break;
+					case 7:
+						maxSlots = 5;
+						break;
+					default:
+						maxSlots = 1;
+
+					}
+					writer.putInt(0); //anything other than 0 seems to mess up the client
+					writer.putInt(targetType);
+					writer.putInt(targetID);
+					writer.putInt(0); //static....
+					writer.putInt(0);//static....
+					writer.putInt(Blueprint.getNpcMaintCost(mobA.getRank()));  // salary
+
+					writer.putInt(Mob.getUpgradeCost(mobA));
+
+					if (mobA.isRanking() && mobA.getUpgradeDateTime().isAfter(DateTime.now()))
+						upgradePeriod = new Period(DateTime.now(), mobA.getUpgradeDateTime());
+					else
+						upgradePeriod = new Period(0);
+
+					writer.put((byte) upgradePeriod.getDays());    //for timer
+					writer.put((byte) unknown26);//unknown
+					writer.putInt(100); //unknown
+
+					writer.put((byte) upgradePeriod.getHours());    //for timer
+					writer.put((byte) upgradePeriod.getMinutes());  //for timer
+					writer.put((byte) upgradePeriod.getSeconds());  //for timer
+
+					if (mobA.isRanking() && mobA.getUpgradeDateTime().isAfter(DateTime.now()))
+						upgradePeriodInSeconds = Seconds.secondsBetween(DateTime.now(), mobA.getUpgradeDateTime()).getSeconds();
+					else
+						upgradePeriodInSeconds = 0;
+
+					writer.putInt(upgradePeriodInSeconds);
+
+
+					writer.put((byte) 0);
+					writer.put((byte) (mobA.getRank() == 7 ? 0 : 1));  //0 will make the upgrade field show "N/A"
+					writer.put((byte) 0);
+					writer.put((byte) 0);
+					writer.putInt(0);
+					writer.putInt(10000);  //no idea...
+					writer.put((byte) 0);
+					writer.put((byte) 0);
+					writer.put((byte) 0);
+					writer.putInt(0);
+
+
+					NPCProfits profit = NPCProfits.defaultProfits;
+					
+					writer.putInt((int) (profit.buyNormal * 100));
+					writer.putInt((int) (profit.buyGuild * 100));
+					writer.putInt((int) (profit.buyNation * 100));
+					writer.putInt((int) (profit.sellNormal * 100));
+					writer.putInt((int) (profit.sellGuild * 100));
+					writer.putInt((int) (profit.sellNation * 100));
+
+					writer.putInt(0); //vendor slots
+					writer.putInt(maxSlots); //artillery slots
+					writer.putInt(0); //runemaster slots
+
+					for (int i = 0; i < 13; i++) {
+						writer.putInt(0); //statics
+					}
+					//some unknown list
+					writer.putInt(1); //list count
+					writer.putInt(16);
+
+					writer.putInt(0); //static
+					writer.putInt(0); //static
+					writer.putInt(0); //runemaster list
+
+					//artillery captain list
+					ConcurrentHashMap<Mob, Integer> siegeMinions = mobA.getSiegeMinionMap();
+
+				
+						writer.putInt(siegeMinions.size() + 1);
+							serializeGuardList(writer, mobA.getContract().getContractID()); //Guard
+				
+					if (siegeMinions != null && siegeMinions.size() > 0)
+
+						for (Mob mob : siegeMinions.keySet()) {
+							this.unknown83 = mob.getObjectUUID();
+							writer.putInt(2);
+							writer.putInt(mob.getObjectType().ordinal());
+							writer.putInt(this.unknown83);
+							writer.putInt(0);
+							writer.putInt(10);
+							writer.putInt(0);
+							writer.putInt(1);
+							writer.putInt(1);
+							writer.put((byte) 0);
+							long curTime = System.currentTimeMillis() / 1000;
+							long upgradeTime = mob.getTimeToSpawnSiege() / 1000;
+							long timeLife = upgradeTime - curTime;
+
+							writer.putInt(900);
+							writer.putInt(900);
+							writer.putInt((int) timeLife); //time remaining?
+							writer.putInt(0);
+							writer.put((byte)0);
+							writer.putString(mob.getNameOverride().isEmpty() ? mob.getName() : mob.getNameOverride());
+							writer.put((byte) 0);
+						}
+
+				}
+
+
+			}
+
+		}
+		
+		}catch(Exception e){
+			e.printStackTrace();
+		}
+
+
+	}
+
+
+
+	//Serializes lists for Bulwarks
+	private static void serializeBulwarkList(ByteBufferWriter writer, int minion) {
+		int minionIndex;
+
+		if (minion < 1 || minion > 3)
+			minionIndex = 1;
+		else
+			minionIndex = minion;
+
+		writer.putInt(0);
+		for (int i = 0; i < 3; i++) {
+			writer.putInt(0); //static
+		}
+		writer.putInt(9);
+		writer.putInt(5);
+		writer.putInt(9);
+		writer.putInt(5);
+		writer.put((byte) 0);
+
+		writer.putInt((minion == 1) ? 900 : 600); //roll time
+		writer.putInt(0);
+		writer.putInt(0);
+		writer.putInt(0); //Array
+		writer.put((byte) 0);
+
+		if (minion == 1)
+			writer.putString("Trebuchet");
+		else if (minion == 2)
+			writer.putString("Ballista");
+		else
+			writer.putString("Mangonel");
+		writer.put((byte) 1);
+		writer.putString("A weapon suited to laying siege");
+	}
+
+	private static void serializeGuardList(ByteBufferWriter writer, int minion) {
+
+		writer.putInt(1);
+
+		for (int i = 0; i < 3; i++)
+			writer.putInt(0); //static
+
+		writer.putInt(minion);
+		writer.putInt(1);
+		writer.putInt(minion);
+		writer.putInt(1);
+		writer.put((byte) 0);
+
+		writer.putInt(600); //roll time
+		writer.putInt(0);
+		writer.putInt(0);
+		writer.putInt(0); //Array
+		writer.put((byte) 0);
+		
+		MinionType minionType = MinionType.ContractToMinionMap.get(minion);
+		writer.putString(minionType != null ? minionType.getRace() + " " + minionType.getName() : "Minion Guard");
+		writer.put((byte) 1);
+		writer.putString("A Guard To Protect Your City.");
+	}
+
+
+
+
+
+	public String getCityName() {
+		return CityName;
+	}
+
+
+	public void setUnknown07(int unknown07) {
+		this.unknown07 = unknown07;
+	}
+
+
+
+	public void setMotto(String motto) {
+		this.motto = motto;
+	}
+
+	public String getMotto() {
+		return motto;
+	}
+
+	public void setUnknown01(int unknown01) {
+		this.unknown01 = unknown01;
+	}
+
+	public int getUnknown01() {
+		return unknown01;
+	}
+
+	public void setUnknown03(int unknown03) {
+		this.unknown03 = unknown03;
+	}
+
+	public int getUnknown03() {
+		return unknown03;
+	}
+
+	public void setUnknown04(int unknown04) {
+		this.unknown04 = unknown04;
+	}
+
+	public int getUnknown04() {
+		return unknown04;
+	}
+
+	public void setUnknown05(int unknown05) {
+		this.unknown05 = unknown05;
+	}
+
+	public int getUnknown05() {
+		return unknown05;
+	}
+
+	public void setUnknown06(int unknown06) {
+		this.unknown06 = unknown06;
+	}
+
+	public int getUnknown06() {
+		return unknown06;
+	}
+
+	public int getBuyNormal() {
+		return buyNormal;
+	}
+
+	public void setBuyNormal(int buyNormal) {
+		this.buyNormal = buyNormal;
+	}
+
+	public int getBuyGuild() {
+		return buyGuild;
+	}
+
+	public void setBuyGuild(int buyGuild) {
+		this.buyGuild = buyGuild;
+	}
+
+	public int getBuyNation() {
+		return buyNation;
+	}
+
+	public void setBuyNation(int buyNation) {
+		this.buyNation = buyNation;
+	}
+
+	public int getSellNormal() {
+		return sellNormal;
+	}
+
+	public void setSellNormal(int sellNormal) {
+		this.sellNormal = sellNormal;
+	}
+
+	public int getSellGuild() {
+		return sellGuild;
+	}
+
+	public void setSellGuild(int sellGuild) {
+		this.sellGuild = sellGuild;
+	}
+
+	public int getSellNation() {
+		return sellNation;
+	}
+
+	public void setSellNation(int sellNation) {
+		this.sellNation = sellNation;
+	}
+
+	public int getMessageType() {
+		return messageType;
+	}
+
+	public void setMessageType(int messageType) {
+		this.messageType = messageType;
+	}
+
+	public int getBuildingID() {
+		return buildingID;
+	}
+
+	public void setBuildingID(int buildingID) {
+		this.buildingID = buildingID;
+	}
+
+	public int getUnknown20() {
+		return unknown20;
+	}
+
+	public void setUnknown20(int unknown20) {
+		this.unknown20 = unknown20;
+	}
+
+	public int getUnknown21() {
+		return unknown21;
+	}
+
+	public void setUnknown21(int unknown21) {
+		this.unknown21 = unknown21;
+	}
+
+	public int getUnknown22() {
+		return unknown22;
+	}
+
+	public void setUnknown22(int unknown22) {
+		this.unknown22 = unknown22;
+	}
+
+	public int getUnknown23() {
+		return unknown23;
+	}
+
+	public void setUnknown23(int unknown23) {
+		this.unknown23 = unknown23;
+	}
+
+	public int getUnknown24() {
+		return unknown24;
+	}
+
+	public void setUnknown24(int unknown24) {
+		this.unknown24 = unknown24;
+	}
+
+	public int getUnknown25() {
+		return unknown25;
+	}
+
+	public void setUnknown25(int unknown25) {
+		this.unknown25 = unknown25;
+	}
+
+	public int getUnknown26() {
+		return unknown26;
+	}
+
+	public void setUnknown26(int unknown26) {
+		this.unknown26 = unknown26;
+	}
+
+	public int getUnknown28() {
+		return unknown28;
+	}
+
+	public void setUnknown28(int unknown28) {
+		this.unknown28 = unknown28;
+	}
+
+	public int getUnknown30() {
+		return unknown30;
+	}
+
+	public void setUnknown30(int unknown30) {
+		this.unknown30 = unknown30;
+	}
+
+	public int getUnknown31() {
+		return unknown31;
+	}
+
+	public void setUnknown31(int unknown31) {
+		this.unknown31 = unknown31;
+	}
+
+	public int getUnknown32() {
+		return unknown32;
+	}
+
+	public void setUnknown32(int unknown32) {
+		this.unknown32 = unknown32;
+	}
+
+	public int getUnknown33() {
+		return unknown33;
+	}
+
+	public void setUnknown33(int unknown33) {
+		this.unknown33 = unknown33;
+	}
+
+	public int getUnknown34() {
+		return unknown34;
+	}
+
+	public void setUnknown34(int unknown34) {
+		this.unknown34 = unknown34;
+	}
+
+	public int getUnknown35() {
+		return unknown35;
+	}
+
+	public void setUnknown35(int unknown35) {
+		this.unknown35 = unknown35;
+	}
+
+	public int getUnknown36() {
+		return unknown36;
+	}
+
+	public void setUnknown36(int unknown36) {
+		this.unknown36 = unknown36;
+	}
+
+	public int getUnknown37() {
+		return unknown37;
+	}
+
+	public void setUnknown37(int unknown37) {
+		this.unknown37 = unknown37;
+	}
+
+	public int getUnknown38() {
+		return unknown38;
+	}
+
+	public void setUnknown38(int unknown38) {
+		this.unknown38 = unknown38;
+	}
+
+	public int getUnknown39() {
+		return unknown39;
+	}
+
+	public void setUnknown39(int unknown39) {
+		this.unknown39 = unknown39;
+	}
+
+	public int getUnknown40() {
+		return unknown40;
+	}
+
+	public void setUnknown40(int unknown40) {
+		this.unknown40 = unknown40;
+	}
+
+	public int getUnknown41() {
+		return unknown41;
+	}
+
+	public void setUnknown41(int unknown41) {
+		this.unknown41 = unknown41;
+	}
+
+	public int getUnknown42() {
+		return unknown42;
+	}
+
+	public void setUnknown42(int unknown42) {
+		this.unknown42 = unknown42;
+	}
+
+	public int getUnknown44() {
+		return unknown44;
+	}
+
+	public void setUnknown44(int unknown44) {
+		this.unknown44 = unknown44;
+	}
+
+	public int getUnknown43() {
+		return unknown43;
+	}
+
+	public void setUnknown43(int unknown43) {
+		this.unknown43 = unknown43;
+	}
+
+	public int getUnknown45() {
+		return unknown45;
+	}
+
+	public void setUnknown45(int unknown45) {
+		this.unknown45 = unknown45;
+	}
+
+	public int getUnknown46() {
+		return unknown46;
+	}
+
+	public void setUnknown46(int unknown46) {
+		this.unknown46 = unknown46;
+	}
+
+	public int getUnknown47() {
+		return unknown47;
+	}
+
+	public void setUnknown47(int unknown47) {
+		this.unknown47 = unknown47;
+	}
+
+	public int getUnknown48() {
+		return unknown48;
+	}
+
+	public void setUnknown48(int unknown48) {
+		this.unknown48 = unknown48;
+	}
+
+	public int getUnknown49() {
+		return unknown49;
+	}
+
+	public void setUnknown49(int unknown49) {
+		this.unknown49 = unknown49;
+	}
+
+	public int getUnknown50() {
+		return unknown50;
+	}
+
+	public void setUnknown50(int unknown50) {
+		this.unknown50 = unknown50;
+	}
+
+	public int getUnknown51() {
+		return unknown51;
+	}
+
+	public void setUnknown51(int unknown51) {
+		this.unknown51 = unknown51;
+	}
+
+	public int getUnknown52() {
+		return unknown52;
+	}
+
+	public void setUnknown52(int unknown52) {
+		this.unknown52 = unknown52;
+	}
+
+	public int getUnknown53() {
+		return unknown53;
+	}
+
+	public void setUnknown53(int unknown53) {
+		this.unknown53 = unknown53;
+	}
+
+	public int getUnknown54() {
+		return unknown54;
+	}
+
+	public void setUnknown54(int unknown54) {
+		this.unknown54 = unknown54;
+	}
+
+	public int getUnknown55() {
+		return unknown55;
+	}
+
+	public void setUnknown55(int unknown55) {
+		this.unknown55 = unknown55;
+	}
+
+	public int getUnknown56() {
+		return unknown56;
+	}
+
+	public void setUnknown56(int unknown56) {
+		this.unknown56 = unknown56;
+	}
+
+	public int getUnknown57() {
+		return unknown57;
+	}
+
+	public void setUnknown57(int unknown57) {
+		this.unknown57 = unknown57;
+	}
+
+	public int getUnknown58() {
+		return unknown58;
+	}
+
+	public void setUnknown58(int unknown58) {
+		this.unknown58 = unknown58;
+	}
+
+	public int getUnknown59() {
+		return unknown59;
+	}
+
+	public void setUnknown59(int unknown59) {
+		this.unknown59 = unknown59;
+	}
+
+	public int getUnknown60() {
+		return unknown60;
+	}
+
+	public void setUnknown60(int unknown60) {
+		this.unknown60 = unknown60;
+	}
+
+	public int getUnknown61() {
+		return unknown61;
+	}
+
+	public void setUnknown61(int unknown61) {
+		this.unknown61 = unknown61;
+	}
+
+	public int getUnknown62() {
+		return unknown62;
+	}
+
+	public void setUnknown62(int unknown62) {
+		this.unknown62 = unknown62;
+	}
+
+	public int getUnknown63() {
+		return unknown63;
+	}
+
+	public void setUnknown63(int unknown63) {
+		this.unknown63 = unknown63;
+	}
+
+	public int getUnknown64() {
+		return unknown64;
+	}
+
+	public void setUnknown64(int unknown64) {
+		this.unknown64 = unknown64;
+	}
+
+	public int getUnknown65() {
+		return unknown65;
+	}
+
+	public void setUnknown65(int unknown65) {
+		this.unknown65 = unknown65;
+	}
+
+	public int getUnknown66() {
+		return unknown66;
+	}
+
+	public void setUnknown66(int unknown66) {
+		this.unknown66 = unknown66;
+	}
+
+	public int getUnknown67() {
+		return unknown67;
+	}
+
+	public void setUnknown67(int unknown67) {
+		this.unknown67 = unknown67;
+	}
+
+	public int getUnknown68() {
+		return unknown68;
+	}
+
+	public void setUnknown68(int unknown68) {
+		this.unknown68 = unknown68;
+	}
+
+	public int getUnknown69() {
+		return unknown69;
+	}
+
+	public void setUnknown69(int unknown69) {
+		this.unknown69 = unknown69;
+	}
+
+	public int getUnknown70() {
+		return unknown70;
+	}
+
+	public void setUnknown70(int unknown70) {
+		this.unknown70 = unknown70;
+	}
+
+	public int getUnknown71() {
+		return unknown71;
+	}
+
+	public void setUnknown71(int unknown71) {
+		this.unknown71 = unknown71;
+	}
+
+	public int getUnknown72() {
+		return unknown72;
+	}
+
+	public void setUnknown72(int unknown72) {
+		this.unknown72 = unknown72;
+	}
+
+	public int getUnknown73() {
+		return unknown73;
+	}
+
+	public void setUnknown73(int unknown73) {
+		this.unknown73 = unknown73;
+	}
+
+	public int getUnknown74() {
+		return unknown74;
+	}
+
+	public void setUnknown74(int unknown74) {
+		this.unknown74 = unknown74;
+	}
+
+	public int getUnknown75() {
+		return unknown75;
+	}
+
+	public void setUnknown75(int unknown75) {
+		this.unknown75 = unknown75;
+	}
+
+	public int getUnknown76() {
+		return unknown76;
+	}
+
+	public void setUnknown76(int unknown76) {
+		this.unknown76 = unknown76;
+	}
+
+	public int getUnknown77() {
+		return unknown77;
+	}
+
+	public void setUnknown77(int unknown77) {
+		this.unknown77 = unknown77;
+	}
+
+	public int getUnknown78() {
+		return unknown78;
+	}
+
+	public void setUnknown78(int unknown78) {
+		this.unknown78 = unknown78;
+	}
+
+	public int getUnknown79() {
+		return unknown79;
+	}
+
+	public void setUnknown79(int unknown79) {
+		this.unknown79 = unknown79;
+	}
+
+	public int getTargetType() {
+		return targetType;
+	}
+
+	public void setTargetType(int targetType) {
+		this.targetType = targetType;
+	}
+
+	public int getTargetID() {
+		return targetID;
+	}
+
+	public void setTargetID(int targetID) {
+		this.targetID = targetID;
+	}
+
+
+
+}
diff --git a/src/engine/net/client/msg/MerchantMsg.java b/src/engine/net/client/msg/MerchantMsg.java
new file mode 100644
index 00000000..1510c493
--- /dev/null
+++ b/src/engine/net/client/msg/MerchantMsg.java
@@ -0,0 +1,228 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+
+
+public class MerchantMsg extends ClientNetMsg {
+
+	private int type;
+	private int unknown01;
+	private int unknown02;
+	private int unknown03;
+	private int npcType;
+	private int npcID;
+	private int cityType;
+	private int cityID;
+	private int teleportTime;
+	private int unknown04;
+	private int itemType;
+	private int itemID;
+	private int amount;
+	private int hashID;
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public MerchantMsg() {
+		super(Protocol.MERCHANT);
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public MerchantMsg(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.MERCHANT, origin, reader);
+	}
+
+	/**
+	 * Copy constructor
+	 */
+	public MerchantMsg(MerchantMsg msg) {
+		super(Protocol.MERCHANT);
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied ByteBufferReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		this.type = reader.getInt();
+		this.unknown01 = reader.getInt();
+		this.unknown02 = reader.getInt();
+		this.unknown03 = reader.getInt();
+		this.npcType = reader.getInt();
+		this.npcID = reader.getInt();
+		if (this.type == 11 || type == 13) {
+			this.cityType = reader.getInt();
+			this.cityID = reader.getInt();
+			this.teleportTime = reader.getInt();
+		}else if(this.type == 18){
+			this.itemType = reader.getInt();
+			this.itemID = reader.getInt();
+			this.amount = reader.getInt();
+		}else if (this.type == 17){
+			this.hashID = reader.getInt();
+			this.amount = reader.getInt();
+		}else if (this.type == 19){
+			this.hashID = reader.getInt();
+		}else {
+
+			this.cityType = 0;
+			this.cityID = 0;
+			this.teleportTime = 0;
+		}
+		this.unknown04 = reader.getInt();
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied ByteBufferWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+		writer.putInt(this.type);
+		writer.putInt(this.unknown01);
+		writer.putInt(this.unknown02);
+		writer.putInt(this.unknown03);
+		writer.putInt(this.npcType);
+		writer.putInt(this.npcID);
+		if (this.type == 11 || type == 13) {
+			writer.putInt(this.cityType);
+			writer.putInt(this.cityID);
+			writer.putInt(this.teleportTime);
+		}
+		writer.putInt(this.unknown04);
+		if (this.type == 5){
+			writer.putInt(2097253);
+			writer.putInt(0);
+		}
+
+	}
+
+	public int getType() {
+		return this.type;
+	}
+
+	public int getUnknown01() {
+		return this.unknown01;
+	}
+
+	public int getUnknown02() {
+		return this.unknown02;
+	}
+
+	public int getUnknown03() {
+		return this.unknown03;
+	}
+
+	public int getNPCType() {
+		return this.npcType;
+	}
+
+	public int getNPCID() {
+		return this.npcID;
+	}
+
+	public int getCityType() {
+		return this.cityType;
+	}
+
+	public int getCityID() {
+		return this.cityID;
+	}
+
+	public int getTeleportTime() {
+		return this.teleportTime;
+	}
+
+	public int getUnknown04() {
+		return this.unknown04;
+	}
+
+	public void setType(int value) {
+		this.type = value;
+	}
+
+	public void setUnknown01(int value) {
+		this.unknown01 = value;
+	}
+
+	public void setUnknown02(int value) {
+		this.unknown02 = value;
+	}
+
+	public void setUnknown03(int value) {
+		this.unknown03 = value;
+	}
+
+	public void setNPCType(int value) {
+		this.npcType = value;
+	}
+
+	public void setNPCID(int value) {
+		this.npcID = value;
+	}
+
+	public void setCityType(int value) {
+		this.cityType = value;
+	}
+
+	public void setCityID(int value) {
+		this.cityID = value;
+	}
+
+	public void setTeleportTime(int value) {
+		this.teleportTime = value;
+	}
+
+	public void setUnknown04(int value) {
+		this.unknown04 = value;
+	}
+
+	public int getItemType() {
+		return itemType;
+	}
+
+	public void setItemType(int itemType) {
+		this.itemType = itemType;
+	}
+
+	public int getItemID() {
+		return itemID;
+	}
+
+	public void setItemID(int itemID) {
+		this.itemID = itemID;
+	}
+
+	public int getAmount() {
+		return amount;
+	}
+
+	public void setAmount(int amount) {
+		this.amount = amount;
+	}
+
+	public int getHashID() {
+		return hashID;
+	}
+
+	public void setHashID(int hashID) {
+		this.hashID = hashID;
+	}
+}
diff --git a/src/engine/net/client/msg/MinionTrainingMessage.java b/src/engine/net/client/msg/MinionTrainingMessage.java
new file mode 100644
index 00000000..7de521e5
--- /dev/null
+++ b/src/engine/net/client/msg/MinionTrainingMessage.java
@@ -0,0 +1,195 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+
+import engine.exception.SerializationException;
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+
+public class MinionTrainingMessage extends ClientNetMsg {
+	private int npcID;
+	private int npcType;
+	private int buildingID;
+	private int buildingType;
+	private int type;
+	private int pad = 0;
+	private int objectType;
+	private int objectUUID;
+	private boolean isTreb = false;
+	private boolean isMangonal = false;
+	private boolean isBallista = false;
+	private int minion;
+	private int mobType;
+	private int mobID;
+	
+	
+	
+	
+	
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public MinionTrainingMessage(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.MINIONTRAINING, origin, reader);
+	}
+	
+	public MinionTrainingMessage() {
+		super(Protocol.MINIONTRAINING);
+	}
+	/**
+	 * Deserializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		this.type = reader.getInt();
+		if (this.type == 2){
+			this.buildingType = reader.getInt();
+			this.buildingID = reader.getInt();
+			this.npcType = reader.getInt();
+			this.npcID = reader.getInt();
+			this.objectType = reader.getInt();
+			this.objectUUID = reader.getInt();
+			reader.getInt();
+			reader.getInt();
+			
+		}else{
+			this.buildingType = reader.getInt();
+			this.buildingID = reader.getInt();
+			this.npcType = reader.getInt();
+			this.npcID = reader.getInt();
+		reader.getInt();
+		this.minion = reader.getInt();
+		if (this.minion == 1)
+			this.isTreb = true;
+		else if(this.minion == 2)
+			this.isBallista = true;
+		else if (this.minion == 3)
+			this.isMangonal = true;
+		reader.getInt();
+		reader.getInt();
+		}
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) throws SerializationException {
+		
+	}
+
+	public int getObjectType() {
+		return objectType;
+	}
+
+	public void setObjectType(int value) {
+		this.objectType = value;
+	}
+
+	public void setPad(int value) {
+		this.pad = value;
+	}
+
+	public int getUUID() {
+		return objectUUID;
+
+	}
+
+	public int getPad() {
+		return pad;
+	}
+	
+	public int getType() {
+		return type;
+	}
+	public void setType(int type) {
+		this.type = type;
+	}
+	public boolean isTreb() {
+		return isTreb;
+	}
+	public void setTreb(boolean isTreb) {
+		this.isTreb = isTreb;
+	}
+	public boolean isMangonal() {
+		return isMangonal;
+	}
+	public void setMangonal(boolean isMangonal) {
+		this.isMangonal = isMangonal;
+	}
+	public boolean isBallista() {
+		return isBallista;
+	}
+	public void setBallista(boolean isBallista) {
+		this.isBallista = isBallista;
+	}
+	public int getMinion() {
+		return minion;
+	}
+	public void setMinion(int minion) {
+		this.minion = minion;
+	}
+
+	public int getNpcID() {
+		return npcID;
+	}
+
+	public void setNpcID(int npcID) {
+		this.npcID = npcID;
+	}
+
+	public int getNpcType() {
+		return npcType;
+	}
+
+	public void setNpcType(int npcType) {
+		this.npcType = npcType;
+	}
+
+	public int getBuildingID() {
+		return buildingID;
+	}
+
+	public void setBuildingID(int buildingID) {
+		this.buildingID = buildingID;
+	}
+
+	public int getBuildingType() {
+		return buildingType;
+	}
+
+	public void setBuildingType(int buildingType) {
+		this.buildingType = buildingType;
+	}
+
+	public int getMobType() {
+		return mobType;
+	}
+
+	public void setMobType(int mobType) {
+		this.mobType = mobType;
+	}
+
+	public int getMobID() {
+		return mobID;
+	}
+
+	public void setMobID(int mobID) {
+		this.mobID = mobID;
+	}
+
+}
diff --git a/src/engine/net/client/msg/ModifyCommitToTradeMsg.java b/src/engine/net/client/msg/ModifyCommitToTradeMsg.java
new file mode 100644
index 00000000..9e2c1b06
--- /dev/null
+++ b/src/engine/net/client/msg/ModifyCommitToTradeMsg.java
@@ -0,0 +1,145 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+
+import engine.exception.SerializationException;
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.objects.AbstractGameObject;
+
+/**
+ * Commit to trade
+ *
+ * @author Eighty
+ */
+public class ModifyCommitToTradeMsg extends ClientNetMsg {
+
+	private int playerType;
+	private int playerID;
+	private int targetType;
+	private int targetID;
+	private byte commit1;
+	private byte commit2;
+
+	/**
+	 * This is the general purpose constructor
+	 */
+	public ModifyCommitToTradeMsg(AbstractGameObject player, AbstractGameObject target, byte commit1, byte commit2) {
+		super(Protocol.TRADECONFIRMSTATUS);
+        this.playerType = player.getObjectType().ordinal();
+        this.playerID = player.getObjectUUID();
+        this.targetType = target.getObjectType().ordinal();
+        this.targetID = target.getObjectUUID();
+        this.commit1 = commit1;
+		this.commit2 = commit2;
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the ByteBuffer into a message. If a BufferUnderflow occurs (based on reading past the limit) then this constructor Throws that Exception to the caller.
+	 */
+	public ModifyCommitToTradeMsg(AbstractConnection origin, ByteBufferReader reader)
+			 {
+		super(Protocol.TRADECONFIRMSTATUS, origin, reader);
+	}
+
+	/**
+	 * Deserializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)
+			 {
+		this.playerType = reader.getInt();
+		this.playerID = reader.getInt();
+		this.targetType = reader.getInt();
+		this.targetID = reader.getInt();
+		commit1 = reader.get();
+		commit2 = reader.get();
+	}
+
+	/**
+	 * Serializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer)
+			throws SerializationException {
+		writer.putInt(playerType);
+		writer.putInt(playerID);
+		writer.putInt(targetType);
+		writer.putInt(targetID);
+		writer.put(commit1);
+		writer.put(commit2);
+	}
+
+	
+
+	/**
+	 * @return the commit1
+	 */
+	public byte getCommit1() {
+		return commit1;
+	}
+
+	/**
+	 * @param commit1 the commit1 to set
+	 */
+	public void setCommit1(byte commit1) {
+		this.commit1 = commit1;
+	}
+
+	/**
+	 * @return the commit2
+	 */
+	public byte getCommit2() {
+		return commit2;
+	}
+
+	/**
+	 * @param commit2 the commit2 to set
+	 */
+	public void setCommit2(byte commit2) {
+		this.commit2 = commit2;
+	}
+
+	public int getPlayerType() {
+		return playerType;
+	}
+
+	public void setPlayerType(int playerType) {
+		this.playerType = playerType;
+	}
+
+	public int getPlayerID() {
+		return playerID;
+	}
+
+	public void setPlayerID(int playerID) {
+		this.playerID = playerID;
+	}
+
+	public int getTargetType() {
+		return targetType;
+	}
+
+	public void setTargetType(int targetType) {
+		this.targetType = targetType;
+	}
+
+	public int getTargetID() {
+		return targetID;
+	}
+
+	public void setTargetID(int targetID) {
+		this.targetID = targetID;
+	}
+
+}
diff --git a/src/engine/net/client/msg/ModifyHealthKillMsg.java b/src/engine/net/client/msg/ModifyHealthKillMsg.java
new file mode 100644
index 00000000..4e0e74dd
--- /dev/null
+++ b/src/engine/net/client/msg/ModifyHealthKillMsg.java
@@ -0,0 +1,236 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.objects.AbstractCharacter;
+
+public class ModifyHealthKillMsg extends ClientNetMsg {
+
+	private int trains;
+	private int unknownID; //effectID
+	private int sourceType;
+	private int sourceID;
+	private int targetType;
+	private int targetID;
+	private int unknown02 = 0; //1 heal, 0 hurt?
+	private int unknown03 = 0; //0=normalCast, 1to4=powerFailed, 5=targetIsImmune, 6=targetResisted
+	private int unknown04 = -1;
+	private int unknown05 = 0;
+	private byte unknownByte = (byte) 0; //0
+	private int powerID;
+	private String powerName;
+	private float health;
+	private float healthMod;
+	private float mana;
+	private float manaMod;
+	private float stamina;
+	private float staminaMod;
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+
+	public ModifyHealthKillMsg(AbstractCharacter source, AbstractCharacter target, float healthMod, float manaMod, float staminaMod, int powerID, String powerName, int trains, int effectID) {
+		super(Protocol.POWERACTIONDDDIE);
+		if (source != null) {
+			this.sourceType = source.getObjectType().ordinal();
+			this.sourceID = source.getObjectUUID();
+		} else {
+			this.sourceType = 0;
+			this.sourceID = 0;
+		}
+		if (target != null) {
+			this.targetType = target.getObjectType().ordinal();
+			this.targetID = target.getObjectUUID();
+			this.health = target.getCurrentHitpoints();
+			this.healthMod = healthMod;
+			this.mana = target.getMana();
+			this.manaMod = manaMod;
+			this.stamina = target.getStamina();
+			this.staminaMod = staminaMod;
+		} else {
+			this.targetType = 0;
+			this.targetID = 0;
+			this.health = 0;
+			this.healthMod = 0;
+			this.mana = 0;
+			this.manaMod = 0;
+			this.stamina = 0;
+			this.staminaMod = 0;
+		}
+		this.unknownID = effectID;
+		this.trains = trains;
+		this.powerID = powerID;
+		this.powerName = powerName;
+
+		this.unknown02 = 0;
+	}
+
+	//called for kills
+	public ModifyHealthKillMsg(AbstractCharacter source, AbstractCharacter target, int powerID, String powerName, int trains, int effectID) {
+		super(Protocol.POWERACTIONDDDIE);
+		if (source != null) {
+			this.sourceType = source.getObjectType().ordinal();
+			this.sourceID = source.getObjectUUID();
+		} else {
+			this.sourceType = 0;
+			this.sourceID = 0;
+		}
+		if (target != null) {
+			this.targetType = target.getObjectType().ordinal();
+			this.targetID = target.getObjectUUID();
+			this.mana = target.getMana();
+			this.stamina = target.getStamina();
+		} else {
+			this.targetType = 0;
+			this.targetID = 0;
+			this.mana = 0f;
+			this.stamina = 0f;
+		}
+		this.health = -50f;
+		this.healthMod = 0f;
+		this.manaMod = 0f;
+		this.staminaMod = 0f;
+		this.unknown02 = 0;
+		this.unknownID = effectID;
+		this.trains = trains;
+		this.powerID = powerID;
+		this.powerName = powerName;
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the ByteBuffer into a message. If a BufferUnderflow occurs (based on reading past the limit) then this constructor Throws that Exception to the caller.
+	 */
+	public ModifyHealthKillMsg(AbstractConnection origin, ByteBufferReader reader)
+			 {
+		super(Protocol.POWERACTIONDDDIE, origin, reader);
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+		writer.putInt(this.trains);
+		writer.putInt(this.unknownID);
+		writer.putInt(this.sourceType);
+		writer.putInt(this.sourceID);
+		writer.putInt(this.targetType);
+		writer.putInt(this.targetID);
+		writer.putInt(this.unknown02);
+		writer.putInt(this.unknown03);
+		writer.putInt(this.unknown04);
+		writer.putInt(this.unknown05);
+		writer.put(this.unknownByte);
+		writer.putInt(this.powerID);
+		writer.putString(this.powerName);
+		writer.putFloat(this.health);
+		writer.putFloat(this.healthMod);
+		writer.putFloat(this.mana);
+		writer.putFloat(this.manaMod);
+		writer.putFloat(this.stamina);
+		writer.putFloat(this.staminaMod);
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		this.trains = reader.getInt();
+		this.unknownID = reader.getInt();
+		this.sourceType = reader.getInt();
+		this.sourceID = reader.getInt();
+		this.targetType = reader.getInt();
+		this.targetID = reader.getInt();
+		this.unknown02 = reader.getInt();
+		this.unknown03 = reader.getInt();
+		this.unknown04 = reader.getInt();
+		this.unknown05 = reader.getInt();
+		this.unknownByte = reader.get();
+		this.powerID = reader.getInt();
+		this.powerName = reader.getString();
+		this.health = reader.getFloat();
+		this.healthMod = reader.getFloat();
+		this.mana = reader.getFloat();
+		this.manaMod = reader.getFloat();
+		this.stamina = reader.getFloat();
+		this.staminaMod = reader.getFloat();
+	}
+
+	/**
+	 * @return the sourceType
+	 */
+	public int getSourceType() {
+		return sourceType;
+	}
+
+	/**
+	 * @return the sourceID
+	 */
+	public int getSourceID() {
+		return sourceID;
+	}
+
+	/**
+	 * @return the targetType
+	 */
+	public int getTargetType() {
+		return targetType;
+	}
+
+	public float getHealthMod() {
+		return healthMod;
+	}
+
+	public float getManaMod() {
+		return manaMod;
+	}
+
+	public float getStaminaMod() {
+		return manaMod;
+	}
+
+	/**
+	 * @return the targetID
+	 */
+	public int getTargetID() {
+		return targetID;
+	}
+
+	public void setSourceType(int value) {
+		this.sourceType = value;
+	}
+
+	public void setSourceID(int value) {
+		this.sourceID = value;
+	}
+
+	public void setTargetType(int value) {
+		this.targetType = value;
+	}
+
+	public void setTargetID(int value) {
+		this.targetID = value;
+	}
+
+	public void setUnknown02(int value) {
+		this.unknown02 = value;
+	}
+
+	public void setUnknown03(int value) {
+		this.unknown03 = value;
+	}
+}
diff --git a/src/engine/net/client/msg/ModifyHealthMsg.java b/src/engine/net/client/msg/ModifyHealthMsg.java
new file mode 100644
index 00000000..77d8d176
--- /dev/null
+++ b/src/engine/net/client/msg/ModifyHealthMsg.java
@@ -0,0 +1,272 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.objects.AbstractCharacter;
+import engine.objects.Building;
+
+public class ModifyHealthMsg extends ClientNetMsg {
+
+	private int trains;
+	private int unknownID; //effectID
+	private int sourceType;
+	private int sourceID;
+	private int targetType;
+	private int targetID;
+	private int omitFromChat = 0; //1 heal, 0 hurt?
+	private int unknown03 = 0; //0=normalCast, 1to4=powerFailed, 5=targetIsImmune, 6=targetResisted
+	private int unknown04 = -1;
+	private int unknown05 = 0;
+	private byte unknownByte = (byte) 0; //0
+	private int powerID;
+	private String powerName;
+	private float health;
+	private float healthMod;
+	private float mana;
+	private float manaMod;
+	private float stamina;
+	private float staminaMod;
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public ModifyHealthMsg(AbstractCharacter source, Building target, float healthMod, float manaMod, float staminaMod, int powerID, String powerName, int trains, int effectID) {
+		super(Protocol.POWERACTIONDD);
+		if (source != null) {
+			this.sourceType = source.getObjectType().ordinal();
+			this.sourceID = source.getObjectUUID();
+		} else {
+			this.sourceType = 0;
+			this.sourceID = 0;
+		}
+		if (target != null) {
+			this.targetType = target.getObjectType().ordinal();
+			this.targetID = target.getObjectUUID();
+			this.health = target.getCurrentHitpoints();
+			this.healthMod = healthMod;
+			this.mana = 0;
+			this.manaMod = 0;
+			this.stamina = 0;
+			this.staminaMod = 0;
+		} else {
+			this.targetType = 0;
+			this.targetID = 0;
+			this.health = 0;
+			this.healthMod = 0;
+			this.mana = 0;
+			this.manaMod = 0;
+			this.stamina = 0;
+			this.staminaMod = 0;
+		}
+		this.unknownID = effectID;
+		this.trains = trains;
+		this.powerID = powerID;
+		this.powerName = powerName;
+
+		this.omitFromChat = 0;
+	}
+
+	public ModifyHealthMsg(AbstractCharacter source, AbstractCharacter target, float healthMod, float manaMod, float staminaMod, int powerID, String powerName, int trains, int effectID) {
+		super(Protocol.POWERACTIONDD);
+		if (source != null) {
+			this.sourceType = source.getObjectType().ordinal();
+			this.sourceID = source.getObjectUUID();
+		} else {
+			this.sourceType = 0;
+			this.sourceID = 0;
+		}
+		if (target != null) {
+			this.targetType = target.getObjectType().ordinal();
+			this.targetID = target.getObjectUUID();
+			this.health = target.getCurrentHitpoints();
+			this.healthMod = healthMod;
+			this.mana = target.getMana();
+			this.manaMod = manaMod;
+			this.stamina = target.getStamina();
+			this.staminaMod = staminaMod;
+		} else {
+			this.targetType = 0;
+			this.targetID = 0;
+			this.health = 0;
+			this.healthMod = 0;
+			this.mana = 0;
+			this.manaMod = 0;
+			this.stamina = 0;
+			this.staminaMod = 0;
+		}
+		this.unknownID = effectID;
+		this.trains = trains;
+		this.powerID = powerID;
+		this.powerName = powerName;
+
+		this.omitFromChat = 0;
+	}
+
+	//called for kills
+	public ModifyHealthMsg(AbstractCharacter source, AbstractCharacter target, int powerID, String powerName, int trains, int effectID) {
+		super(Protocol.POWERACTIONDD);
+		if (source != null) {
+			this.sourceType = source.getObjectType().ordinal();
+			this.sourceID = source.getObjectUUID();
+		} else {
+			this.sourceType = 0;
+			this.sourceID = 0;
+		}
+		if (target != null) {
+			this.targetType = target.getObjectType().ordinal();
+			this.targetID = target.getObjectUUID();
+			this.mana = target.getMana();
+			this.stamina = target.getStamina();
+		} else {
+			this.targetType = 0;
+			this.targetID = 0;
+			this.mana = 0f;
+			this.stamina = 0f;
+		}
+		this.health = -50f;
+		this.healthMod = 0f;
+		this.manaMod = 0f;
+		this.staminaMod = 0f;
+		this.omitFromChat = 0;
+		this.unknownID = effectID;
+		this.trains = trains;
+		this.powerID = powerID;
+		this.powerName = powerName;
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the ByteBuffer into a message. If a BufferUnderflow occurs (based on reading past the limit) then this constructor Throws that Exception to the caller.
+	 */
+	public ModifyHealthMsg(AbstractConnection origin, ByteBufferReader reader)
+			 {
+		super(Protocol.POWERACTIONDD, origin, reader);
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+		writer.putInt(this.trains);
+		writer.putInt(this.unknownID);
+		writer.putInt(this.sourceType);
+		writer.putInt(this.sourceID);
+		writer.putInt(this.targetType);
+		writer.putInt(this.targetID);
+		writer.putInt(this.omitFromChat);
+		writer.putInt(this.unknown03);
+		writer.putInt(this.unknown04);
+		writer.putInt(this.unknown05);
+		writer.put(this.unknownByte);
+		writer.putInt(this.powerID);
+		writer.putString(this.powerName);
+		writer.putFloat(this.health);
+		writer.putFloat(this.healthMod);
+		writer.putFloat(this.mana);
+		writer.putFloat(this.manaMod);
+		writer.putFloat(this.stamina);
+		writer.putFloat(this.staminaMod);
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		this.trains = reader.getInt();
+		this.unknownID = reader.getInt();
+		this.sourceType = reader.getInt();
+		this.sourceID = reader.getInt();
+		this.targetType = reader.getInt();
+		this.targetID = reader.getInt();
+		this.omitFromChat = reader.getInt();
+		this.unknown03 = reader.getInt();
+		this.unknown04 = reader.getInt();
+		this.unknown05 = reader.getInt();
+		this.unknownByte = reader.get();
+		this.powerID = reader.getInt();
+		this.powerName = reader.getString();
+		this.health = reader.getFloat();
+		this.healthMod = reader.getFloat();
+		this.mana = reader.getFloat();
+		this.manaMod = reader.getFloat();
+		this.stamina = reader.getFloat();
+		this.staminaMod = reader.getFloat();
+	}
+
+	/**
+	 * @return the sourceType
+	 */
+	public int getSourceType() {
+		return sourceType;
+	}
+
+	/**
+	 * @return the sourceID
+	 */
+	public int getSourceID() {
+		return sourceID;
+	}
+
+	/**
+	 * @return the targetType
+	 */
+	public int getTargetType() {
+		return targetType;
+	}
+
+	public float getHealthMod() {
+		return healthMod;
+	}
+
+	public float getManaMod() {
+		return manaMod;
+	}
+
+	public float getStaminaMod() {
+		return manaMod;
+	}
+
+	/**
+	 * @return the targetID
+	 */
+	public int getTargetID() {
+		return targetID;
+	}
+
+	public void setSourceType(int value) {
+		this.sourceType = value;
+	}
+
+	public void setSourceID(int value) {
+		this.sourceID = value;
+	}
+
+	public void setTargetType(int value) {
+		this.targetType = value;
+	}
+
+	public void setTargetID(int value) {
+		this.targetID = value;
+	}
+
+	public void setOmitFromChat(int value) {
+		this.omitFromChat = value;
+	}
+
+	public void setUnknown03(int value) {
+		this.unknown03 = value;
+	}
+}
diff --git a/src/engine/net/client/msg/ModifyStatMsg.java b/src/engine/net/client/msg/ModifyStatMsg.java
new file mode 100644
index 00000000..7b5909ca
--- /dev/null
+++ b/src/engine/net/client/msg/ModifyStatMsg.java
@@ -0,0 +1,91 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+
+public class ModifyStatMsg extends ClientNetMsg {
+
+	private int amount;
+	private int type;
+	private int unknown01;
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public ModifyStatMsg() {
+		super(Protocol.RAISEATTR);
+	}
+
+	public ModifyStatMsg(int amount, int type, int unknown01) {
+		super(Protocol.RAISEATTR);
+		this.amount = amount;
+		this.type = type;
+		this.unknown01 = unknown01;
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public ModifyStatMsg(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.RAISEATTR, origin, reader);
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+		writer.putInt(this.amount);
+		writer.putInt(this.type);
+		writer.putInt(this.unknown01);
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		this.amount = reader.getInt();
+		this.type = reader.getInt();
+		this.unknown01 = reader.getInt();
+	}
+
+	public int getAmount() {
+		return this.amount;
+	}
+
+	public int getType() {
+		return this.type;
+	}
+
+	public int getUnknown01() {
+		return this.unknown01;
+	}
+
+	public void setAmount(int value) {
+		this.amount = value;
+	}
+
+	public void setType(int value) {
+		this.type = value;
+	}
+
+	public void setUnknown01(int value) {
+		this.unknown01 = value;
+	}
+}
diff --git a/src/engine/net/client/msg/MoveCorrectionMsg.java b/src/engine/net/client/msg/MoveCorrectionMsg.java
new file mode 100644
index 00000000..e527df67
--- /dev/null
+++ b/src/engine/net/client/msg/MoveCorrectionMsg.java
@@ -0,0 +1,226 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+
+import engine.math.Vector3fImmutable;
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.objects.AbstractCharacter;
+
+public class MoveCorrectionMsg extends ClientNetMsg {
+
+	private int sourceType;
+	private int sourceID;
+	private float startLat;
+	private float startAlt;
+	private float startLon;
+	private float endLat;
+	private float endAlt;
+	private float endLon;
+	private int unknown01 = 2;
+	private int unknown02 = 0;
+	private int unknown03 = 0;
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public MoveCorrectionMsg(AbstractCharacter ac, boolean teleport) {
+		super(Protocol.MOVECORRECTION);
+		this.sourceType = ac.getObjectType().ordinal();
+		this.sourceID = ac.getObjectUUID();
+		this.startLat = ac.getLoc().x;
+		this.startAlt = ac.getLoc().y;
+		this.startLon = ac.getLoc().z;
+		if (teleport){
+			this.endLat = ac.getLoc().x;
+			this.endAlt = ac.getLoc().y;
+			this.endLon = ac.getLoc().z;
+		}else{
+			if (ac.isMoving()){
+				this.endLat = ac.getEndLoc().x;
+				this.endAlt = ac.getEndLoc().y;
+				this.endLon = ac.getEndLoc().z;
+			}else{
+				this.endLat = ac.getLoc().x;
+				this.endAlt = ac.getLoc().y;
+				this.endLon = ac.getLoc().z;
+			}
+		}
+
+		this.unknown01 = Float.floatToIntBits(ac.getAltitude());
+		this.unknown02 =Float.floatToIntBits(ac.getAltitude());
+		this.unknown03 = Float.floatToIntBits(ac.getAltitude());
+
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public MoveCorrectionMsg(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.MOVECORRECTION, origin, reader);
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+		writer.putInt(this.sourceType);
+		writer.putInt(this.sourceID);
+
+		writer.putFloat(this.startLat);
+		writer.putFloat(this.startAlt);
+		writer.putFloat(this.startLon);
+
+		writer.putFloat(this.endLat);
+		writer.putFloat(this.endAlt);
+		writer.putFloat(this.endLon);
+		writer.putInt(this.unknown01);
+		writer.putInt(this.unknown02);
+		writer.putInt(this.unknown03);
+
+
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		this.sourceType = reader.getInt();
+		this.sourceID = reader.getInt();
+
+		this.startLat = reader.getFloat();
+		this.startAlt = reader.getFloat();
+		this.startLon = reader.getFloat();
+
+		this.endLat = reader.getFloat();
+		this.endAlt = reader.getFloat();
+		this.endLon = reader.getFloat();
+
+		this.unknown01 = reader.getInt();
+		this.unknown02 = reader.getInt();
+		this.unknown03 = reader.getInt();
+
+	}
+
+	public int getSourceType() {
+		return this.sourceType;
+	}
+
+	public int getSourceID() {
+		return this.sourceID;
+	}
+
+	public float getStartLat() {
+		return this.startLat;
+	}
+
+	public float getStartLon() {
+		return this.startLon;
+	}
+
+	public float getStartAlt() {
+		return this.startAlt;
+	}
+
+	public float getEndLat() {
+		return this.endLat;
+	}
+
+	public float getEndLon() {
+		return this.endLon;
+	}
+
+	public float getEndAlt() {
+		return this.endAlt;
+	}
+
+	public int getUnknown01() {
+		return this.unknown01;
+	}
+
+	public int getUnknown02() {
+		return this.unknown01;
+	}
+
+	public int getUnknown03() {
+		return this.unknown01;
+	}
+
+	public void setSourceType(int value) {
+		this.sourceType = value;
+	}
+
+	public void setSourceID(int value) {
+		this.sourceID = value;
+	}
+
+	public void setStartLat(float value) {
+		this.startLat = value;
+	}
+
+	public void setStartLon(float value) {
+		this.startLon = value;
+	}
+
+	public void setStartAlt(float value) {
+		this.startAlt = value;
+	}
+
+	public void setStartCoord(Vector3fImmutable value) {
+		this.startLat = value.x;
+		this.startAlt = value.y;
+		this.startLon = value.z;
+	}
+
+	public void setEndLat(float value) {
+		this.endLat = value;
+	}
+
+	public void setEndLon(float value) {
+		this.endLon = value;
+	}
+
+	public void setEndAlt(float value) {
+		this.endAlt = value;
+	}
+
+	public void setEndCoord(Vector3fImmutable value) {
+		this.endLat = value.x;
+		this.endAlt = value.y;
+		this.endLon = value.z;
+	}
+
+	public void setUnknown01(int value) {
+		this.unknown01 = value;
+	}
+
+	public void setUnknown02(int value) {
+		this.unknown02 = value;
+	}
+
+	public void setUnknown03(int value) {
+		this.unknown03 = value;
+	}
+
+	public void setPlayer(AbstractCharacter ac) {
+		this.sourceType = 85;
+		this.sourceID = ac.getObjectUUID();
+		this.setStartCoord(ac.getLoc());
+		this.setEndCoord(ac.getEndLoc());
+	}
+}
diff --git a/src/engine/net/client/msg/MoveToPointMsg.java b/src/engine/net/client/msg/MoveToPointMsg.java
new file mode 100644
index 00000000..4a22fa01
--- /dev/null
+++ b/src/engine/net/client/msg/MoveToPointMsg.java
@@ -0,0 +1,305 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+import engine.Enum.GameObjectType;
+import engine.gameManager.ZoneManager;
+import engine.math.Vector3fImmutable;
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.objects.AbstractCharacter;
+import engine.objects.Building;
+
+public class MoveToPointMsg extends ClientNetMsg {
+
+	private int sourceType;
+	private int sourceID;
+	private float startLat;
+	private float startLon;
+	private float startAlt;
+	private float endLat;
+	private float endLon;
+	private float endAlt;
+	private int targetType;
+	private int targetID;
+	private int inBuilding; // 0=true, -1=false 0/1/2 = floor you are on
+	private int unknown01;
+	private byte unknown02;
+	private byte unknown03;
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public MoveToPointMsg() {
+		super(Protocol.MOVETOPOINT);
+	}
+
+
+
+	public MoveToPointMsg(MoveToPointMsg msg) {
+		super(Protocol.MOVETOPOINT);
+		this.sourceType = msg.sourceType;
+		this.sourceID = msg.sourceID;
+		this.startLat = msg.startLat;
+		this.startLon = msg.startLon;
+		this.startAlt = msg.startAlt;
+		this.endLat = msg.endLat;
+		this.endLon = msg.endLon;
+		this.endAlt = msg.endAlt;
+		this.targetType = msg.targetType;
+		this.targetID = msg.targetID;
+		this.inBuilding = msg.inBuilding;
+		this.unknown01 = msg.unknown01;
+		this.unknown02 = msg.unknown02;
+		this.unknown03 = msg.unknown03;
+	}
+	//Moving Furniture out of building to unload properly. //for outside regions only
+	public MoveToPointMsg(Building building) {
+		super(Protocol.MOVETOPOINT);
+		this.sourceType = building.getObjectType().ordinal();
+		this.sourceID = building.getObjectUUID();
+		this.startLat = building.getLoc().x;
+		this.startLon = building.getLoc().z;
+		this.startAlt = building.getLoc().y;
+		this.endLat = building.getLoc().x;
+		this.endLon = building.getLoc().z;
+		this.endAlt = building.getLoc().y;
+		this.targetType = 0;
+		this.targetID = 0;
+		this.inBuilding = -1;
+		this.unknown01 = -1;
+		this.unknown02 = 0;
+		this.unknown03 = 0;
+	}
+
+
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public MoveToPointMsg(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.MOVETOPOINT, origin, reader);
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+		writer.putInt(this.sourceType);
+		writer.putInt(this.sourceID);
+
+		writer.putFloat(this.startLat);
+		writer.putFloat(this.startAlt);
+		writer.putFloat(this.startLon);
+
+		writer.putFloat(this.endLat);
+		writer.putFloat(this.endAlt);
+		writer.putFloat(this.endLon);
+
+		writer.putInt(this.targetType);
+		writer.putInt(this.targetID);
+
+		writer.putInt(this.inBuilding);
+		writer.putInt(this.unknown01);
+
+		writer.put((byte)0);
+		writer.put((byte)0);
+
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		this.sourceType = reader.getInt();
+		this.sourceID = reader.getInt();
+
+		this.startLat = reader.getFloat();
+		this.startAlt = reader.getFloat();
+		this.startLon = reader.getFloat();
+
+		this.endLat = reader.getFloat();
+		this.endAlt = reader.getFloat();
+		this.endLon = reader.getFloat();
+
+		this.targetType = reader.getInt();
+		this.targetID = reader.getInt();
+
+		this.inBuilding = reader.getInt();
+		this.unknown01 = reader.getInt();
+
+		this.unknown02 = reader.get();
+		this.unknown03 = reader.get();
+	}
+
+	public int getSourceType() {
+		return this.sourceType;
+	}
+
+	public int getSourceID() {
+		return this.sourceID;
+	}
+
+	public float getStartLat() {
+		return this.startLat;
+	}
+
+	public float getStartLon() {
+		return this.startLon;
+	}
+
+	public float getStartAlt() {
+		return this.startAlt;
+	}
+
+	public float getEndLat() {
+		return this.endLat;
+	}
+
+	public float getEndLon() {
+		return this.endLon;
+	}
+
+	public float getEndAlt() {
+		return this.endAlt;
+	}
+
+	public int getTargetType() {
+		return this.targetType;
+	}
+
+	public int getTargetID() {
+		return this.targetID;
+	}
+
+	public int getInBuilding() {
+		return this.inBuilding;
+	}
+
+	public int getUnknown01() {
+		return this.unknown01;
+	}
+
+	public void setSourceType(int value) {
+		this.sourceType = value;
+	}
+
+	public void setSourceID(int value) {
+		this.sourceID = value;
+	}
+
+	public void setStartLat(float value) {
+		this.startLat = value;
+	}
+
+	public void setStartLon(float value) {
+		this.startLon = value;
+	}
+
+	public void setStartAlt(float value) {
+		this.startAlt = value;
+	}
+
+	public void setStartCoord(Vector3fImmutable value) {
+		this.startLat = value.x;
+		this.startAlt = value.y;
+		this.startLon = value.z;
+	}
+
+	public void setEndLat(float value) {
+		this.endLat = value;
+	}
+
+	public void setEndLon(float value) {
+		this.endLon = value;
+	}
+
+	public void setEndAlt(float value) {
+		this.endAlt = value;
+	}
+
+	public void setEndCoord(Vector3fImmutable value) {
+		this.endLat = value.x;
+		this.endAlt = value.y;
+		this.endLon = value.z;
+	}
+
+	public void setTargetType(int value) {
+		this.targetType = value;
+	}
+
+	public void setTargetID(int value) {
+		this.targetID = value;
+	}
+
+	public void clearTarget() {
+		this.targetType = 0;
+		this.targetID = 0;
+	}
+
+	public void setInBuilding(int value) {
+		this.inBuilding = value;
+	}
+
+	public void setUnknown01(int value) {
+		this.unknown01 = value;
+	}
+
+	public void setPlayer(AbstractCharacter ac) {
+		this.sourceType = ac.getObjectType().ordinal();
+		this.sourceID = ac.getObjectUUID();
+		this.setStartCoord(ac.getLoc());
+		this.setEndCoord(ac.getEndLoc());
+		this.targetType = 0;
+		this.targetID = 0;
+		this.inBuilding = ac.getInBuilding();
+		this.unknown01 = ac.getInFloorID();
+
+	}
+	
+	public void setTarget(AbstractCharacter ac, Building target){
+		if (target == null){
+			this.setStartCoord(ac.getLoc());
+			this.setEndCoord(ac.getEndLoc());
+			this.targetType = 0;
+			this.targetID = 0;
+			this.inBuilding = -1;
+			this.unknown01 = -1;
+		}else{
+			Vector3fImmutable convertLocStart = ZoneManager.convertWorldToLocal(target, ac.getLoc());
+			Vector3fImmutable convertLocEnd = convertLocStart;
+			if (ac.isMoving())
+				convertLocEnd = ZoneManager.convertWorldToLocal(target, ac.getEndLoc());
+			
+			this.setStartCoord(convertLocStart);
+			this.setEndCoord(convertLocEnd);
+			this.targetType = GameObjectType.Building.ordinal();
+			this.targetID = target.getObjectUUID();
+			this.inBuilding = ac.getInBuilding();
+			this.unknown01 = ac.getInFloorID();
+		}
+		
+	}
+
+	public int getUnknown03() {
+		return unknown03;
+	}
+
+	public int getUnknown02() {
+		return unknown02;
+	}
+}
diff --git a/src/engine/net/client/msg/ObjectActionMsg.java b/src/engine/net/client/msg/ObjectActionMsg.java
new file mode 100644
index 00000000..c1a66dcd
--- /dev/null
+++ b/src/engine/net/client/msg/ObjectActionMsg.java
@@ -0,0 +1,131 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+
+
+
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+
+import engine.exception.SerializationException;
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+
+import java.util.ArrayList;
+
+
+/**
+ * Use item
+ *
+ * @author Eighty
+ */
+public class ObjectActionMsg extends ClientNetMsg {
+
+	private int unknown01;
+	private int unknown02;
+	private ArrayList<Long> targetCompID;
+
+	/**
+	 * This is the general purpose constructor
+	 */
+	public ObjectActionMsg(int unknown01, int unknown02, ArrayList<Long> targetCompID) {
+		super(Protocol.OBJECTACTION);
+		this.unknown01 = unknown01;
+		this.unknown02 = unknown02;
+		this.targetCompID = targetCompID;
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the ByteBuffer into a message. If a BufferUnderflow occurs (based on reading past the limit) then this constructor Throws that Exception to the caller.
+	 */
+	public ObjectActionMsg(AbstractConnection origin, ByteBufferReader reader)
+			 {
+		super(Protocol.OBJECTACTION, origin, reader);
+	}
+
+	/**
+	 * Deserializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)
+			 {
+		if (targetCompID == null)
+			targetCompID = new ArrayList<>();
+		unknown01 = reader.getInt();
+		unknown02 = reader.getInt();
+		for (int i=0; i<unknown01; i++)
+			targetCompID.add(reader.getLong());
+	}
+
+	/**
+	 * Serializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer)
+			throws SerializationException {
+		writer.putInt(unknown01);
+		writer.putInt(unknown02);
+		for (int i=0; i<unknown01; i++)
+			writer.putLong(targetCompID.get(i));
+	}
+
+	/**
+	 * @return the unknown01
+	 */
+	public int getUnknown01() {
+		return unknown01;
+	}
+
+	/**
+	 * @param unknown01 the unknown01 to set
+	 */
+	public void setUnknown01(int unknown01) {
+		this.unknown01 = unknown01;
+	}
+
+	/**
+	 * @return the unknown02
+	 */
+	public int getUnknown02() {
+		return unknown02;
+	}
+
+	/**
+	 * @param unknown02 the unknown02 to set
+	 */
+	public void setUnknown02(int unknown02) {
+		this.unknown02 = unknown02;
+	}
+
+	/**
+	 * @return the targetCompID
+	 */
+	public ArrayList<Long> getTargetCompID() {
+		return targetCompID;
+	}
+
+	/**
+	 * @param targetCompID the targetCompID to set
+	 */
+	public void setTargetCompID(ArrayList<Long> targetCompID) {
+		this.targetCompID = targetCompID;
+	}
+
+}
diff --git a/src/engine/net/client/msg/OpenFriendsCondemnListMsg.java b/src/engine/net/client/msg/OpenFriendsCondemnListMsg.java
new file mode 100644
index 00000000..b28fb317
--- /dev/null
+++ b/src/engine/net/client/msg/OpenFriendsCondemnListMsg.java
@@ -0,0 +1,931 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+import engine.Enum.GameObjectType;
+import engine.gameManager.DbManager;
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.ClientConnection;
+import engine.net.client.Protocol;
+import engine.objects.*;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class OpenFriendsCondemnListMsg extends ClientNetMsg {
+
+	private int messageType;
+    private ArrayList<Integer> characterList;
+	private int buildingType;
+	private int buildingID;
+	
+	private int playerType;
+	private int playerID;
+	private int guildID;
+	private int inviteType;
+	private ConcurrentHashMap<Integer,BuildingFriends>friends;
+	private int removeFriendType;
+	private int removeFriendID;
+	private boolean reverseKOS; //TODO Rename this for to fit ReverseKOS/Activate/deactive Condemned.
+	private ConcurrentHashMap<Integer,Condemned> guildCondemned;
+	private int nationID;
+
+	public OpenFriendsCondemnListMsg(int messageType,ConcurrentHashMap<Integer,BuildingFriends>friends) {
+		super(Protocol.OPENFRIENDSCONDEMNLIST);
+		this.messageType = messageType;
+		this.friends = friends;
+		
+	}
+	
+	public OpenFriendsCondemnListMsg(int messageType,ConcurrentHashMap<Integer,Condemned> guildCondemned ,boolean reverse) {
+		super(Protocol.OPENFRIENDSCONDEMNLIST);
+		this.messageType = messageType;
+		this.guildCondemned = guildCondemned;
+		this.reverseKOS = reverse;
+	}
+
+    // clone
+
+    public OpenFriendsCondemnListMsg(OpenFriendsCondemnListMsg openFriendsCondemnListMsg) {
+        super(Protocol.OPENFRIENDSCONDEMNLIST);
+        this.messageType = openFriendsCondemnListMsg.messageType;
+        this.guildCondemned = openFriendsCondemnListMsg.guildCondemned;
+        this.reverseKOS = openFriendsCondemnListMsg.reverseKOS;
+        this.playerType = openFriendsCondemnListMsg.playerType;
+        this.playerID = openFriendsCondemnListMsg.playerID;
+        this.inviteType = openFriendsCondemnListMsg.inviteType;
+        this.removeFriendID = openFriendsCondemnListMsg.removeFriendID;
+        this.removeFriendType = openFriendsCondemnListMsg.removeFriendType;
+        this.reverseKOS = openFriendsCondemnListMsg.reverseKOS;
+        this.nationID = openFriendsCondemnListMsg.nationID;
+        this.buildingType = openFriendsCondemnListMsg.buildingType;
+        this.buildingID = openFriendsCondemnListMsg.buildingID;
+        this.friends = openFriendsCondemnListMsg.friends;
+        this.characterList = openFriendsCondemnListMsg.characterList;
+    }
+
+    public void configure() {
+
+        // Pre-Cache all players and guild targets
+
+        if (characterList == null)
+            return;
+
+        for (Integer uuid : characterList) {
+
+            PlayerCharacter player = (PlayerCharacter) DbManager.getObject(GameObjectType.PlayerCharacter, uuid);
+
+            if (player == null)
+                continue;
+
+            Guild guild = player.getGuild();
+
+            if (guild == null)
+                continue;
+        }
+
+    }
+    
+    public void configureHeraldry(PlayerCharacter player){
+    	
+    }
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public OpenFriendsCondemnListMsg(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.OPENFRIENDSCONDEMNLIST, origin, reader); // openFriendsCondemnList =1239809615
+        characterList = new ArrayList<>();
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+		writer.putInt(this.messageType);
+		
+		if (this.messageType == 2){
+			this.showHeraldy(writer);
+			return;
+		}
+		
+		if (this.messageType == 4){
+			this.writeAddHealrdy(writer);
+			return;
+		}
+		if (this.messageType == 26){
+			showBuildingFriends(writer);
+			return;
+		}
+
+		if (this.messageType == 12){
+			this.showCondemnList(writer);
+			return;
+		}
+		
+		if (this.messageType == 15){
+			this.removeCondemned(writer);
+			return;
+		}
+		if (this.messageType == 17){
+			this.handleActivateCondemned(writer);
+			return;
+		}
+		
+	
+		
+		writer.putInt(0);
+		writer.putInt(this.characterList.size());
+		writer.putInt(this.characterList.size());
+
+		for (Integer uuid : characterList) {
+
+            PlayerCharacter player = (PlayerCharacter) DbManager.getObject(GameObjectType.PlayerCharacter, uuid);
+
+            if (player == null)
+                continue;
+
+			writer.put((byte) 1);
+			writer.putInt(0);
+			writer.putInt(0);
+			writer.putInt(1);
+			writer.putInt(GameObjectType.PlayerCharacter.ordinal());
+			writer.putInt(player.getObjectUUID());
+			Guild guild = player.getGuild();
+			Guild nation = null;
+
+			if (guild != null) {
+				writer.putInt(guild.getObjectType().ordinal());
+				writer.putInt(guild.getObjectUUID());
+				nation = guild.getNation();
+				if (nation != null) {
+					writer.putInt(nation.getObjectType().ordinal());
+					writer.putInt(nation.getObjectUUID());
+				} else {
+					writer.putInt(0);
+					writer.putInt(0);
+				}
+			} else {
+				for (int i=0;i<4;i++)
+					writer.putInt(0);
+			}
+			writer.putShort((short)0);
+			writer.put((byte)0);
+			writer.putString(player.getFirstName());
+
+			if (guild != null) {
+				GuildTag._serializeForDisplay(guild.getGuildTag(),writer);
+			} else {
+				writer.putInt(16);
+				writer.putInt(16);
+				writer.putInt(16);
+				writer.putInt(0);
+				writer.putInt(0);
+			}
+			if (nation != null) {
+				GuildTag._serializeForDisplay(nation.getGuildTag(),writer);
+			} else {
+				writer.putInt(16);
+				writer.putInt(16);
+				writer.putInt(16);
+				writer.putInt(0);
+				writer.putInt(0);
+			}
+			if (guild != null)
+				writer.putString(guild.getName());
+			else
+				writer.putString("[No Guild]");
+			if (nation != null)
+				writer.putString(nation.getName());
+			else
+				writer.putString("[No Nation]");
+			writer.putInt(0);
+		}
+	}
+	
+	private void readHandleToItem(ByteBufferReader reader){
+		reader.getInt();
+		reader.getInt();
+		reader.getInt();
+		
+		reader.getInt(); //object Type;
+		reader.getInt(); //objectID;
+		
+		reader.getInt();
+		reader.getInt();
+		reader.getInt();
+		reader.getInt();
+		reader.getInt();
+		reader.getInt();
+	}
+	
+	private void writeHandleToItem(ByteBufferWriter writer){
+	}
+	
+	private void removeCondemned(ByteBufferWriter writer){
+		writer.putInt(0);
+		writer.putInt(playerType);
+		writer.putInt(playerID);
+		writer.putInt(0);
+		writer.putInt(0);
+		writer.putInt(0);
+		writer.putInt(0);
+		writer.putInt(0);
+		writer.putInt(0);
+		writer.putInt(removeFriendType);
+		writer.putInt(removeFriendID);
+		writer.putInt(buildingType);
+		writer.putInt(buildingID);
+		writer.putInt(0);
+		writer.putInt(0);
+		writer.putShort((short) 0);
+
+	}
+
+
+	/**
+	 * Deserializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		this.messageType = reader.getInt(); //25 on open of friends list // 28 friends list // 11 open condemned list // 14 Open condemned selection of (indivual, guild, nation)
+
+		if (this.messageType == 1){
+			this.viewHealrdy(reader);
+			return;
+		}
+		
+		if (this.messageType == 4){
+			this.readAddHealrdy(reader);
+			return;
+		}
+		
+		if (this.messageType == 6){
+			this.readRemoveHeraldry(reader);
+			return;
+		}
+		if (this.messageType == 11){
+			this.ackBuildingFriends(reader);
+			reader.get();
+			return;
+		}
+		
+		if (this.messageType == 14){
+			this.addGuildCondemn(reader);
+			return;
+		}
+		if (this.messageType == 15){
+			this.removeFriendList(reader);
+			reader.get();
+			return;
+		}
+		
+		if (this.messageType == 17){
+			this.handleActivateCondemned(reader);
+			return;
+		}
+		
+		if (this.messageType == 18){
+			this.handleCondemnErrant(reader);
+			return;
+		}
+		
+		
+		if (this.messageType == 19){
+			this.handleKOS(reader);
+			return;
+		}
+		
+		if (this.messageType == 23){
+			this.readHandleToItem(reader);
+			return;
+		}
+		if (this.messageType == 28){
+			addFriendsList(reader);
+			return;
+		}
+		if (this.messageType == 30){
+			removeFriendList(reader);
+			return;
+		}
+		if (this.messageType == 25){
+			ackBuildingFriends(reader);
+			return;
+		}
+
+		reader.getInt();
+		int size = reader.getInt();
+		reader.getInt(); //size again
+		for (int i=0;i<size;i++) {
+			reader.get();
+			reader.getInt(); //0
+			reader.getInt(); //0
+			reader.getInt(); //1
+			reader.getInt(); //Player Type
+			int ID = reader.getInt(); //Player ID
+			reader.getLong(); //Guild ID
+			reader.getLong(); //Nation ID
+			reader.getShort();
+			reader.get();
+			reader.getString(); //name
+			for (int j=0;j<10;j++)////////The problem is in here its stops here!!!!
+				reader.getInt(); //Guild and Nation tags
+			reader.getString(); //guild name
+			reader.getString(); //nation name
+			reader.getInt();
+			this.characterList.add(ID);
+		}
+	}
+	
+	private void readRemoveHeraldry(ByteBufferReader reader){
+		reader.getInt();
+		reader.getInt();
+		reader.getInt();
+		this.playerType = reader.getInt();
+		this.playerID = reader.getInt();
+		reader.getInt();
+		reader.getInt();
+		reader.getInt();
+		reader.getInt();
+		reader.getInt();
+		reader.getInt();
+	}
+	
+	private void handleActivateCondemned(ByteBufferReader reader){
+		reader.getInt();
+		this.removeFriendType=reader.getInt();
+		this.removeFriendID = reader.getInt();
+		reader.getInt();
+		this.buildingID = reader.getInt();
+		this.reverseKOS = reader.get() == 1? true:false;
+		reader.getInt();
+		reader.getInt();
+		reader.get();
+	}
+	
+	private void handleCondemnErrant(ByteBufferReader reader){
+		reader.getInt();
+		this.removeFriendType=reader.getInt();
+		this.removeFriendID = reader.getInt();
+		reader.getInt();
+		this.buildingID = reader.getInt();
+		this.reverseKOS = reader.get() == 1? true:false;
+		reader.getInt();
+		reader.getInt();
+		reader.get();
+	}
+	private void handleActivateCondemned(ByteBufferWriter writer){
+		writer.putInt(0);
+		writer.putInt(removeFriendType);
+		writer.putInt(removeFriendID);
+		writer.putInt(GameObjectType.Building.ordinal());
+		writer.putInt(buildingID);
+		writer.put(reverseKOS ? (byte)1 : (byte)0);
+		writer.putInt(0);
+		writer.putInt(0);
+		writer.put((byte)0);
+	}
+	@Override
+	protected int getPowerOfTwoBufferSize() {
+		// Larger size for historically larger opcodes
+		return 12;
+	}
+	
+	private void addGuildCondemn(ByteBufferReader reader){
+		reader.getInt();
+		this.inviteType = reader.getInt();
+		reader.getInt();
+		this.playerID = reader.getInt();
+		reader.getInt();
+		this.guildID = reader.getInt();
+		reader.getInt();
+		this.nationID = reader.getInt();
+		reader.getInt();
+		reader.getInt();
+		reader.getInt();
+		reader.getInt();
+		reader.getInt();
+		reader.getInt();
+		reader.getInt();
+		reader.getInt();
+		this.buildingID = reader.getInt();
+		reader.getLong();
+		reader.getShort();
+		
+		
+	}
+
+	private void addFriendsList(ByteBufferReader reader){
+		reader.getInt();
+		this.inviteType = reader.getInt(); //7 individual, 8 guild, 9 guild IC
+		this.playerType = reader.getInt();
+		this.playerID = reader.getInt();
+		reader.getInt();
+		this.guildID = reader.getInt();
+		
+		reader.getLong(); //Nation, do we need this?
+		reader.getLong();
+		reader.getLong();
+		reader.getLong();
+		reader.getInt();
+		this.buildingType = reader.getInt();
+		this.buildingID = reader.getInt();
+		
+		reader.getInt();
+		reader.getInt();
+		reader.get();
+
+	}
+	
+	private void handleKOS(ByteBufferReader reader){
+		reader.getInt();
+		reader.getInt();
+		reader.getInt();
+		reader.getInt();
+		this.buildingID = reader.getInt();
+		reader.get();
+		byte reverse = reader.get();
+		this.reverseKOS = reverse == 1 ? true : false;
+		reader.getInt();
+		reader.getInt();
+
+	}
+	
+	private void removeFriendList(ByteBufferReader reader){
+		reader.getInt();
+		this.playerType = reader.getInt();
+		this.playerID = reader.getInt();
+		reader.getInt();
+		reader.getInt();
+		reader.getInt();
+		reader.getInt();
+		reader.getInt();
+		reader.getInt();
+		this.removeFriendType = reader.getInt();
+		this.removeFriendID = reader.getInt();
+		this.buildingType = reader.getInt();
+		this.buildingID = reader.getInt();
+		reader.getInt();
+		reader.getInt();
+		reader.get();
+
+	}
+
+
+    public int getRemoveFriendID() {
+		return removeFriendID;
+	}
+	
+	private void showCondemnList(ByteBufferWriter writer){
+		String name = "";
+		PlayerCharacter pc = null;
+		Guild guild = null;
+		
+		writer.putInt(0);
+		writer.putInt(0);
+		writer.putInt(0);
+		writer.putInt(0);
+		writer.putInt(0);
+		writer.put((byte)0);
+		writer.put(reverseKOS ? (byte)1 : 0); //Reverse?
+		
+		int listSize = this.guildCondemned.size();
+		writer.putInt(listSize);
+		writer.putInt(listSize);
+
+		for (Condemned condemned:this.guildCondemned.values()){
+			
+			
+			writer.put((byte)1);
+			
+			switch (condemned.getFriendType()){
+			case 2:
+                PlayerCharacter playerCharacter = (PlayerCharacter) DbManager.getObject(engine.Enum.GameObjectType.PlayerCharacter, condemned.getPlayerUID());
+
+
+				guild = playerCharacter.getGuild();
+				writer.putInt(GameObjectType.PlayerCharacter.ordinal());
+				writer.putInt(condemned.getPlayerUID());
+				writer.putInt(condemned.getFriendType());
+				writer.putInt(GameObjectType.PlayerCharacter.ordinal());
+				writer.putInt(condemned.getPlayerUID());
+				writer.putInt(0);
+				writer.putInt(0);
+				writer.putInt(GameObjectType.Guild.ordinal());
+				if (guild != null)
+				writer.putInt(guild.getObjectUUID());
+				else
+					writer.putInt(0);
+				writer.put(condemned.isActive() ?(byte)1:(byte)0);
+				writer.put((byte)0);
+				writer.put(condemned.isActive() ?(byte)1:(byte)0);
+
+				if (playerCharacter != null)
+				writer.putString(playerCharacter.getFirstName());
+				else
+					writer.putInt(0);
+				GuildTag._serializeForDisplay(GuildTag.ERRANT,writer);
+				if (guild != null)
+					GuildTag._serializeForDisplay(guild.getGuildTag(),writer);
+				else{
+					GuildTag._serializeForDisplay(GuildTag.ERRANT,writer);
+				}
+				break;
+			case 4:
+				guild = Guild.getGuild(condemned.getGuildUID());
+				writer.putInt(GameObjectType.Guild.ordinal());
+				writer.putInt(condemned.getGuildUID());
+				writer.putInt(condemned.getFriendType());
+				writer.putLong(0);
+				writer.putInt(GameObjectType.Guild.ordinal());
+				writer.putInt(condemned.getGuildUID());
+				writer.putLong(0);
+				writer.put((byte)0);
+				writer.put(condemned.isActive() ?(byte)1:(byte)0);
+				writer.put((byte)0);
+				if (guild != null)
+				writer.putString(guild.getName());
+				else
+					writer.putInt(0);
+				
+				if (guild != null)
+					GuildTag._serializeForDisplay(guild.getGuildTag(),writer);
+				else
+					GuildTag._serializeForDisplay(GuildTag.ERRANT,writer);
+				GuildTag._serializeForDisplay(GuildTag.ERRANT,writer);
+				break;
+			case 5:
+				guild = Guild.getGuild(condemned.getGuildUID());
+				writer.putInt(GameObjectType.Guild.ordinal());
+				writer.putInt(condemned.getGuildUID());
+				writer.putInt(condemned.getFriendType());
+				writer.putLong(0);
+				writer.putLong(0);
+				writer.putInt(GameObjectType.Guild.ordinal());
+				writer.putInt(condemned.getGuildUID());
+				writer.put((byte)0);
+				writer.put((byte)0);
+				writer.put(condemned.isActive() ?(byte)1:(byte)0);
+				if (guild != null)
+				writer.putString(guild.getName());
+				else
+					writer.putInt(0);
+				GuildTag._serializeForDisplay(GuildTag.ERRANT,writer);
+				if (guild != null)
+					GuildTag._serializeForDisplay(guild.getGuildTag(),writer);
+				else{
+					GuildTag._serializeForDisplay(GuildTag.ERRANT,writer);
+				}
+				break;
+			}
+
+			
+			
+			writer.putInt(0);
+			writer.putInt(0);
+			writer.putInt(0);
+		}
+	}
+
+	private void showBuildingFriends(ByteBufferWriter writer){
+		
+		
+		String name = "";
+		PlayerCharacter pc = null;
+		Guild guild = null;
+		
+		writer.putInt(0);
+		writer.putInt(0);
+		writer.putInt(0);
+		writer.putInt(0);
+		writer.putInt(0);
+		writer.put((byte)0);
+		int listSize = this.friends.size();
+		writer.putInt(listSize);
+		writer.putInt(listSize);
+
+		for (BuildingFriends friend:this.friends.values()){
+			pc = PlayerCharacter.getFromCache(friend.getPlayerUID());
+				guild = Guild.getGuild(friend.getGuildUID());
+			if (friend.getFriendType() == 7){
+				if (pc != null)
+				name = pc.getCombinedName();
+			}
+			
+			else if (guild != null)
+				name = guild.getName();
+			writer.put((byte)1);
+			if (friend.getFriendType() == 7){
+				writer.putInt(GameObjectType.PlayerCharacter.ordinal());
+				writer.putInt(friend.getPlayerUID());
+			}else{
+				writer.putInt(GameObjectType.Guild.ordinal());
+				writer.putInt(friend.getGuildUID());
+			}
+			writer.putInt(friend.getFriendType());
+			writer.putInt(0);
+			writer.putInt(0);
+                        
+			if (guild != null) {
+				writer.putInt(guild.getObjectType().ordinal());
+				writer.putInt(guild.getObjectUUID());
+
+				if (!guild.getNation().isErrant()) {
+					writer.putInt(guild.getNation().getObjectType().ordinal());
+					writer.putInt(guild.getNation().getObjectUUID());
+				}else{
+					writer.putInt(0);
+					writer.putInt(0);
+				}
+			}else{
+				writer.putLong(0);
+				writer.putLong(0);
+			}
+			writer.putShort((short)0);
+			writer.put((byte)0);
+			writer.putString(name);
+			if (guild != null)
+				GuildTag._serializeForDisplay(guild.getGuildTag(),writer);
+			else{
+				writer.putInt(16);
+				writer.putInt(16);
+				writer.putInt(16);
+				writer.putInt(0);
+				writer.putInt(0);
+			}
+			writer.putInt(16);
+			writer.putInt(16);
+			writer.putInt(16);
+			writer.putInt(0);
+			writer.putInt(0);
+			writer.putInt(0);
+			writer.putInt(0);
+			writer.putInt(0);
+		}
+
+	}
+
+	private void ackBuildingFriends(ByteBufferReader reader){
+		reader.getInt();
+		reader.getInt();
+		reader.getInt();
+		this.buildingType = reader.getInt();
+		this.buildingID = reader.getInt();
+		reader.getInt();
+		reader.getInt();
+		reader.get();
+		
+	}
+	
+	private void viewHealrdy(ByteBufferReader reader){
+		reader.getInt();
+		reader.getInt();
+		reader.getInt();
+	}
+	
+	private void showHeraldy(ByteBufferWriter writer){
+		
+		PlayerCharacter player = ((ClientConnection)this.getOrigin()).getPlayerCharacter();
+		writer.putInt(0); //error pop up msg
+		writer.putInt(0);
+		
+//		writer.putInt(0);
+		
+		HashMap<Integer,Integer> heraldryMap = Heraldry.HeraldyMap.get(player.getObjectUUID());
+		
+		//send empty list if no heraldry
+		if (heraldryMap == null || heraldryMap.isEmpty()){
+			writer.putInt(0);
+			return;
+		}
+		
+		
+		writer.putInt(heraldryMap.size());
+		
+		for (int characterID : heraldryMap.keySet()){
+			AbstractCharacter heraldryCharacter = null;
+			int characterType = heraldryMap.get(characterID);
+			if (characterType == GameObjectType.PlayerCharacter.ordinal())
+				heraldryCharacter = PlayerCharacter.getFromCache(characterID);
+			else if (characterType == GameObjectType.NPC.ordinal())
+				heraldryCharacter = NPC.getFromCache(characterID);
+			else if (characterType == GameObjectType.Mob.ordinal())
+				heraldryCharacter = Mob.getFromCache(characterID);
+			
+			if (heraldryCharacter == null)
+				this.showNullHeraldryCharacter(writer);
+			else{
+				writer.put((byte)1);
+				writer.putInt(heraldryCharacter.getObjectType().ordinal());
+				writer.putInt(heraldryCharacter.getObjectUUID());
+			
+			writer.putInt(9);
+			writer.putInt(heraldryCharacter.getObjectType().ordinal());
+			writer.putInt(heraldryCharacter.getObjectUUID());
+	                    
+			if (heraldryCharacter.getGuild() != null) {
+				writer.putInt(heraldryCharacter.getGuild().getObjectType().ordinal());
+				writer.putInt(heraldryCharacter.getGuild().getObjectUUID());
+
+				if (!heraldryCharacter.getGuild().getNation().isErrant()) {
+					writer.putInt(heraldryCharacter.getGuild().getNation().getObjectType().ordinal());
+					writer.putInt(heraldryCharacter.getGuild().getNation().getObjectUUID());
+				}else{
+					writer.putInt(0);
+					writer.putInt(0);
+				}
+			}else{
+				writer.putLong(0);
+				writer.putLong(0);
+			}
+			writer.putShort((short)0);
+			writer.put((byte)0);
+			writer.putString(heraldryCharacter.getName());
+			if (heraldryCharacter.getGuild() != null)
+				GuildTag._serializeForDisplay(heraldryCharacter.getGuild().getGuildTag(),writer);
+			else{
+				writer.putInt(16);
+				writer.putInt(16);
+				writer.putInt(16);
+				writer.putInt(0);
+				writer.putInt(0);
+			}
+			writer.putInt(16);
+			writer.putInt(16);
+			writer.putInt(16);
+			writer.putInt(0);
+			writer.putInt(0);
+			if (heraldryCharacter.getGuild() == null){
+				writer.putString("Errant");
+				writer.putString("Errant");
+			}	
+			else{
+				writer.putString(heraldryCharacter.getGuild().getName());
+				if (heraldryCharacter.getGuild().getNation() == null)
+					writer.putString("Errant");
+				else
+					writer.putString(heraldryCharacter.getGuild().getNation().getName());
+			}
+			writer.putInt(0);
+			}
+			
+		}
+	
+	}
+	
+	private void readAddHealrdy(ByteBufferReader reader){
+		reader.getInt();
+		reader.getInt();
+		this.playerType = reader.getInt(); //player object type;
+		this.playerID = reader.getInt();
+		reader.getInt();
+		reader.getInt();
+		reader.getInt();
+		reader.getInt();
+		reader.getInt();
+		reader.getInt();
+		reader.getInt();
+		reader.getInt();
+		
+		reader.getInt();
+		reader.getInt();
+		reader.getInt();
+	}
+	
+	private void writeAddHealrdy(ByteBufferWriter writer){
+		writer.putInt(0);
+		writer.putInt(0);
+		writer.putInt(this.playerType); //player object type;
+		writer.putInt(this.playerID);
+		writer.putInt(0);
+		writer.putInt(0);
+		writer.putInt(0);
+		writer.putInt(0);
+		writer.putInt(0);
+		writer.putInt(0);
+		writer.putInt(0);
+		writer.putInt(0);
+		
+		writer.putInt(0);
+		writer.putInt(0);
+		writer.putInt(0);
+	}
+
+	public int getMessageType() {
+		return this.messageType;
+	}
+
+	public ArrayList<Integer> getList() {
+		return this.characterList;
+	}
+
+	public void setMessageType(int value) {
+		this.messageType = value;
+	}
+
+	public void setList(ArrayList<Integer> value) {
+		this.characterList = value;
+	}
+
+	public void updateMsg(int messageType, ArrayList<Integer> list) {
+		this.messageType = messageType;
+		this.characterList = list;
+        this.configure();
+	}
+
+	public int getInviteType() {
+		return inviteType;
+	}
+
+    public int getPlayerType() {
+		return playerType;
+	}
+
+	public void setPlayerType(int playerType) {
+		this.playerType = playerType;
+	}
+
+	public int getPlayerID() {
+		return playerID;
+	}
+
+	public void setPlayerID(int playerID) {
+		this.playerID = playerID;
+	}
+
+	public int getGuildID() {
+		return guildID;
+	}
+	
+	public int getNationID() {
+		return nationID;
+	}
+
+	public void setGuildID(int guildID) {
+		this.guildID = guildID;
+	}
+
+    public int getBuildingID() {
+		return buildingID;
+	}
+
+	public void setBuildingID(int buildingID) {
+		this.buildingID = buildingID;
+	}
+
+	public boolean isReverseKOS() {
+		return reverseKOS;
+	}
+
+	public void setReverseKOS(boolean reverseKOS) {
+		this.reverseKOS = reverseKOS;
+	}
+	
+	
+	private void showNullHeraldryCharacter(ByteBufferWriter writer){
+		writer.put((byte)1);
+		writer.putInt(0);
+		writer.putInt(0);
+	
+	writer.putInt(6);
+	writer.putInt(0);
+	writer.putInt(0);
+                
+	
+		writer.putLong(0);
+		writer.putLong(0);
+	writer.putShort((short)0);
+	writer.put((byte)0);
+	writer.putInt(0);
+	
+		writer.putInt(16);
+		writer.putInt(16);
+		writer.putInt(16);
+		writer.putInt(0);
+		writer.putInt(0);
+	writer.putInt(16);
+	writer.putInt(16);
+	writer.putInt(16);
+	writer.putInt(0);
+	writer.putInt(0);
+	
+	writer.putInt(0);
+	writer.putInt(0);
+	writer.putInt(0);
+	
+	}
+}
diff --git a/src/engine/net/client/msg/OpenTradeWindowMsg.java b/src/engine/net/client/msg/OpenTradeWindowMsg.java
new file mode 100644
index 00000000..6bc21d35
--- /dev/null
+++ b/src/engine/net/client/msg/OpenTradeWindowMsg.java
@@ -0,0 +1,94 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+
+import engine.exception.SerializationException;
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.objects.AbstractGameObject;
+
+/**
+ * Open trade window
+ *
+ * @author Eighty
+ */
+public class OpenTradeWindowMsg extends ClientNetMsg {
+
+	private int unknown01;
+	private int playerType;
+	private int playerID;
+	private int targetType;
+	private int targetID;
+	/**
+	 * This is the general purpose constructor
+	 */
+	public OpenTradeWindowMsg(int unknown01, AbstractGameObject player, AbstractGameObject target) {
+		super(Protocol.INITIATETRADEHUDS);
+		this.unknown01 = unknown01;
+		this.playerType = player.getObjectType().ordinal();
+		this.playerID = player.getObjectUUID();
+		this.targetType = target.getObjectType().ordinal();
+		this.targetID = target.getObjectUUID();
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the ByteBuffer into a message. If a BufferUnderflow occurs (based on reading past the limit) then this constructor Throws that Exception to the caller.
+	 */
+	public OpenTradeWindowMsg(AbstractConnection origin, ByteBufferReader reader)
+			 {
+		super(Protocol.INITIATETRADEHUDS, origin, reader);
+	}
+
+	/**
+	 * Deserializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)
+			 {
+		unknown01 = reader.getInt();
+		playerType = reader.getInt();
+		playerID = reader.getInt();
+		targetType = reader.getInt();
+		targetID = reader.getInt();
+	}
+
+	/**
+	 * Serializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer)
+			throws SerializationException {
+		writer.putInt(unknown01);
+		writer.putInt(playerType);
+		writer.putInt(playerID);
+		writer.putInt(targetType);
+		writer.putInt(targetID);
+	}
+
+	/**
+	 * @return the unknown01
+	 */
+	public int getUnknown01() {
+		return unknown01;
+	}
+
+	/**
+	 * @param unknown01 the unknown01 to set
+	 */
+	public void setUnknown01(int unknown01) {
+		this.unknown01 = unknown01;
+	}
+
+
+
+}
diff --git a/src/engine/net/client/msg/OpenVaultMsg.java b/src/engine/net/client/msg/OpenVaultMsg.java
new file mode 100644
index 00000000..2ae03be3
--- /dev/null
+++ b/src/engine/net/client/msg/OpenVaultMsg.java
@@ -0,0 +1,76 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+
+import engine.exception.SerializationException;
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.objects.AbstractGameObject;
+
+/**
+ * Vault window opened
+ * @author Eighty
+ */
+public class OpenVaultMsg extends ClientNetMsg {
+
+	private int playerType;
+	private int playerID;
+	private int npcType;
+	private int npcID;
+	
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public OpenVaultMsg(AbstractGameObject ago, AbstractGameObject target) {
+		super(Protocol.OPENVAULT);
+		this.playerType = ago.getObjectType().ordinal();
+		this.playerID = ago.getObjectUUID();
+		this.npcType = target.getObjectType().ordinal();
+		this.npcID = target.getObjectUUID();
+		
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the ByteBuffer into a message. If a BufferUnderflow occurs (based on reading past the limit) then this constructor Throws that Exception to the caller.
+	 */
+	public OpenVaultMsg(AbstractConnection origin,
+			ByteBufferReader reader)  {
+		super(Protocol.OPENVAULT, origin, reader);
+	}
+
+	/**
+	 * Deserializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		playerType = reader.getInt();
+		playerID = reader.getInt();
+		npcType = reader.getInt();
+		npcID = reader.getInt();
+		
+	}
+
+	/**
+	 * Serializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer)
+			throws SerializationException {
+		writer.putInt(playerType);
+		writer.putInt(playerID);
+		writer.putInt(npcType);
+		writer.putInt(npcID);
+	}
+
+}
diff --git a/src/engine/net/client/msg/OrderNPCMsg.java b/src/engine/net/client/msg/OrderNPCMsg.java
new file mode 100644
index 00000000..fb4ab60f
--- /dev/null
+++ b/src/engine/net/client/msg/OrderNPCMsg.java
@@ -0,0 +1,209 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+
+package engine.net.client.msg;
+
+import engine.Enum.GameObjectType;
+import engine.math.Vector3fImmutable;
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+
+import java.util.ArrayList;
+
+public class OrderNPCMsg extends ClientNetMsg {
+
+	// 2 = manage this asset.  20 = manage entire city
+	private int objectType;
+	private int npcUUID;
+	private int buildingUUID;
+	private int unknown02;
+	private ArrayList<Vector3fImmutable> patrolPoints;
+	private ArrayList<Vector3fImmutable> sentryPoints;
+	private int patrolSize;
+	private int sentrySize;
+
+	private int actionType;
+	private float buySellPercent;
+
+	/**
+	 * This is the general purpose constructor
+	 */
+	public OrderNPCMsg() {
+		super(Protocol.ORDERNPC);
+		this.actionType = 0;
+		this.unknown02 = 0;
+		this.npcUUID = 0;
+		this.buildingUUID = 0;
+
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public OrderNPCMsg(AbstractConnection origin, ByteBufferReader reader)
+			 {
+		super(Protocol.ORDERNPC, origin, reader);
+	}
+
+	/**
+	 * Deserializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)
+			 {
+		actionType = reader.getInt();
+		if (this.actionType == 28){
+			this.handleCityCommand(reader);
+			return;
+		}
+		unknown02 = reader.getInt();
+		this.objectType = reader.getInt(); // Object Type Padding
+		npcUUID = reader.getInt();
+		reader.getInt(); // Object Type Padding
+		buildingUUID = reader.getInt();
+		this.buySellPercent = reader.getFloat();
+                 if (actionType > 6 && actionType < 13)
+			reader.getInt();
+
+
+
+	}
+
+	/**
+	 * Serializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+		writer.putInt(actionType);
+		writer.putInt(unknown02);
+		writer.putInt(GameObjectType.NPC.ordinal());
+		writer.putInt(npcUUID);
+		writer.putInt(GameObjectType.Building.ordinal());
+		writer.putInt(buildingUUID);
+		writer.putFloat(this.buySellPercent);
+		writer.putInt(0);
+
+
+	}
+
+	private void handleCityCommand(ByteBufferReader reader){
+		reader.getInt();
+		reader.getInt();
+		reader.getInt();
+		reader.getInt();
+		this.buildingUUID = reader.getInt();
+		reader.get();
+		reader.get();
+		reader.getInt();
+		patrolSize = reader.getInt();
+		if (patrolSize > 0){
+			this.patrolPoints = new ArrayList<>();
+			for (int i = 0;i<patrolSize;i++){
+				float x = reader.getFloat();
+				float y = reader.getFloat();
+				float z = reader.getFloat();
+				if (this.patrolPoints.size() < 4)
+					this.patrolPoints.add(new Vector3fImmutable(x,y,z));
+			}
+		}
+		sentrySize = reader.getInt();
+		if (sentrySize > 0){
+			this.sentryPoints = new ArrayList<>();
+			for (int i = 0;i<sentrySize;i++){
+				float x = reader.getFloat();
+				float y = reader.getFloat();
+				float z = reader.getFloat();
+				if (this.sentryPoints.size() < 4)
+					this.sentryPoints.add(new Vector3fImmutable(x,y,z));
+			}
+		}
+		reader.getInt();
+		reader.getInt();
+	}
+
+	/**
+	 * @return the npcUUID
+	 */
+	public int getNpcUUID() {
+		return npcUUID;
+	}
+
+
+
+	public int getActionType() {
+		return actionType;
+	}
+
+
+	public int getBuildingUUID() {
+		return buildingUUID;
+	}
+
+	/**
+	 * @param buildingUUID the buildingUUID to set
+	 */
+	public void setBuildingUUID(int buildingUUID) {
+		this.buildingUUID = buildingUUID;
+	}
+
+	public float getBuySellPercent() {
+		return buySellPercent;
+	}
+
+	public void setBuySellPercent(float buySellPercent) {
+		this.buySellPercent = buySellPercent;
+	}
+
+	public int getObjectType() {
+		return objectType;
+	}
+
+	public void setObjectType(int objectType) {
+		this.objectType = objectType;
+	}
+
+	public ArrayList<Vector3fImmutable> getPatrolPoints() {
+		return patrolPoints;
+	}
+
+	public ArrayList<Vector3fImmutable> getSentryPoints() {
+		return sentryPoints;
+	}
+
+	public int getPatrolSize() {
+		return patrolSize;
+	}
+
+	public void setPatrolSize(int patrolSize) {
+		this.patrolSize = patrolSize;
+	}
+
+	public int getSentrySize() {
+		return sentrySize;
+	}
+
+	public void setSentrySize(int sentrySize) {
+		this.sentrySize = sentrySize;
+	}
+
+}
+
+//Debug Info
+//Run: Failed to make object TEMPLATE:135700 INSTANCE:1717987027141... (t=50.46) (r=7/4/2011 11:56:39)
+//C:\ArcanePrime\Main_Branch\Shadowbane\Source\ArcObjectLoader.cpp(466):ERROR: ArcObjectLoader::Run: Failed to make object TEMPLATE:108760 INSTANCE:1717987027161... (t=50.46) (r=7/4/2011 11:56:39)
+//C:\ArcanePrime\Main_Branch\Shadowbane\Source\ArcObjectLoader.cpp(466):ERROR: ArcObjectLoader::Run: Failed to make object TEMPLATE:108760 INSTANCE:1717987027177... (t=50.67) (r=7/4/2011 11:56:39)
+//C:\ArcanePrime\Main_Branch\Shadowbane\Source\ArcObjectLoader.cpp(466):ERROR: ArcObjectLoader::Run: Failed to make object TEMPLATE:60040 INSTANCE:1717987027344... (t=50.87) (r=7/4/2011 11:56:39)
+//C:\ArcanePrime\Main_Branch\Shadowbane\Source\ArcObjectLoader.cpp(466):ERROR: ArcObjectLoader::Run: Failed to make object TEMPLATE:3 INSTANCE:1717987027164... (t=50.88) (r=7/4/2011 11:56:39)
+
diff --git a/src/engine/net/client/msg/PassiveMessageTriggerMsg.java b/src/engine/net/client/msg/PassiveMessageTriggerMsg.java
new file mode 100644
index 00000000..8d32f498
--- /dev/null
+++ b/src/engine/net/client/msg/PassiveMessageTriggerMsg.java
@@ -0,0 +1,65 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+
+
+public class PassiveMessageTriggerMsg extends ClientNetMsg {
+
+	private byte animation; //not sure if it's animation, 0 or 1.
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public PassiveMessageTriggerMsg(byte animation) {
+		super(Protocol.PASSIVEMESSAGETRIGGER);
+		this.animation = animation;
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public PassiveMessageTriggerMsg(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.PASSIVEMESSAGETRIGGER, origin, reader);
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+		writer.put(this.animation);
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		this.animation = reader.get();
+	}
+
+	public byte getAnimation() {
+		return this.animation;
+	}
+
+	public void setAnimation(byte value) {
+		this.animation = value;
+	}
+
+}
diff --git a/src/engine/net/client/msg/PerformActionMsg.java b/src/engine/net/client/msg/PerformActionMsg.java
new file mode 100644
index 00000000..c955fa32
--- /dev/null
+++ b/src/engine/net/client/msg/PerformActionMsg.java
@@ -0,0 +1,298 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+
+import engine.math.Vector3f;
+import engine.math.Vector3fImmutable;
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+
+public class PerformActionMsg extends ClientNetMsg {
+
+	protected int powerUsedID;
+	protected int numTrains;
+	protected int sourceType;
+	protected int sourceID;
+	protected int targetType;
+	protected int targetID;
+
+	protected float targetX;
+	protected float targetY;
+	protected float targetZ;
+	protected int unknown04; //1, 2, 6
+	protected int unknown05;
+
+	protected int realTrains; //not serialized. Used for mob AI tracking.
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public PerformActionMsg() {
+		super(Protocol.POWER);
+	}
+
+	public PerformActionMsg(int powerUsedID, int numTrains, int sourceType, int sourceID, int targetType, int targetID, float targetX, float targetY, float targetZ, int unknown04, int unknown05) {
+		super(Protocol.POWER);
+		this.powerUsedID = powerUsedID;
+		this.numTrains = numTrains;
+		this.sourceType = sourceType;
+		this.sourceID = sourceID;
+		this.targetType = targetType;
+		this.targetID = targetID;
+		this.targetX = targetX;
+		this.targetY = targetY;
+		this.targetZ = targetZ;
+		this.unknown04 = unknown04;
+		this.unknown05 = unknown05;
+
+		this.realTrains = this.numTrains;
+	}
+	
+	
+
+	public PerformActionMsg(PerformActionMsg msg) {
+		super(Protocol.POWER);
+		this.powerUsedID = msg.powerUsedID;
+		this.numTrains = msg.numTrains;
+		this.sourceType = msg.sourceType;
+		this.sourceID = msg.sourceID;
+		this.targetType = msg.targetType;
+		this.targetID = msg.targetID;
+		this.targetX = msg.targetX;
+		this.targetY = msg.targetY;
+		this.targetZ = msg.targetZ;
+		this.unknown04 = msg.unknown04;
+		this.unknown05 = msg.unknown05;
+		this.realTrains = msg.realTrains;
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public PerformActionMsg(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.POWER, origin, reader);
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+		writer.putInt(this.powerUsedID);
+		writer.putInt(this.numTrains);
+
+		writer.putInt(this.sourceType);
+		writer.putInt(this.sourceID);
+		writer.putInt(this.targetType);
+		writer.putInt(this.targetID);
+
+		writer.putFloat(this.targetX);
+		writer.putFloat(this.targetY);
+		writer.putFloat(this.targetZ);
+		writer.putInt(this.unknown04);
+		writer.putInt(this.unknown05);
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		this.powerUsedID = reader.getInt();
+		this.numTrains = reader.getInt();
+		this.sourceType = reader.getInt();
+		this.sourceID = reader.getInt();
+		this.targetType = reader.getInt();
+		this.targetID = reader.getInt();
+
+		this.targetX = reader.getFloat();
+		this.targetY = reader.getFloat();
+		this.targetZ = reader.getFloat();
+		this.unknown04 = reader.getInt();
+		this.unknown05 = reader.getInt(); //2=FailToCast, 3=Miss, 4=Dodge, 5=Immune, 6=resisted, 7=targetDead, 8=PowerInterupted, 9=NoValidTargets, 10=NotBeenGrantedPower
+		this.realTrains = this.numTrains;
+	}
+
+	/**
+	 * @return the powerUsedID
+	 */
+	public int getPowerUsedID() {
+		return powerUsedID;
+	}
+
+	/**
+	 * @param powerUsedID
+	 *            the powerUsedID to set
+	 */
+	public void setPowerUsedID(int powerUsedID) {
+		this.powerUsedID = powerUsedID;
+	}
+
+	/**
+	 * @return the numTrains
+	 */
+	public int getNumTrains() {
+		return numTrains;
+	}
+
+	/**
+	 * @param numTrains
+	 *            the numTrains to set
+	 */
+	public void setNumTrains(int numTrains) {
+		this.numTrains = numTrains;
+	}
+
+	/**
+	 * @return the sourceType
+	 */
+	public int getSourceType() {
+		return sourceType;
+	}
+
+	/**
+	 * @param sourceType
+	 *            the sourceType to set
+	 */
+	public void setSourceType(int sourceType) {
+		this.sourceType = sourceType;
+	}
+
+	/**
+	 * @return the sourceID
+	 */
+	public int getSourceID() {
+		return sourceID;
+	}
+
+	/**
+	 * @param sourceID
+	 *            the sourceID to set
+	 */
+	public void setSourceID(int sourceID) {
+		this.sourceID = sourceID;
+	}
+
+	/**
+	 * @return the targetType
+	 */
+	public int getTargetType() {
+		return targetType;
+	}
+
+	/**
+	 * @param targetType
+	 *            the targetType to set
+	 */
+	public void setTargetType(int targetType) {
+		this.targetType = targetType;
+	}
+
+	/**
+	 * @return the targetID
+	 */
+	public int getTargetID() {
+		return targetID;
+	}
+
+	/**
+	 * @param targetID
+	 *            the targetID to set
+	 */
+	public void setTargetID(int targetID) {
+		this.targetID = targetID;
+	}
+
+	/**
+	 * @return the unknown01
+	 */
+	public float getTargetX() {
+		return targetX;
+	}
+
+	/**
+     */
+	public void setTargetX(float targetX) {
+		this.targetX = targetX;
+	}
+
+	/**
+	 * @return the unknown02
+	 */
+	public float getTargetY() {
+		return targetY;
+	}
+
+	public void setTargetY(float targetY) {
+		this.targetY = targetY;
+	}
+
+	/**
+	 * @return the unknown03
+	 */
+	public float getTargetZ() {
+		return targetZ;
+	}
+
+
+	public void setTargetZ(float targetZ) {
+		this.targetZ = targetZ;
+	}
+
+	public void setTargetLoc(Vector3f targetLoc) {
+		this.targetX = targetLoc.x;
+		this.targetY = targetLoc.y;
+		this.targetZ = targetLoc.z;
+	}
+
+	public Vector3fImmutable getTargetLoc() {
+		return new Vector3fImmutable(this.targetX, this.targetY, this.targetZ);
+	}
+
+	/**
+	 * @return the unknown04
+	 */
+	public int getUnknown04() {
+		return unknown04;
+	}
+
+	/**
+	 * @param unknown04
+	 *            the unknown04 to set
+	 */
+	public void setUnknown04(int unknown04) {
+		this.unknown04 = unknown04;
+	}
+
+	/**
+	 * @return the unknown05
+	 */
+	public int getUnknown05() {
+		return unknown05;
+	}
+
+	/**
+	 * @param unknown05
+	 *            the unknown05 to set
+	 */
+	public void setUnknown05(int unknown05) {
+		this.unknown05 = unknown05;
+	}
+
+	public int getRealTrains() {
+		return this.realTrains;
+	}
+}
diff --git a/src/engine/net/client/msg/PetAttackMsg.java b/src/engine/net/client/msg/PetAttackMsg.java
new file mode 100644
index 00000000..d25f029a
--- /dev/null
+++ b/src/engine/net/client/msg/PetAttackMsg.java
@@ -0,0 +1,73 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+
+public class PetAttackMsg extends ClientNetMsg {
+
+	private int targetType;
+	private int targetID;
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public PetAttackMsg() {
+		super(Protocol.ARCPETATTACK);
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public PetAttackMsg(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.ARCPETATTACK, origin, reader);
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+		writer.putInt(this.targetType);
+		writer.putInt(this.targetID);
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		this.targetType = reader.getInt();
+		this.targetID = reader.getInt();
+	}
+
+	public int getTargetType() {
+		return this.targetType;
+	}
+
+	public int getTargetID() {
+		return this.targetID;
+	}
+
+	public void setTargetType(int value) {
+		this.targetType = value;
+	}
+
+	public void setTargetID(int value) {
+		this.targetID = value;
+	}
+}
diff --git a/src/engine/net/client/msg/PetCmdMsg.java b/src/engine/net/client/msg/PetCmdMsg.java
new file mode 100644
index 00000000..79215389
--- /dev/null
+++ b/src/engine/net/client/msg/PetCmdMsg.java
@@ -0,0 +1,68 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+
+public class PetCmdMsg extends ClientNetMsg {
+
+	private int type;
+
+	//1: stop attack
+	//2: dismiss
+	//3: toggle assist
+	//5: rest
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public PetCmdMsg(int type) {
+		super(Protocol.ARCPETCMD);
+		this.type = type;
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public PetCmdMsg(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.ARCPETCMD, origin, reader);
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+		writer.putInt(type);
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		this.type = reader.getInt();
+	}
+
+	public int getType() {
+		return this.type;
+	}
+
+	public void setType(int value) {
+		this.type = value;
+	}
+}
diff --git a/src/engine/net/client/msg/PetMsg.java b/src/engine/net/client/msg/PetMsg.java
new file mode 100644
index 00000000..eb681588
--- /dev/null
+++ b/src/engine/net/client/msg/PetMsg.java
@@ -0,0 +1,117 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.objects.Mob;
+
+public class PetMsg extends ClientNetMsg {
+
+	private int type; //5 or 6
+	private Mob pet;
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public PetMsg(int type, Mob pet) {
+		super(Protocol.PET);
+		if (this.type != 6)
+			this.type = 5;
+		this.pet = pet;
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public PetMsg(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.PET, origin, reader);
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+		writer.putInt(this.type);
+
+		if (this.pet != null) {
+			writer.putInt(pet.getObjectType().ordinal());
+			writer.putInt(pet.getObjectUUID());
+		} else {
+			writer.putInt(0);
+			writer.putInt(0);
+		}
+
+		if (type == 6) {
+			writer.putInt(0);
+		} else if (type == 5) {
+			if (pet != null){
+				writer.putInt((int)(pet.getCurrentHitpoints() / pet.getHealthMax())); //suspect %health left
+				writer.putInt((int)(pet.getMana() / pet.getManaMax())); //suspect %mana left
+				writer.putInt((int)(pet.getStamina() / pet.getStaminaMax())); //suspect %stamina left
+				writer.putString(pet.getName());
+				writer.putInt(0);
+				writer.put((byte)0);
+			}else{
+				writer.putInt(0); //suspect %health left
+				writer.putInt(0); //suspect %mana left
+				writer.putInt(0); //suspect %stamina left
+				writer.putString("No Pet");
+				writer.putInt(0);
+				writer.put((byte)0);
+			}
+			
+		}
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		this.type = reader.getInt();
+		reader.getInt();
+		int petID = reader.getInt();
+		this.pet = Mob.getFromCache(petID);
+		if (this.type == 5) {
+			reader.getInt();
+		} else if (this.type == 6) {
+			reader.getInt();
+			reader.getInt();
+			reader.getInt();
+			reader.getString();
+			reader.getInt();
+			reader.get();
+		}
+	}
+
+	public int getType() {
+		return this.type;
+	}
+
+	public Mob getPet() {
+		return this.pet;
+	}
+
+	public void setType(int value) {
+		this.type = value;
+	}
+
+	public void setPet(Mob value) {
+		this.pet = value;
+	}
+}
diff --git a/src/engine/net/client/msg/PetitionReceivedMsg.java b/src/engine/net/client/msg/PetitionReceivedMsg.java
new file mode 100644
index 00000000..0626ed88
--- /dev/null
+++ b/src/engine/net/client/msg/PetitionReceivedMsg.java
@@ -0,0 +1,274 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+
+
+public class PetitionReceivedMsg extends ClientNetMsg {
+
+	// TODO pull these statics out into SBEmuStatics.java
+	private static final int PETITION_NEW = 1;
+	private static final int PETITION_CANCEL = 2;
+
+	private static final int TYPE_GENERAL_HELP = 1;
+	private static final int TYPE_FEEDBACK = 2;
+	private static final int TYPE_STUCK = 3;
+	private static final int TYPE_HARASSMENT = 4;
+	private static final int TYPE_EXPLOIT = 5;
+	private static final int TYPE_BUG = 6;
+	private static final int TYPE_GAME_STOPPER = 7;
+	private static final int TYPE_TECH_SUPPORT = 8;
+
+	private static final int SUBTYPE_EXPLOIT_DUPE = 1;
+	private static final int SUBTYPE_EXPLOIT_LEVELING = 2;
+	private static final int SUBTYPE_EXPLOIT_SKILL_GAIN = 3;
+	private static final int SUBTYPE_EXPLOIT_KILLING = 4;
+	private static final int SUBTYPE_EXPLOIT_POLICY = 5;
+	private static final int SUBTYPE_EXPLOIT_OTHER = 6;
+	private static final int SUBTYPE_TECH_VIDEO = 7;
+	private static final int SUBTYPE_TECH_SOUND = 8;
+	private static final int SUBTYPE_TECH_NETWORK = 9;
+	private static final int SUBTYPE_TECH_OTHER = 10;
+
+	private int petition;
+	private int unknown01;
+	private int unknown02;
+	private byte unknownByte01;
+	private int unknown03;
+	private int unknown04;
+	private int unknown05;
+	private int unknown06;
+	private int type;
+	private int subType;
+	private String compType;
+	private String language;
+	private int unknown07;
+	private String message;
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public PetitionReceivedMsg() {
+		super(Protocol.CUSTOMERPETITION);
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public PetitionReceivedMsg(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.CUSTOMERPETITION, origin, reader);
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+		writer.putInt(this.petition);
+		if (this.petition == PETITION_NEW) {
+			writer.putInt(this.unknown01);
+			writer.putInt(this.unknown02);
+			writer.put(this.unknownByte01);
+			writer.putInt(this.unknown03);
+			writer.putInt(this.unknown04);
+			writer.putInt(this.unknown05);
+			writer.putInt(this.unknown06);
+			writer.putInt(this.type);
+			writer.putInt(this.subType);
+			writer.putString(this.compType);
+			writer.putString(this.language);
+			writer.putInt(this.unknown07);
+			writer.putUnicodeString(message);
+		} else if (this.petition == PETITION_CANCEL) {
+			writer.putInt(this.unknown01);
+			writer.putInt(this.unknown02);
+			writer.put(this.unknownByte01);
+			writer.putInt(this.unknown03);
+			writer.putInt(this.unknown04);
+		}
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		petition = reader.getInt();
+		if (petition == PETITION_NEW) {
+			this.unknown01 = reader.getInt();
+			this.unknown02 = reader.getInt();
+			this.unknownByte01 = reader.get();
+			this.unknown03 = reader.getInt();
+			this.unknown04 = reader.getInt();
+			this.unknown05 = reader.getInt();
+			this.unknown06 = reader.getInt();
+			this.type = reader.getInt();
+			this.subType = reader.getInt();
+			this.compType = reader.getString();
+			this.language = reader.getString();
+			this.unknown07 = reader.getInt();
+			this.message = reader.getUnicodeString();
+		} else if (petition == PETITION_CANCEL) {
+			this.unknown01 = reader.getInt();
+			this.unknown02 = reader.getInt();
+			this.unknownByte01 = reader.get();
+			this.unknown03 = reader.getInt();
+			this.unknown04 = reader.getInt();
+		}
+	}
+
+	/**
+	 * @return the petition
+	 */
+	public int getPetition() {
+		return petition;
+	}
+
+	/**
+	 * @param petition
+	 *            the petition to set
+	 */
+	public void setPetition(int petition) {
+		this.petition = petition;
+	}
+
+	/**
+	 * @return the type
+	 */
+	public int getType() {
+		return type;
+	}
+
+	/**
+	 * @param type
+	 *            the type to set
+	 */
+	public void setType(int type) {
+		this.type = type;
+	}
+
+	/**
+	 * @return the subType
+	 */
+	public int getSubType() {
+		return subType;
+	}
+
+	/**
+	 * @param subType
+	 *            the subType to set
+	 */
+	public void setSubType(int subType) {
+		this.subType = subType;
+	}
+
+	/**
+	 * @return the message
+	 */
+	public String getMessage() {
+		return message;
+	}
+
+	/**
+	 * @param message
+	 *            the message to set
+	 */
+	public void setMessage(String message) {
+		this.message = message;
+	}
+
+	public int getUnknown01() {
+		return this.unknown01;
+	}
+
+	public void setUnknown01(int value) {
+		this.unknown01 = value;
+	}
+
+	public int getUnknown02() {
+		return this.unknown02;
+	}
+
+	public void setUnknown02(int value) {
+		this.unknown02 = value;
+	}
+
+	public int getUnknown03() {
+		return this.unknown03;
+	}
+
+	public void setUnknown03(int value) {
+		this.unknown03 = value;
+	}
+
+	public int getUnknown04() {
+		return this.unknown04;
+	}
+
+	public void setUnknown04(int value) {
+		this.unknown04 = value;
+	}
+
+	public int getUnknown05() {
+		return this.unknown05;
+	}
+
+	public void setUnknown05(int value) {
+		this.unknown05 = value;
+	}
+
+	public int getUnknown06() {
+		return this.unknown06;
+	}
+
+	public void setUnknown06(int value) {
+		this.unknown06 = value;
+	}
+
+	public int getUnknown07() {
+		return this.unknown07;
+	}
+
+	public void setUnknown07(int value) {
+		this.unknown07 = value;
+	}
+
+	public byte getUnknownByte01() {
+		return this.unknownByte01;
+	}
+
+	public void setUnknownByte01(byte value) {
+		this.unknownByte01 = value;
+	}
+
+	public String getCompType() {
+		return this.compType;
+	}
+
+	public void setCompType(String value) {
+		this.compType = value;
+	}
+
+	public String getLanguage() {
+		return this.language;
+	}
+
+	public void setLanguage(String value) {
+		this.language = value;
+	}
+}
diff --git a/src/engine/net/client/msg/PlaceAssetMsg.java b/src/engine/net/client/msg/PlaceAssetMsg.java
new file mode 100644
index 00000000..2ac5989f
--- /dev/null
+++ b/src/engine/net/client/msg/PlaceAssetMsg.java
@@ -0,0 +1,593 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+import engine.Enum;
+import engine.math.Vector3fImmutable;
+import engine.net.*;
+import engine.net.client.ClientConnection;
+import engine.net.client.Protocol;
+import engine.objects.Zone;
+import org.pmw.tinylog.Logger;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+public class 	PlaceAssetMsg extends ClientNetMsg {
+
+	/*
+	Client -> Server
+	//Type 1
+	//940962DF 00000001 00000000 0A400000 03903DC1 00000000 00000000 00000000 3F800000 00000000 00000000 00000000 00
+	//00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00
+	//00000000
+	//00000000
+	//00000000
+	//Type3
+	//940962DF 00000003 00000000 00000000 00000000 00000000 00000000 00000000 3F800000 00000000 00000000 00000000 00
+	//00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 01
+	//00000000
+	//00000001
+	//	00000000 0013FF24 475765FD 417BD060 C794FF75 B33BBD2E 00000000 3F800000 00000000 <-placement info
+	//00000000
+
+
+	Server -> Client
+	//Type 2 (Asset list / walls)
+	//940962DF 00000002 00000000 00000000 00000000 00000000 00000000 00000000 3F800000 00000000 00000000 00000000 01
+	//475725EC 412BD080 C794DF86 44600000 44600000 44600000 43000000 43000000 43000000 445AC000 445AC000 01
+	//00000000
+	//00000000
+	//00000006 <-building list for walls
+	//	00000000 0013FA74 00061A80 <-wall ID and cost
+	//	00000000 0013FBA0 000249F0
+	//	00000000 0013FCCC 000186A0
+	//	00000000 0013FDF8 0007A120
+	//	00000000 0013FF24 0007A120
+	//	00000000 004D5FD0 0007A120
+	//Type 2 (Single asset)
+	//940962DF 00000002 00000000 00000000 00000000 00000000 00000000 00000000 3F800000 00000000 00000000 00000000 01
+	//475725EC 412BD080 C794DF86 44600000 44600000 44600000 43000000 43000000 43000000 445AC000 445AC000 00
+	//00000001
+	//	00000000 00063060 00000001 <-blueprintUUID, 1 Building
+	//00000000
+	//00000000
+	//Type 0 (Response Error)
+	//940962DF 00000000 00000006 0000001b
+	//430061006e006e006f007400200070006c00610063006500200061007300730065007400200069006e00200077006100740065007200
+	//00000000 00000000 00000000 00000000 00000000 3F800000 00000000 00000000 00000000 00
+	//00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00
+	//00000000
+	//00000000
+	//00000000
+	//Type 4 (Response Success (place asset))
+	//940962DF 00000000 00000002 0000000D 00460065007500640061006C0020004300680075007200630068
+	//00000000 00000000 00000000 00000000 00000000 3F800000 00000000 00000000 00000000 00
+	//00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00
+	//00000000
+	//00000000
+	//00000000
+	 */
+
+	//UseItem(C->S)->Type 2(S->C)->Type 1(C->S)  <-close placement window, no response
+	//UseItem(C->S)->Type 2(S->C)->Type 3(C->S)->Type 0(S->C)  <-Attempt place asset, with error response
+	//UseItem(C->S)->Type 2(S->C)->Type 3(C->S)->Type 4(S->C)  <-Attempt place asset, with success response
+
+	/* Error msg codes
+	//1		"msg append here" <- what's in msg string.
+	//2		Conflict with "msg append here"
+	//3		Conflict between proposed assets
+	//4		Asset "msg append here" Cannot be isolated.
+	//5		Asset "msg append here" is outside the fortress zone.
+	//6		Cannot place the asset in water.
+	//7		Cannot place asset on land.
+	//8		NULL template for asset
+	//9		You must be a guild member to place this asset
+	//10	You must be a guild leader to place this asset
+	//11	No city asset template
+	//12	Only leaders of your guild can place fortresses
+	//13	Your guild cannot place fortress placements here
+	//14	This city architechture cannot be placed in this zone
+	//15	Cannot place assets in peace zone
+	//16	You do not belong to a guild
+	//17	Yours is not an errant or swarn guild
+	//18	There are no guild trees to be found
+	//19	NULL tree city asset
+	//20	You cannot place a bane circle on a tree your affiliated with
+	//21	This tree cannot be affected by bane circles
+	//22	Bane circles cannot effect dead trees
+	//23	A bane circle is already attached to nearest tree
+	//24	Banecircle is too far from guild
+	//25	Banecircle is too close to guild tree
+	//26	Cannot find deed in inventory
+	//27	Target object is not a deed
+	//28	You do not have enough gold to complete this request
+	//29	You apparently do not have access to some of these assets
+	//30	Insufficient deeds
+	//31	Unable to locate deed to this asset
+	//32	Unknown error occurred: 32
+	//33	Unknown error occurred: 33
+	//34	Unknown error occurred: 34
+	//35	Unknown error occurred: 35
+	//36	Unknown error occurred: 36
+	//37	Unknown error occurred: 37
+	//38	Unknown error occurred: 38
+	//39	Too close to another tree
+	//40	Cannot place into occupied guild zone
+	//41	Cannot place outisde a guild zone
+	//42	Tree cannot support anymore shrines
+	//43	The city already has a shrine of that type
+	//44	You must be in a player guild to place a bane circle
+	//45	Tree cannot support anymore spires
+	//46	A spire of that type already exists
+	//47	Tree cannot support anymore barracks
+	//48	Assets (except walls) must be placed one at a time
+	//49	The city cannot support a warehouse at its current rank
+	//50	You can only have one warehouse
+	//51	This asset cannot be placed on city grid
+	//52	No city to associate asset with
+	//53	Buildings of war cannot be placed around a city grid unless there is an active bane
+	//54	You must belong to the nation of the Bane Circle or the Tree to place buildings of war.
+	//55	You must belong to a nation to place a bane circle
+	//56	The building of war must be placed closer to the city
+	//57	No building may be placed within this territory
+	//58	This territory is full, it can support no more then "msg append here" trees.
+	//59	No city to siege at this location.
+	//60	This scroll's rank is too low to bane this city
+	//61	The bane circle cannot support any more buildings of war
+	//62	The tree cannot support any more buildings of war
+	//63	Failure in guild tree claiming phase
+	//64	Your nation is already at war and your limit has been reached
+	//65	Unable to find a tree to target
+	//66	There is no bane circle to support this building of war
+	//67	There is no tree to support this building of war
+	//68	There is not tree or bane circle to support this building of war
+	//69	Trees must be placed within a territory
+	//70	Unknown error occurred: 38
+	//71	This building of war may not be placed on a city grid by attackers
+	//72	You cannot place a bane circle while you are in a non player nation
+	//73	Only the guild leader or inner council may place a bane circle
+	//74	Only buildings of war may be placed during a bane
+	//75	This current vigor of the tree withstands your attempt to place a bane circle. Minutes remaining: "msg appended here"
+	//76	This tree cannot support towers or gatehouses
+	//77	This tree cannot support more towers or gatehouses
+	//78	This tree cannot support walls.
+	//79	This tree cannot support more walls.
+	 */
+
+	private static final Map<Integer,Integer> wallToCost;
+	static {
+		Map<Integer,Integer> map = new HashMap<>();
+		map.put(454700, 100000);   //Straight Outer Wall
+		map.put(1309900, 100000);  //Irekei Outer Straight Wall
+		map.put(1348900, 100000);  //Invorri Outer Straight Wall
+		map.put(454650, 150000);   //Outer Wall with Stairs
+		map.put(455000, 150000);   //Outer Wall with Tower
+		map.put(454550, 150000);   //Outer Wall Gate
+		map.put(455700, 150000);   //Small Gate House
+		map.put(1309600, 150000);  //Irekei Outer Wall with Stairs
+		map.put(1309300, 150000);  //Irekei Outer Wall Gate
+		map.put(1331200, 150000);  //Elven Straight Outer Wall
+		map.put(1330900, 150000);  //Elven Outer Wall with Stairs
+		map.put(1332100, 150000);  //Elven Outer Wall with Tower
+		map.put(1330300, 150000);  //Elven Outer Wall Gate
+		map.put(1348600, 150000);  //Invorri Outer Wall with Stairs
+		map.put(1348300, 150000);  //Invorri Outer Wall Gate
+		map.put(454750, 300000);   //Concave Tower
+		map.put(458100, 300000);   //Artillery Tower
+		map.put(455300, 300000);   //Tower Junction
+		map.put(454800, 300000);   //Convex Tower (inside corner)
+		map.put(1310200, 300000);  //Irekei Concave Tower
+		map.put(5070800, 300000);  //Irekei Artillery Tower
+		map.put(1310500, 300000);  //Irekei Convex Tower
+		map.put(1330600, 300000);  //Elven Gate House
+		map.put(1331500, 300000);  //Elven Concave Tower
+		map.put(5070200, 300000);  //Elven Artillery Tower
+		map.put(1332400, 300000);  //Elven Tower Junction
+		map.put(1331800, 300000);  //Elven Convex Tower
+		map.put(1349200, 300000);  //Invorri Concave Tower
+		map.put(5071400, 300000);  //Invorri Artillery Tower
+		map.put(1349500, 300000);  //Invorri Convex Tower
+		wallToCost = Collections.unmodifiableMap(map);
+	}
+	private static final Map<Integer,Integer> wallToUseId;
+	static {
+		Map<Integer,Integer> map = new HashMap<>();
+		///Feudal Outer Walls
+		map.put(454700, 1);   //Straight Outer Wall
+		map.put(454650, 1);   //Outer Wall with Stairs
+		map.put(455000, 1);   //Outer Wall with Tower
+		map.put(454550, 1);   //Outer Wall Gate
+		map.put(455700, 1);   //Small Gate House
+		map.put(454750, 1);   //Concave Tower
+		map.put(458100, 1);   //Artillery Tower
+		map.put(455300, 1);   //Tower Junction
+		map.put(454800, 1);   //Convex Tower (inside corner)
+		//map.put(1, 454000, 1); //Gate House (giant gatehouse) NOT USE IN GAME
+		//Feudal Inner Walls
+		/*
+		map.put(454100, 2);   //Inner Archway
+		map.put(454200, 2);   //Inner Wall Corner
+		map.put(454250, 2);   //Inner Wall Gate
+		map.put(454300, 2);   //Inner Straight Wall
+		map.put(454350, 2);   //Inner Wall T-Junction
+		map.put(454400, 2);   //Inner Wall Cross Junction
+		map.put(454850, 2);   //Tower-Inner Wall Junction (T-East)	Stuck inside left
+		map.put(454900, 2);   //Tower-Inner Wall Junction (T-South) stuck inside right
+		map.put(454950, 2);   //Tower-Inner Wall Junction (4-way) stuck inside
+		 */
+		//Irekei Outer Walls
+		map.put(1309900, 3);  //Irekei Outer Straight Wall
+		map.put(1309600, 3);  //Irekei Outer Wall with Stairs
+		map.put(1309300, 3);  //Irekei Outer Wall Gate
+		map.put(1310200, 3);  //Irekei Concave Tower
+		map.put(5070800, 3);  //Irekei Artillery Tower
+		map.put(1310500, 3);  //Irekei Convex Tower
+		//Elven Outer Walls
+		map.put(1331200, 4);  //Elven Straight Outer Wall
+		map.put(1330900, 4);  //Elven Outer Wall with Stairs
+		map.put(1332100, 4);  //Elven Outer Wall with Tower
+		map.put(1330300, 4);  //Elven Outer Wall Gate
+		map.put(1330600, 4);  //Elven Gate House
+		map.put(1331500, 4);  //Elven Concave Tower
+		map.put(5070200, 4);  //Elven Artillery Tower
+		map.put(1332400, 4);  //Elven Tower Junction
+		map.put(1331800, 4);  //Elven Convex Tower
+		//Invorri Outer Walls
+		map.put(1348900, 5);  //Invorri Outer Straight Wall
+		map.put(1348600, 5);  //Invorri Outer Wall with Stairs
+		map.put(1348300, 5);  //Invorri Outer Wall Gate
+		map.put(1349200, 5);  //Invorri Concave Tower
+		map.put(5071400, 5);  //Invorri Artillery Tower
+		map.put(1349500, 5);  //Invorri Convex Tower
+		wallToUseId = Collections.unmodifiableMap(map);
+	}
+	private static final Map<Integer, Map<Integer,Integer>> useIdToWallCostMaps;
+	static {
+		//autoloaded based on wallToUseId and wallToCost
+		Map<Integer, Map<Integer,Integer>> map = new HashMap<>();
+		for (Map.Entry<Integer,Integer> entry : wallToUseId.entrySet()) {
+			int wallId = entry.getKey();
+			int useId = entry.getValue();
+			int cost = 0;
+			Integer costCheck = wallToCost.get(wallId);
+			if (costCheck != null) {
+				cost = costCheck;
+			} else {
+				throw new Error("PlaceAssetMsg: WallId '" + wallId + "' has no cost in 'wallToCost' but exists in 'useIdToWall'.");
+			}
+			if (!map.containsKey(useId)) {
+				map.put(useId, new HashMap<>());
+			}
+			map.get(useId).put(wallId, cost);
+		}
+		for (Map.Entry<Integer,Map<Integer,Integer>> entry : map.entrySet()) {
+			map.put(entry.getKey(), Collections.unmodifiableMap(entry.getValue()));
+		}
+		useIdToWallCostMaps = Collections.unmodifiableMap(map);
+	}
+
+	private int actionType; //1,3 (recv), 0,2 (send)
+	private int msgID; //used on type 0, 0 on all other types
+	private String msg; //used on type 0
+	private int contractType; //used on type 1
+	private int contractID; //used on type 1
+	private byte unknown01; //0x01 on type 2 (send city data). 0x00 otherwise
+	private float x;
+	private float y;
+	private float z;
+	private byte unknown02; //0x01 if data follow, 0x00 otherwise. Best guess
+	private ArrayList<PlacementInfo> placementInfo;
+
+	private static final int NONE = 0;
+	private static final int CLIENTREQ_UNKNOWN = 1;
+	private static final int SERVER_OPENWINDOW = 2;
+	private static final int CLIENTREQ_NEWBUILDING = 3;  // Request to place asset
+	private static final int SERVER_CLOSEWINDOW = 4;
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public PlaceAssetMsg() {
+		super(Protocol.PLACEASSET);
+		this.placementInfo = new ArrayList<>();
+		this.actionType = SERVER_OPENWINDOW;
+		this.msgID = 0;
+		this.msg = "";
+		this.contractType = 0;
+		this.contractID = 0;
+		this.unknown01 = (byte)0x00;
+		this.x = 0f;
+		this.y = 0f;
+		this.z = 0f;
+		this.unknown02 = (byte)0x01;
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public PlaceAssetMsg(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.PLACEASSET, origin, reader);
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+
+		writer.putInt(this.actionType);
+
+		if (this.actionType == NONE) {
+			writer.putInt(this.msgID);
+			if (this.msgID != 0)
+			writer.putString(this.msg);
+		} else if (this.actionType == SERVER_CLOSEWINDOW) {
+			//writer.putInt(1);  //Qty of assets placed?? A 0 will crash the client. Any value >0 seems to do the same thing.
+			writer.putInt(0);
+		} else {
+			writer.putInt(0);
+		}
+
+		writer.putInt(this.contractType);
+		writer.putInt(this.contractID);
+		writer.putInt(0);
+		writer.putInt(0);
+		writer.putInt(0);
+		writer.putFloat(1f);
+		writer.putInt(0);
+		writer.putInt(0);
+		writer.putInt(0);
+
+		if (this.actionType == SERVER_OPENWINDOW || (this.actionType == NONE && this.msgID == 0) ) {
+			//send Place Asset Msg
+			writer.put((byte)0x01);
+			writer.putFloat(x);
+			writer.putFloat(y);
+			writer.putFloat(z);
+			for (int i=0;i<3;i++)
+				writer.putFloat(896); // city Bounds full extent.
+			for (int i=0;i<3;i++)
+				writer.putFloat(128); //grid dimensions
+			PlacementInfo pi = (this.placementInfo.size() > 0) ? this.placementInfo.get(0) : null;
+			int buildingID = (pi != null) ? pi.getBlueprintUUID() : 0;
+			if (buildingID == 24200){
+				writer.putFloat(875);
+				writer.putFloat(875);
+			}else{
+				writer.putFloat(576);
+				writer.putFloat(576);
+			}
+
+
+
+			if (buildingID < 6) {
+				//place wall lists
+				writer.put((byte)0x01);
+				writer.putInt(0);
+				writer.putInt(0);
+				Map<Integer, Integer> buildings = getBuildingList(buildingID);
+				writer.putInt(buildings.size());
+				for (int bID : buildings.keySet()) {
+					writer.putInt(0);
+					writer.putInt(bID);
+					writer.putInt(buildings.get(bID));
+				}
+			} else {
+				//send individual building
+				writer.put((byte)0x00);
+				writer.putInt(1);
+				writer.putInt(0);
+				writer.putInt(buildingID);
+				writer.putInt(1);
+				writer.putInt(0);
+				writer.putInt(0);
+			}
+		} else {
+			//Send Server response to client placing asset
+			writer.put((byte)0x00);
+			for (int i=0;i<11;i++)
+				writer.putFloat(0f);
+			writer.put((byte)0x00);
+			writer.putInt(0);
+			if (this.placementInfo == null)
+				writer.putInt(0);
+			else{
+				writer.putInt(this.placementInfo.size());
+				for (PlacementInfo placementInfo : this.placementInfo){
+					writer.putInt(0);
+					writer.putInt(placementInfo.blueprintUUID);
+					writer.putVector3f(placementInfo.loc);
+					writer.putFloat(placementInfo.w);
+					writer.putVector3f(placementInfo.rot);
+				}
+			}
+			
+			writer.putInt(0);
+		}
+	}
+
+	private static Map<Integer, Integer> getBuildingList(int buildingID) {
+		if (useIdToWallCostMaps.containsKey(buildingID)) {
+			return useIdToWallCostMaps.get(buildingID);
+		}
+		return new HashMap<>(0);
+	}
+
+	public static int getWallCost(int blueprintUUID) {
+		if (wallToCost.containsKey(blueprintUUID)) {
+			return wallToCost.get(blueprintUUID);
+		}
+		Logger.warn("Cost of Wall '" + blueprintUUID + "' was requested but no cost is configured for that wallId.");
+		return 0;
+	}
+
+	public static void sendPlaceAssetError(ClientConnection origin, int errorID, String stringData) {
+
+		PlaceAssetMsg outMsg;
+
+		outMsg = new PlaceAssetMsg();
+        outMsg.actionType = 0;
+        outMsg.msgID = errorID;
+        outMsg.msg = stringData;
+
+        Dispatch dispatch = Dispatch.borrow(origin.getPlayerCharacter(), outMsg);
+		DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+	}
+	
+	public static void sendPlaceAssetConfirmWall(ClientConnection origin, Zone zone) {
+		
+		PlaceAssetMsg outMsg = new PlaceAssetMsg();
+        outMsg.actionType = 0;
+        outMsg.msgID = 0;
+        outMsg.msg = "";
+        outMsg.x = zone.getLoc().x + 64;
+        outMsg.y = zone.getLoc().y;
+        outMsg.z = zone.getLoc().z + 64;
+        
+
+        Dispatch dispatch = Dispatch.borrow(origin.getPlayerCharacter(), outMsg);
+		DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		this.placementInfo = new ArrayList<>();
+		this.actionType = reader.getInt();
+		reader.getInt();
+		this.contractType = reader.getInt();
+		this.contractID = reader.getInt();
+		for (int i=0; i<7; i++)
+			reader.getInt();
+		reader.get();
+		for (int i=0; i<11; i++)
+			reader.getInt();
+		reader.get();
+		reader.getInt();
+		int placementInfo = reader.getInt();
+		for (int i=0;i<placementInfo;i++) {
+			reader.getInt();
+			PlacementInfo pi = new PlacementInfo(reader.getInt(), reader.getFloat(), reader.getFloat(),
+					reader.getFloat(), reader.getFloat(), reader.getFloat(), reader.getFloat(), reader.getFloat());
+			this.placementInfo.add(pi);
+		}
+		reader.getInt();
+	}
+
+	public int getActionType() {
+		return this.actionType;
+	}
+	public int getID() {
+		return this.msgID;
+	}
+	public String getMsg() {
+		return msg;
+	}
+	public int getContractType() {
+		return this.contractType;
+	}
+	public int getContractID() {
+		return this.contractID;
+	}
+	public byte getUnknown01() {
+		return this.unknown01;
+	}
+	public float getX() {
+		return this.x;
+	}
+	public float getY() {
+		return this.y;
+	}
+	public float getZ() {
+		return this.z;
+	}
+	public byte getUnknown02() {
+		return this.unknown02;
+	}
+	public ArrayList<PlacementInfo> getPlacementInfo() {
+		return this.placementInfo;
+	}
+	public PlacementInfo getFirstPlacementInfo() {
+		if (this.placementInfo.size() > 0)
+			return this.placementInfo.get(0);
+		return null;
+	}
+
+	public void setActionType(int value) {
+		this.actionType = value;
+	}
+	public void setMsgID(int value) {
+		this.msgID = value;
+	}
+	public void setMsg(String value) {
+		this.msg = value;
+	}
+	public void setContractType(int value) {
+		this.contractType = value;
+	}
+	public void setContractID(int value) {
+		this.contractID = value;
+	}
+	public void setUnknown01(byte value) {
+		this.unknown01 = value;
+	}
+	public void setX(float value) {
+		this.x = value;
+	}
+	public void setY(float value) {
+		this.y = value;
+	}
+	public void setZ(float value) {
+		this.z = value;
+	}
+	public void setUnknown02(byte value) {
+		this.unknown02 = value;
+	}
+	public void addPlacementInfo(int ID) {
+		PlacementInfo pi = new PlacementInfo(ID, 0f, 0f, 0f, 0f, 0f, 0f, 0f);
+		this.placementInfo.add(pi);
+	}
+
+	public static class PlacementInfo {
+		int blueprintUUID;
+		Vector3fImmutable loc;
+		float w;
+		Vector3fImmutable rot;
+		public PlacementInfo(int blueprintUUID, float locX, float locY, float locZ, float w, float rotX, float rotY, float rotZ) {
+			this.blueprintUUID = blueprintUUID;
+			this.loc = new Vector3fImmutable(locX, locY, locZ);
+			this.w = w;
+			this.rot = new Vector3fImmutable(rotX, rotY, rotZ);
+		}
+		public int getBlueprintUUID() {
+			return this.blueprintUUID;
+		}
+		public Vector3fImmutable getLoc() {
+			return this.loc;
+		}
+		public float getW() {
+			return this.w;
+		}
+		public Vector3fImmutable getRot() {
+			return this.rot;
+		}
+		public void setLoc(Vector3fImmutable loc) {
+			this.loc = loc;
+		}
+	}
+}
diff --git a/src/engine/net/client/msg/PowerProjectileMsg.java b/src/engine/net/client/msg/PowerProjectileMsg.java
new file mode 100644
index 00000000..83a0c1d1
--- /dev/null
+++ b/src/engine/net/client/msg/PowerProjectileMsg.java
@@ -0,0 +1,92 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+
+import engine.exception.SerializationException;
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.objects.AbstractWorldObject;
+
+
+public class PowerProjectileMsg extends ClientNetMsg {
+	private AbstractWorldObject source;
+	private AbstractWorldObject target;
+	private float range = 10;
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+
+	public PowerProjectileMsg(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.ARCPOWERPROJECTILE, origin, reader);
+	}
+
+	public PowerProjectileMsg() {
+		super(Protocol.ARCPOWERPROJECTILE);
+	}
+
+	public PowerProjectileMsg(AbstractWorldObject source,AbstractWorldObject target) {
+		super(Protocol.ARCPOWERPROJECTILE);
+		this.source = source;
+		this.target = target;
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+
+	}
+
+	// Pre-cache and configure values so they are available when we serialize
+
+	public void configure() {
+
+		if (this.source == null)
+			return;
+
+		if (this.target == null)
+			return;
+
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) throws SerializationException {
+
+		engine.math.Vector3fImmutable faceDir = this.source.getLoc().subtract2D(target.getLoc()).normalize();
+		engine.math.Vector3fImmutable newLoc =faceDir.scaleAdd(range, target.getLoc());
+
+		newLoc = newLoc.setY(newLoc.getY() + range);
+
+		writer.putInt(this.source.getObjectType().ordinal());
+		writer.putInt(this.source.getObjectUUID());
+		writer.putVector3f(newLoc);
+
+	}
+
+	public float getRange() {
+		return range;
+	}
+
+	public void setRange(float range) {
+		this.range = range;
+	}
+
+}
diff --git a/src/engine/net/client/msg/PromptRecallMsg.java b/src/engine/net/client/msg/PromptRecallMsg.java
new file mode 100644
index 00000000..40e3bf26
--- /dev/null
+++ b/src/engine/net/client/msg/PromptRecallMsg.java
@@ -0,0 +1,59 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+
+public class PromptRecallMsg extends ClientNetMsg {
+
+	private byte confirmation;
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public PromptRecallMsg() {
+		super(Protocol.ARCPROMPTRECALL);
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public PromptRecallMsg(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.ARCPROMPTRECALL, origin, reader);
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+		writer.put((byte)0x01); // tried 0 and 1
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		this.confirmation = reader.get();
+	}
+	
+	public boolean getConfirmed() {
+
+        return confirmation == 1;
+	}
+}
\ No newline at end of file
diff --git a/src/engine/net/client/msg/RandomMsg.java b/src/engine/net/client/msg/RandomMsg.java
new file mode 100644
index 00000000..e29d9932
--- /dev/null
+++ b/src/engine/net/client/msg/RandomMsg.java
@@ -0,0 +1,94 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+
+public class RandomMsg extends ClientNetMsg {
+
+	private int max;
+	private int roll;
+	private int sourceType;
+	private int sourceID;
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+
+	public RandomMsg() {
+		super(Protocol.RANDOM);
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the ByteBuffer into a message. If a BufferUnderflow occurs (based on reading past the limit) then this constructor Throws that Exception to the caller.
+	 */
+	public RandomMsg(AbstractConnection origin, ByteBufferReader reader)
+			 {
+		super(Protocol.RANDOM, origin, reader);
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+		writer.putInt(this.max);
+		writer.putInt(this.roll);
+		writer.putInt(this.sourceType);
+		writer.putInt(this.sourceID);
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		this.max = reader.getInt();
+		this.roll = reader.getInt();
+		this.sourceType = reader.getInt();
+		this.sourceID = reader.getInt();
+	}
+
+	public int getMax() {
+		return this.max;
+	}
+
+	public int getRoll() {
+		return this.roll;
+	}
+
+	public int getSourceType() {
+		return this.sourceType;
+	}
+
+	public int getSourceID() {
+		return this.sourceID;
+	}
+
+	public void setMax(int value) {
+		this.max = value;
+	}
+
+	public void setRoll(int value) {
+		this.roll = value;
+	}
+
+	public void setSourceType(int value) {
+		this.sourceType = value;
+	}
+
+	public void setSourceID(int value) {
+		this.sourceID = value;
+	}
+}
diff --git a/src/engine/net/client/msg/RecommendNationMsg.java b/src/engine/net/client/msg/RecommendNationMsg.java
new file mode 100644
index 00000000..663e2797
--- /dev/null
+++ b/src/engine/net/client/msg/RecommendNationMsg.java
@@ -0,0 +1,92 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+import engine.Enum.GameObjectType;
+import engine.exception.SerializationException;
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.objects.PlayerCharacter;
+
+
+
+public class RecommendNationMsg extends ClientNetMsg {
+
+
+	private int guildID;
+	private byte ally;
+	private byte enemy;
+
+
+
+
+	public RecommendNationMsg(PlayerCharacter player) {
+		super(Protocol.RECOMMENDNATION);
+
+	}
+
+	public RecommendNationMsg() {
+		super(Protocol.RECOMMENDNATION);
+	}
+
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public RecommendNationMsg(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.RECOMMENDNATION, origin, reader);
+	}
+	//CALL THIS AFTER SANITY CHECKS AND BEFORE UPDATING HEALTH/GOLD.
+
+
+	/**
+	 * Deserializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		reader.getInt();
+		this.guildID = reader.getInt();
+		this.ally = reader.get();
+		this.enemy = reader.get();
+	}
+
+
+	// Precache and configure this message before we serialize it
+
+
+	/**
+	 * Serializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) throws SerializationException {
+		writer.putInt(GameObjectType.Guild.ordinal());
+		writer.putInt(guildID);
+		writer.put(this.ally);
+		writer.put(this.enemy);
+	}
+
+	public int getGuildID() {
+		return guildID;
+	}
+
+	public byte getAlly() {
+		return ally;
+	}
+
+	public byte getEnemy() {
+		return enemy;
+	}
+
+}
diff --git a/src/engine/net/client/msg/RecvSummonsRequestMsg.java b/src/engine/net/client/msg/RecvSummonsRequestMsg.java
new file mode 100644
index 00000000..bb034063
--- /dev/null
+++ b/src/engine/net/client/msg/RecvSummonsRequestMsg.java
@@ -0,0 +1,111 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+
+public class RecvSummonsRequestMsg extends ClientNetMsg {
+
+	private int sourceType;
+	private int sourceID;
+	private String sourceName;
+	private String locationName; //where being summoned to
+	private boolean accepted;
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public RecvSummonsRequestMsg(int sourceType, int sourceID, String sourceName, String locationName, boolean accepted) {
+		super(Protocol.ARCSUMMON);
+		this.sourceType = sourceType;
+		this.sourceID = sourceID;
+		this.sourceName = sourceName;
+		this.locationName = locationName;
+		this.accepted = accepted;
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public RecvSummonsRequestMsg(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.ARCSUMMON, origin, reader);
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+		writer.putInt(this.sourceType);
+		writer.putInt(this.sourceID);
+		writer.putString(this.sourceName);
+		writer.putString(this.locationName);
+		writer.put(this.accepted ? (byte)1 : (byte)0);
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		this.sourceType = reader.getInt();
+		this.sourceID = reader.getInt();
+		this.sourceName = reader.getString();
+		this.locationName = reader.getString();
+		this.accepted = (reader.get() == 1) ? true : false;
+	}
+
+	public int getSourceType() {
+		return this.sourceType;
+	}
+
+	public int getSourceID() {
+		return this.sourceID;
+	}
+
+	public String getSourceName() {
+		return this.sourceName;
+	}
+
+	public String getLocationName() {
+		return this.locationName;
+	}
+
+	public boolean accepted() {
+		return this.accepted;
+	}
+
+	public void setSourceType(int value) {
+		this.sourceType = value;
+	}
+
+	public void setSourceID(int value) {
+		this.sourceID = value;
+	}
+
+	public void setSourceName(String value) {
+		this.sourceName = value;
+	}
+
+	public void setLocationName(String value) {
+		this.locationName = value;
+	}
+
+	public void setAccepted(boolean value) {
+		this.accepted = value;
+	}
+}
diff --git a/src/engine/net/client/msg/RecyclePowerMsg.java b/src/engine/net/client/msg/RecyclePowerMsg.java
new file mode 100644
index 00000000..841efe4a
--- /dev/null
+++ b/src/engine/net/client/msg/RecyclePowerMsg.java
@@ -0,0 +1,62 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+
+
+public class RecyclePowerMsg extends ClientNetMsg {
+
+	protected int token;
+
+	public RecyclePowerMsg(int token) {
+		super(Protocol.RECYCLEPOWER);
+		this.token = token;
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public RecyclePowerMsg(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.RECYCLEPOWER, origin, reader);
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+		writer.putInt(this.token);
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		this.token = reader.getInt();
+	}
+
+	public int getToken() {
+		return this.token;
+	}
+
+	public void setToken(int value) {
+		this.token = value;
+	}
+
+}
diff --git a/src/engine/net/client/msg/RefineMsg.java b/src/engine/net/client/msg/RefineMsg.java
new file mode 100644
index 00000000..f7757783
--- /dev/null
+++ b/src/engine/net/client/msg/RefineMsg.java
@@ -0,0 +1,226 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+
+import engine.gameManager.SessionManager;
+import engine.net.*;
+import engine.net.client.ClientConnection;
+import engine.net.client.Protocol;
+import engine.objects.*;
+import engine.server.MBServerStatics;
+
+import java.util.concurrent.ConcurrentHashMap;
+
+public class RefineMsg extends ClientNetMsg {
+
+	private int npcType;
+	private int npcID;
+	private int unknown01;
+	private int type;
+	private int token;
+	private int unknown02;
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public RefineMsg() {
+		super(Protocol.ARCUNTRAINABILITY);
+	}
+
+	public RefineMsg(int npcType, int npcID, int type, int token) {
+		super(Protocol.ARCUNTRAINABILITY);
+		this.npcType = npcType;
+		this.npcID = npcID;
+		this.unknown01 = 1;
+		this.type = type;
+		this.token = token;
+		this.unknown02 = 0;
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public RefineMsg(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.ARCUNTRAINABILITY, origin, reader);
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+		writer.putInt(this.npcType);
+		writer.putInt(this.npcID);
+		writer.putInt(this.unknown01);
+		writer.putInt(this.type);
+		writer.putInt(this.token);
+		writer.putInt(this.unknown02);
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		this.npcType = reader.getInt();
+		this.npcID = reader.getInt();
+		this.unknown01 = reader.getInt();
+		this.type = reader.getInt();
+		this.token = reader.getInt();
+		this.unknown02 = reader.getInt();
+	}
+
+	public int getNpcType() {
+		return this.npcType;
+	}
+
+	public int getNpcID() {
+		return this.npcID;
+	}
+
+	public int getUnknown01() {
+		return this.unknown01;
+	}
+
+	public int getType() {
+		return this.type;
+	}
+
+	public int getToken() {
+		return this.token;
+	}
+
+	public int getUnknown02() {
+		return this.unknown02;
+	}
+
+	public void setUnknown01(int value) {
+		this.unknown01 = value;
+	}
+
+	public void setType(int value) {
+		this.type = value;
+	}
+
+	public void setToken(int value) {
+		this.token = value;
+	}
+
+	public void setUnknown02(int value) {
+		this.unknown02 = value;
+	}
+
+	public static void refine(RefineMsg msg, ClientConnection origin) {
+		if (origin == null)
+			return;
+		PlayerCharacter pc = SessionManager.getPlayerCharacter(origin);
+		if (pc == null)
+			return;
+        NPC npc = NPC.getFromCache(msg.npcID);
+		if (npc == null)
+			return;
+        int type = msg.type;
+        int token = msg.token;
+		boolean worked = false;
+		boolean skillPower = true;
+		if (type == 0) { //refine skill
+			worked = refineSkill(origin, pc, token, msg);
+		} else if (type == 1) { //refine power
+			worked = refinePower(origin, pc, token, msg);
+		} else if (type == 2) { //refine stat
+			worked = refineStat(origin, pc, token, msg);
+			skillPower = false;
+		}
+
+		if (worked) {
+
+			//update player
+			pc.applyBonuses();
+			pc.getCharItemManager().RemoveEquipmentFromLackOfSkill(pc, true);
+
+			//echo refine message back
+
+			Dispatch dispatch = Dispatch.borrow(pc, msg);
+			DispatchMessage.dispatchMsgDispatch(dispatch, engine.Enum.DispatchChannel.SECONDARY);
+
+			//			if (type == 0 && token == 1488335491){
+			//				dispatch = Dispatch.borrow(pc, msg);
+			//				DispatchMessage.dispatchMsgDispatch(dispatch, engine.Enum.DispatchChannel.SECONDARY);
+			//			}
+
+			//resend refine screen
+
+			RefinerScreenMsg refinerScreenMsg = new RefinerScreenMsg(skillPower, npc.getSellPercent(pc)); //TODO set npc cost
+			dispatch = Dispatch.borrow(pc, refinerScreenMsg);
+			DispatchMessage.dispatchMsgDispatch(dispatch, engine.Enum.DispatchChannel.SECONDARY);
+		}
+	}
+
+	private static boolean refineSkill(ClientConnection origin, PlayerCharacter pc, int token, RefineMsg msg) {
+		CharacterSkill skill = null;
+		ConcurrentHashMap<String, CharacterSkill> skills = pc.getSkills();
+		for (CharacterSkill sk : skills.values()) {
+			if (sk == null)
+				continue;
+			SkillsBase sb = sk.getSkillsBase();
+			if (sb == null)
+				continue;
+			if (sb.getToken() == token)
+				skill = sk;
+		}
+		//check if player has skill to refine
+		if (skill == null)
+			return false;
+		//check there's a train to refine
+		if (skill.getNumTrains() < 1)
+			return false;
+
+		//TODO verify if any skills have this as prereq
+
+		//TODO verify if any powers have this as a prereq
+
+		//refine skill
+		return skill.refine(pc);
+	}
+
+	private static boolean refinePower(ClientConnection origin, PlayerCharacter pc, int token, RefineMsg msg) {
+		CharacterPower power = null;
+		ConcurrentHashMap<Integer, CharacterPower> powers = pc.getPowers();
+		if (!powers.containsKey(token))
+			return false;
+		power = powers.get(token);
+		if (power == null)
+			return false;
+		if (power.getTrains() < 1)
+			return false;
+
+		//TODO verify if any powers have this as a prereq
+
+		return power.refine(pc);
+	}
+
+	private static boolean refineStat(ClientConnection origin, PlayerCharacter pc, int token, RefineMsg msg) {
+		if (token == MBServerStatics.STAT_STR_ID)
+			return pc.refineStr();
+		if (token == MBServerStatics.STAT_DEX_ID)
+			return pc.refineDex();
+		if (token == MBServerStatics.STAT_CON_ID)
+			return pc.refineCon();
+		if (token == MBServerStatics.STAT_INT_ID)
+			return pc.refineInt(msg);
+		if (token == MBServerStatics.STAT_SPI_ID)
+			return pc.refineSpi();
+		return false;
+	}
+}
diff --git a/src/engine/net/client/msg/RefinerScreenMsg.java b/src/engine/net/client/msg/RefinerScreenMsg.java
new file mode 100644
index 00000000..4895674a
--- /dev/null
+++ b/src/engine/net/client/msg/RefinerScreenMsg.java
@@ -0,0 +1,117 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+
+public class RefinerScreenMsg extends ClientNetMsg {
+
+	private int npcType;
+	private int npcID;
+	private int unknown01; //might be - 0: skills/powers, 2: stats
+	private float unknown02; //cost to refine
+	private int unknown03;
+	private int unknown04;
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public RefinerScreenMsg(boolean skillPower, float cost) {
+		super(Protocol.ARCUNTRAINLIST);
+		if (skillPower)
+			this.unknown01 = 0; //skill/power screen
+		else
+			this.unknown01 = 2; //stat screen
+		this.unknown02 = cost;
+		this.unknown03 = 0;
+		this.unknown04 = 0;
+	}
+
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public RefinerScreenMsg(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.ARCUNTRAINLIST, origin, reader);
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+		writer.putInt(this.npcType);
+		writer.putInt(this.npcID);
+		writer.putInt(this.unknown01);
+		writer.putFloat(this.unknown02);
+		writer.putInt(this.unknown03);
+		writer.putInt(this.unknown04);
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		this.npcType = reader.getInt();
+		this.npcID = reader.getInt();
+		this.unknown01 = reader.getInt();
+		this.unknown02 = reader.getInt();
+		this.unknown03 = reader.getInt();
+		this.unknown04 = reader.getInt();
+	}
+
+	public int getNpcType() {
+		return this.npcType;
+	}
+
+	public int getNpcID() {
+		return this.npcID;
+	}
+
+	public int getUnknown01() {
+		return this.unknown01;
+	}
+
+	public float getUnknown02() {
+		return this.unknown02;
+	}
+
+	public int getUnknown03() {
+		return this.unknown03;
+	}
+
+	public int getUnknown04() {
+		return this.unknown04;
+	}
+
+	public void setUnknown01(int value) {
+		this.unknown01 = value;
+	}
+
+	public void setUnknown02(int value) {
+		this.unknown02 = value;
+	}
+
+	public void setUnknown03(int value) {
+		this.unknown03 = value;
+	}
+
+	public void setUnknown04(int value) {
+		this.unknown04 = value;
+	}
+}
diff --git a/src/engine/net/client/msg/RejectTradeRequestMsg.java b/src/engine/net/client/msg/RejectTradeRequestMsg.java
new file mode 100644
index 00000000..8b847e17
--- /dev/null
+++ b/src/engine/net/client/msg/RejectTradeRequestMsg.java
@@ -0,0 +1,113 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+
+import engine.exception.SerializationException;
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+
+
+/**
+ * Reject trade request
+ *
+ * @author Eighty
+ */
+public class RejectTradeRequestMsg extends ClientNetMsg {
+
+	private int unknown01; //pad?
+	private long playerCompID;
+	private long targetCompID;
+
+	/**
+	 * This is the general purpose constructor
+	 */
+	public RejectTradeRequestMsg(int unknown01, long playerCompID, long targetCompID) {
+		super(Protocol.REQUESTTRADECANCEL);
+		this.unknown01 = unknown01;
+		this.playerCompID = playerCompID;
+		this.targetCompID = targetCompID;
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the ByteBuffer into a message. If a BufferUnderflow occurs (based on reading past the limit) then this constructor Throws that Exception to the caller.
+	 */
+	public RejectTradeRequestMsg(AbstractConnection origin, ByteBufferReader reader)
+			 {
+		super(Protocol.REQUESTTRADECANCEL, origin, reader);
+	}
+
+	/**
+	 * Deserializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)
+			 {
+		unknown01 = reader.getInt();
+		playerCompID = reader.getLong();
+		targetCompID = reader.getLong();
+	}
+
+	/**
+	 * Serializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer)
+			throws SerializationException {
+		writer.putInt(unknown01);
+		writer.putLong(playerCompID);
+		writer.putLong(targetCompID);
+	}
+
+	/**
+	 * @return the unknown01
+	 */
+	public int getUnknown01() {
+		return unknown01;
+	}
+
+	/**
+	 * @param unknown01 the unknown01 to set
+	 */
+	public void setUnknown01(int unknown01) {
+		this.unknown01 = unknown01;
+	}
+
+	/**
+	 * @return the playerCompID
+	 */
+	public long getPlayerCompID() {
+		return playerCompID;
+	}
+
+	/**
+	 * @param playerCompID the playerCompID to set
+	 */
+	public void setPlayerCompID(long playerCompID) {
+		this.playerCompID = playerCompID;
+	}
+
+	/**
+	 * @return the targetCompID
+	 */
+	public long getTargetCompID() {
+		return targetCompID;
+	}
+
+	/**
+	 * @param targetCompID the targetCompID to set
+	 */
+	public void setTargetCompID(long targetCompID) {
+		this.targetCompID = targetCompID;
+	}
+
+}
diff --git a/src/engine/net/client/msg/RemoveFriendMessage.java b/src/engine/net/client/msg/RemoveFriendMessage.java
new file mode 100644
index 00000000..7e96f4c7
--- /dev/null
+++ b/src/engine/net/client/msg/RemoveFriendMessage.java
@@ -0,0 +1,67 @@
+/*
+HashSet<Integer> playerFriendSet = PlayerFriendsMap.get(playerUID);
+			playerFriendSet.add(friendUID); * Copyright 2013 MagicBane Emulator Project
+ * All Rights Reserved
+ */
+package engine.net.client.msg;
+
+
+import engine.Enum.GameObjectType;
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+
+
+public class RemoveFriendMessage extends ClientNetMsg {
+
+	public int friendID;
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public RemoveFriendMessage(int friendID) {
+		super(Protocol.REMOVEFRIEND);
+		this.friendID = friendID;
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public RemoveFriendMessage(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.REMOVEFRIEND, origin, reader);
+	}
+
+	/**
+	 * Copy constructor
+	 */
+	public RemoveFriendMessage(RemoveFriendMessage msg) {
+		super(Protocol.REMOVEFRIEND);
+	}
+	
+	
+
+	
+	
+	/**
+	 * Deserializes the subclass specific items from the supplied ByteBufferReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		//Do we even want to try this?
+		reader.getInt();
+		this.friendID = reader.getInt();
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied ByteBufferWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+	writer.putInt(GameObjectType.PlayerCharacter.ordinal());
+	writer.putInt(this.friendID);
+	}
+}
diff --git a/src/engine/net/client/msg/RepairBuildingMsg.java b/src/engine/net/client/msg/RepairBuildingMsg.java
new file mode 100644
index 00000000..a11cb39a
--- /dev/null
+++ b/src/engine/net/client/msg/RepairBuildingMsg.java
@@ -0,0 +1,105 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+import engine.Enum.GameObjectType;
+import engine.exception.SerializationException;
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+
+
+public class RepairBuildingMsg extends ClientNetMsg {
+
+	private int type;
+	private int buildingID;
+	private int maxHP;
+	private int missingHealth;
+	private int strongBox;
+	private int repairCost;
+
+
+
+	public RepairBuildingMsg(int buildingID, int maxHP, int missingHealth,int repairCost,int strongBox) {
+		super(Protocol.REPAIRBUILDING);
+		this.buildingID = buildingID;
+		this.maxHP = maxHP;
+		this.missingHealth = missingHealth;
+		this.repairCost = repairCost;
+		this.strongBox = strongBox;
+	}
+
+	public RepairBuildingMsg() {
+		super(Protocol.REPAIRBUILDING);
+	}
+
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public RepairBuildingMsg(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.REPAIRBUILDING, origin, reader);
+	}
+	//CALL THIS AFTER SANITY CHECKS AND BEFORE UPDATING HEALTH/GOLD.
+
+
+	/**
+	 * Deserializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		this.type = reader.getInt();
+		reader.getInt(); //Building Type
+		this.buildingID = reader.getInt();
+		reader.getInt();
+		reader.getInt();
+		reader.getInt();
+		reader.getInt();
+		reader.getInt();
+
+	}
+
+
+	// Precache and configure this message before we serialize it
+
+
+	/**
+	 * Serializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) throws SerializationException {
+		writer.putInt(0);
+		writer.putInt(GameObjectType.Building.ordinal());
+		writer.putInt(this.buildingID);
+		writer.putInt(this.maxHP);
+		writer.putInt(this.strongBox);
+		writer.putInt(0); //?
+		writer.putInt(this.repairCost);
+		writer.putInt(this.missingHealth);
+
+	}
+
+
+
+	public int getBuildingID() {
+		return buildingID;
+	}
+
+	public void setBuildingID(int buildingID) {
+		this.buildingID = buildingID;
+	}
+	public int getType() {
+		return type;
+	}
+}
diff --git a/src/engine/net/client/msg/RepairMsg.java b/src/engine/net/client/msg/RepairMsg.java
new file mode 100644
index 00000000..4d91a28d
--- /dev/null
+++ b/src/engine/net/client/msg/RepairMsg.java
@@ -0,0 +1,218 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+
+import engine.exception.SerializationException;
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.objects.Contract;
+import engine.objects.NPC;
+
+import java.util.ArrayList;
+
+/**
+ * Sell to NPC window msg
+ *
+ * @author 
+ */
+public class RepairMsg extends ClientNetMsg {
+
+//Item Types:
+//1: weapon
+//2: armor/cloth/shield
+//5: scrolls
+//8: potions
+//10: charters
+//13: Jewelry
+
+	private int msgType;
+	//static 0
+	private int unknown01; //0 or 10
+	private int npcType;
+	private int npcID;
+	private NPC npc = null;
+	private int itemType;
+	private int itemID;
+	private int amountRepaired;
+	//static 0
+	//static 0 //end repair req/ack here
+	//01 inc, 00 out
+	//item list
+	//skill list
+	//unk list
+	//static 0 //out
+	//static 0 //out
+	//static 10000 out?
+	//static 0
+
+	/**
+	 * This is the general purpose constructor
+	 */
+	public RepairMsg() {
+		super(Protocol.REPAIROBJECT);
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the ByteBuffer into a message. If a BufferUnderflow occurs (based on reading past the limit) then this constructor Throws that Exception to the caller.
+	 */
+	public RepairMsg(AbstractConnection origin, ByteBufferReader reader)
+			 {
+		super(Protocol.REPAIROBJECT, origin, reader);
+	}
+
+	/**
+	 * Deserializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)
+			 {
+
+		this.msgType = reader.getInt();
+		reader.getInt();
+		this.unknown01 = reader.getInt();
+		this.npcType = reader.getInt();
+		this.npcID = reader.getInt();
+		this.itemType = reader.getInt();
+		this.itemID = reader.getInt();
+		this.amountRepaired = reader.getInt();
+		reader.getInt();
+		reader.getInt();
+
+		if (this.msgType == 1) {
+			reader.get(); //0x00 inc
+			int size = reader.getInt();
+			for (int i=0;i<size;i++)
+				reader.getInt();
+			size = reader.getInt();
+			for (int i=0;i<size;i++)
+				reader.getInt();
+			size = reader.getInt();
+			for (int i=0;i<size;i++)
+				reader.getInt();
+			reader.getInt();
+			reader.getInt();
+			reader.getInt();
+			reader.getInt();
+		}
+	}
+
+	/**
+	 * Serializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer)
+			throws SerializationException {
+		writer.putInt(this.msgType);
+		writer.putInt(0);
+		writer.putInt(this.unknown01);
+		writer.putInt(this.npcType);
+		writer.putInt(this.npcID);
+		writer.putInt(this.itemType);
+		writer.putInt(this.itemID);
+		writer.putInt(this.amountRepaired);
+		writer.putInt(0);
+		writer.putInt(0);
+		if (this.msgType == 1) {
+			writer.put((byte)0x00);
+			Contract c = (npc != null) ? npc.getContract() : null;
+			if (c != null) {
+				ArrayList<Integer> list = c.getBuyItemType();
+				writer.putInt(list.size());
+				for (int l : list)
+					writer.putInt(l);
+				list = c.getBuySkillToken();
+				writer.putInt(list.size());
+				for (int l : list)
+					writer.putInt(l);
+				list = c.getBuyUnknownToken();
+				writer.putInt(list.size());
+				for (int l : list)
+					writer.putInt(l);
+			} else {
+				writer.putInt(0);
+				writer.putInt(0);
+				writer.putInt(0);
+			}
+			writer.putInt(0);
+			writer.putInt(0);
+			writer.putInt(10000);
+			writer.putInt(0);
+		}
+	}
+
+	public int getMsgType() {
+		return this.msgType;
+	}
+
+	public int getNPCType() {
+		return this.npcType;
+	}
+
+	public int getNPCID() {
+		return this.npcID;
+	}
+
+	public int getItemType() {
+		return this.itemType;
+	}
+
+	public int getItemID() {
+		return this.itemID;
+	}
+
+	public int getAmountRepaired() {
+		return this.amountRepaired;
+	}
+
+	public NPC getNPC() {
+		return this.npc;
+	}
+
+	public void setMsgType(int value) {
+		this.msgType = value;
+	}
+
+	public void setNPCType(int value) {
+		this.npcType = value;
+	}
+
+	public void setNPCID(int value) {
+		this.npcID = value;
+	}
+
+	public void setItemType(int value) {
+		this.itemType = value;
+	}
+
+	public void setItemID(int value) {
+		this.itemID = value;
+	}
+
+	public void setAmountRrepaired(int value) {
+		this.amountRepaired = value;
+	}
+
+	public void setNPC(NPC value) {
+		this.npc = value;
+	}
+
+	public void setupRepairAck(int amountRepaired) {
+		this.unknown01 = 10;
+		this.amountRepaired = amountRepaired;
+	}
+
+	public void setRepairWindowAck(NPC npc) {
+		this.unknown01 = 10;
+		this.npc = npc;
+	}
+}
diff --git a/src/engine/net/client/msg/ReqBankInventoryMsg.java b/src/engine/net/client/msg/ReqBankInventoryMsg.java
new file mode 100644
index 00000000..5fc06443
--- /dev/null
+++ b/src/engine/net/client/msg/ReqBankInventoryMsg.java
@@ -0,0 +1,87 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+
+import engine.exception.SerializationException;
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.objects.AbstractGameObject;
+
+/**
+ * Bank inventory requested
+ *
+ * @author Burfo
+ */
+public class ReqBankInventoryMsg extends ClientNetMsg {
+	
+	private int playerType;
+	private int playerID;
+	private long unknown01; // possibly NPC ID?
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public ReqBankInventoryMsg(AbstractGameObject ago, long unknown01) {
+		super(Protocol.OKCOSTTOOPENBANK);
+		this.playerType = ago.getObjectType().ordinal();
+		this.playerID = ago.getObjectUUID();
+		this.unknown01 = unknown01;
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public ReqBankInventoryMsg(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.OKCOSTTOOPENBANK, origin, reader);
+	}
+
+	/**
+	 * Deserializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		playerType = reader.getInt();
+		playerID = reader.getInt();
+		unknown01 = reader.getLong();
+	}
+
+	/**
+	 * Serializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) throws SerializationException {
+		writer.putInt(playerType);
+		writer.putInt(playerID);
+		writer.putLong(unknown01);
+	}
+
+	public int getPlayerType() {
+		return playerType;
+	}
+
+	public void setPlayerType(int playerType) {
+		this.playerType = playerType;
+	}
+
+	public int getPlayerID() {
+		return playerID;
+	}
+
+	public void setPlayerID(int playerID) {
+		this.playerID = playerID;
+	}
+        
+}
diff --git a/src/engine/net/client/msg/RequestBallListMessage.java b/src/engine/net/client/msg/RequestBallListMessage.java
new file mode 100644
index 00000000..0483476e
--- /dev/null
+++ b/src/engine/net/client/msg/RequestBallListMessage.java
@@ -0,0 +1,73 @@
+/*
+HashSet<Integer> playerFriendSet = PlayerFriendsMap.get(playerUID);
+			playerFriendSet.add(friendUID); * Copyright 2013 MagicBane Emulator Project
+ * All Rights Reserved
+ */
+package engine.net.client.msg;
+
+
+import engine.Enum.GameObjectType;
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.objects.PlayerCharacter;
+
+
+public class RequestBallListMessage extends ClientNetMsg {
+
+	public int playerID;
+	public String errorMessage;
+	public int msgType;
+	
+	public static int REQUEST = 0;
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public RequestBallListMessage(PlayerCharacter pc) {
+		super(Protocol.REQUESTBALLLIST);
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public RequestBallListMessage(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.REQUESTBALLLIST, origin, reader);
+	}
+
+	/**
+	 * Copy constructor
+	 */
+	public RequestBallListMessage(RequestBallListMessage msg) {
+		super(Protocol.REQUESTBALLLIST);
+	}
+
+	
+	
+	/**
+	 * Deserializes the subclass specific items from the supplied ByteBufferReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		reader.getInt();
+		this.playerID = reader.getInt();
+		this.msgType = reader.getInt();
+		this.errorMessage = reader.getString();
+		
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied ByteBufferWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+		writer.putInt(GameObjectType.PlayerCharacter.ordinal());
+		writer.putInt(this.playerID);
+		writer.putInt(this.msgType);
+		writer.putString(this.errorMessage);
+	}
+}
diff --git a/src/engine/net/client/msg/RequestEnterWorldMsg.java b/src/engine/net/client/msg/RequestEnterWorldMsg.java
new file mode 100644
index 00000000..47af6075
--- /dev/null
+++ b/src/engine/net/client/msg/RequestEnterWorldMsg.java
@@ -0,0 +1,71 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+
+public class RequestEnterWorldMsg extends ClientNetMsg {
+
+	private byte pad;
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public RequestEnterWorldMsg() {
+		super(Protocol.ENTERWORLD);
+		this.pad = 0x00;
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public RequestEnterWorldMsg(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.ENTERWORLD, origin, reader);
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+		writer.put(this.pad);
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		this.pad = reader.get();
+	}
+
+	/**
+	 * @return the pad
+	 */
+	public byte getPad() {
+		return pad;
+	}
+
+	/**
+	 * @param pad
+	 *            the pad to set
+	 */
+	public void setPad(byte pad) {
+		this.pad = pad;
+	}
+
+}
diff --git a/src/engine/net/client/msg/RespawnMsg.java b/src/engine/net/client/msg/RespawnMsg.java
new file mode 100644
index 00000000..21419e47
--- /dev/null
+++ b/src/engine/net/client/msg/RespawnMsg.java
@@ -0,0 +1,100 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+
+public class RespawnMsg extends ClientNetMsg {
+
+	protected int objectType;
+	protected int objectID;
+	protected float playerHealth;
+	protected int playerExp;
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public RespawnMsg() {
+		super(Protocol.RESETAFTERDEATH);
+		this.objectType = 0;
+		this.objectID = 0;
+		this.playerHealth = 0;
+		this.playerExp = 0;
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public RespawnMsg(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.RESETAFTERDEATH, origin, reader);
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+		writer.putInt(this.objectType);
+		writer.putInt(this.objectID);
+		writer.putFloat(this.playerHealth);
+		writer.putInt(this.playerExp);
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+
+		this.objectType = reader.getInt();
+		this.objectID = reader.getInt();
+		this.playerHealth = reader.getFloat();
+		this.playerExp = reader.getInt();
+	}
+
+	public int getObjectType() {
+		return this.objectType;
+	}
+
+	public int getObjectID() {
+		return this.objectID;
+	}
+
+	public float getPlayerHealth() {
+		return this.playerHealth;
+	}
+
+	public int getPlayerExp() {
+		return this.playerExp;
+	}
+
+	public void setObjectType(int value) {
+		this.objectType = value;
+	}
+
+	public void setObjectID(int value) {
+		this.objectID = value;
+	}
+
+	public void setPlayerHealth(float value) {
+		this.playerHealth = value;
+	}
+
+	public void setPlayerExp(int value) {
+		this.playerExp = value;
+	}
+}
diff --git a/src/engine/net/client/msg/RespondLeaveWorldMsg.java b/src/engine/net/client/msg/RespondLeaveWorldMsg.java
new file mode 100644
index 00000000..0268925f
--- /dev/null
+++ b/src/engine/net/client/msg/RespondLeaveWorldMsg.java
@@ -0,0 +1,52 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+
+
+public class RespondLeaveWorldMsg extends ClientNetMsg {
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public RespondLeaveWorldMsg() {
+		super(Protocol.LEAVEWORLD);
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public RespondLeaveWorldMsg(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.LEAVEWORLD, origin, reader);
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+	}
+
+}
diff --git a/src/engine/net/client/msg/RotateObjectMsg.java b/src/engine/net/client/msg/RotateObjectMsg.java
new file mode 100644
index 00000000..30a9677a
--- /dev/null
+++ b/src/engine/net/client/msg/RotateObjectMsg.java
@@ -0,0 +1,61 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.objects.Mob;
+
+public class RotateObjectMsg extends ClientNetMsg {
+
+
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public RotateObjectMsg(int type, Mob pet) {
+		super(Protocol.ROTATEMSG);
+	
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public RotateObjectMsg(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.ROTATEMSG, origin, reader);
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+	
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		reader.getInt();
+		reader.getInt();
+		reader.getFloat();
+		reader.getFloat();
+		reader.getFloat();
+		reader.getInt();
+	}
+}
diff --git a/src/engine/net/client/msg/SafeModeMsg.java b/src/engine/net/client/msg/SafeModeMsg.java
new file mode 100644
index 00000000..3286e0a5
--- /dev/null
+++ b/src/engine/net/client/msg/SafeModeMsg.java
@@ -0,0 +1,72 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+
+
+public class SafeModeMsg extends ClientNetMsg {
+
+	private String message;
+
+	/**
+	 * Helper Constructor.
+	 *
+	 * This is the general purpose constructor.
+	 */
+	public SafeModeMsg() {
+		this("Safe Mode");
+	}
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public SafeModeMsg(String message) {
+		super(Protocol.SAFEMODE);
+		this.message = message;
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public SafeModeMsg(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.SAFEMODE, origin, reader);
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+		writer.putString(this.message);
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		this.message = reader.getString();
+	}
+
+	/**
+	 * @return the message
+	 */
+	public String getMessage() {
+		return message;
+	}
+}
diff --git a/src/engine/net/client/msg/ScaleObjectMsg.java b/src/engine/net/client/msg/ScaleObjectMsg.java
new file mode 100644
index 00000000..f363672b
--- /dev/null
+++ b/src/engine/net/client/msg/ScaleObjectMsg.java
@@ -0,0 +1,99 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+
+public class ScaleObjectMsg extends ClientNetMsg {
+
+	private long compID;
+	private float scaleX;
+	private float scaleY;
+	private float scaleZ;
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public ScaleObjectMsg(long compID, float scaleX, float scaleY, float scaleZ) {
+		super(Protocol.SCALEOBJECT);
+		this.compID = compID;
+		this.scaleX = scaleX;
+		this.scaleY = scaleY;
+		this.scaleZ = scaleZ;
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public ScaleObjectMsg(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.SCALEOBJECT, origin, reader);
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+		writer.putLong(this.compID);
+		writer.putFloat(this.scaleX);
+		writer.putFloat(this.scaleY);
+		writer.putFloat(this.scaleZ);
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		this.compID = reader.getLong();
+		this.scaleX = reader.getFloat();
+		this.scaleY = reader.getFloat();
+		this.scaleZ = reader.getFloat();
+	}
+
+	public long getCompID() {
+		return this.compID;
+	}
+
+	public float getScaleX() {
+		return this.scaleX;
+	}
+
+	public float getScaleY() {
+		return this.scaleY;
+	}
+
+	public float getScaleZ() {
+		return this.scaleZ;
+	}
+
+	public void setCompID(long value) {
+		this.compID = value;
+	}
+
+	public void setScaleX(float value) {
+		this.scaleX = value;
+	}
+
+	public void setScaleY(float value) {
+		this.scaleY = value;
+	}
+
+	public void setScaleZ(float value) {
+		this.scaleZ = value;
+	}
+}
diff --git a/src/engine/net/client/msg/SelectCityMsg.java b/src/engine/net/client/msg/SelectCityMsg.java
new file mode 100644
index 00000000..5e96a644
--- /dev/null
+++ b/src/engine/net/client/msg/SelectCityMsg.java
@@ -0,0 +1,93 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+
+import engine.net.AbstractConnection;
+import engine.net.AbstractNetMsg;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.objects.PlayerCharacter;
+
+
+public class SelectCityMsg extends ClientNetMsg {
+
+	private PlayerCharacter pc;
+	private boolean isTeleport;
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public SelectCityMsg(PlayerCharacter pc, boolean isTeleport) {
+		super(Protocol.SELECTCITY);
+		this.pc = pc;
+		this.isTeleport = isTeleport;
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public SelectCityMsg(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.SELECTCITY, origin, reader);
+	}
+
+	/**
+	 * Copy constructor
+	 */
+	public SelectCityMsg(SelectCityMsg msg) {
+		super(Protocol.SELECTCITY);
+		this.pc = msg.pc;
+		this.isTeleport = msg.isTeleport;
+	}
+
+	/**
+	 * @see AbstractNetMsg#getPowerOfTwoBufferSize()
+	 */
+	@Override
+	protected int getPowerOfTwoBufferSize() {
+		// Larger size for historically larger opcodes
+		return (12);
+	}
+	/**
+	 * Deserializes the subclass specific items from the supplied ByteBufferReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		//Do we even want to try this?
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied ByteBufferWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+	writer.put((byte)0);
+	}
+
+	public PlayerCharacter pc() {
+		return this.pc;
+	}
+
+	public boolean isTeleport() {
+		return this.isTeleport;
+	}
+
+	public void setPC(PlayerCharacter pc) {
+		this.pc = pc;
+	}
+
+	public void setIsTeleport(boolean value) {
+		this.isTeleport = value;
+	}
+}
diff --git a/src/engine/net/client/msg/SellToNPCMsg.java b/src/engine/net/client/msg/SellToNPCMsg.java
new file mode 100644
index 00000000..0c28f003
--- /dev/null
+++ b/src/engine/net/client/msg/SellToNPCMsg.java
@@ -0,0 +1,115 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+
+import engine.exception.SerializationException;
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+
+/**
+ * Buy from NPC msg
+ *
+ * @author Eighty
+ */
+public class SellToNPCMsg extends ClientNetMsg {
+
+	int npcType;
+	int npcID;
+	int itemType;
+	int itemID;
+	int unknown01;
+
+	/**
+	 * This is the general purpose constructor
+	 */
+	public SellToNPCMsg() {
+		super(Protocol.SELLOBJECT);
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public SellToNPCMsg(AbstractConnection origin, ByteBufferReader reader)
+			 {
+		super(Protocol.SELLOBJECT, origin, reader);
+	}
+
+	/**
+	 * Deserializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)
+			 {
+		this.npcType = reader.getInt();
+		this.npcID = reader.getInt();
+		this.itemType = reader.getInt();
+		this.itemID = reader.getInt();
+		this.unknown01 = reader.getInt();
+	}
+
+	/**
+	 * Serializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer)
+			throws SerializationException {
+		writer.putInt(this.npcType);
+		writer.putInt(this.npcID);
+		writer.putInt(this.itemType);
+		writer.putInt(this.itemID);
+		writer.putInt(this.unknown01);
+	}
+
+	public int getNPCType() {
+		return this.npcType;
+	}
+
+	public int getNPCID() {
+		return this.npcID;
+	}
+
+	public int getItemType() {
+		return this.itemType;
+	}
+
+	public int getItemID() {
+		return this.itemID;
+	}
+
+	public int getUnknown01() {
+		return this.unknown01;
+	}
+
+	public void setNPCType(int value) {
+		this.npcType = value;
+	}
+
+	public void setNPCID(int value) {
+		this.npcID = value;
+	}
+
+	public void setItemType(int value) {
+		this.itemType = value;
+	}
+
+	public void setItemID(int value) {
+		this.itemID = value;
+	}
+
+	public void setUnknown01(int value) {
+		this.unknown01 = value;
+	}
+}
diff --git a/src/engine/net/client/msg/SellToNPCWindowMsg.java b/src/engine/net/client/msg/SellToNPCWindowMsg.java
new file mode 100644
index 00000000..b9a30c74
--- /dev/null
+++ b/src/engine/net/client/msg/SellToNPCWindowMsg.java
@@ -0,0 +1,289 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+
+import engine.exception.SerializationException;
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+
+import java.util.ArrayList;
+
+
+/**
+ * Sell to NPC window msg
+ *
+ * @author Eighty
+ */
+public class SellToNPCWindowMsg extends ClientNetMsg {
+
+//Item Types:
+//1: weapon
+//2: armor/cloth/shield
+//5: scrolls
+//8: potions
+//10: charters
+//13: Jewelry
+
+
+	private int npcType;
+	private int npcID;
+	private byte unknownByte01; //so far always 0x00
+	private byte unknownByte02; //0: show only specified, 1: show all
+	private ArrayList<Integer> itemTypes;
+	private ArrayList<Integer> skillTokens;
+	private ArrayList<Integer> unknownArray02;
+	private int unknown01; //so far always 0
+	private int unknown02; //so far always 0 on output
+	private int unknown03; //so far always 10000
+	private int unknown04; //so far always 0 on output
+	private float unknown05; //suspect sell percentage, ex: 0.26 for 26%
+	private int unknown06; //suspect gold available on vendor
+	private int unknown07; //so far always 0
+
+	/**
+	 * This is the general purpose constructor
+	 */
+	public SellToNPCWindowMsg(int npcType, int npcID, byte unknownByte02,
+							  ArrayList<Integer> itemTypes, ArrayList<Integer> skillTokens,
+							  ArrayList<Integer> unknownArray02, float unknown05, int unknown06) {
+		super(Protocol.SHOPINFO);
+		this.npcType = npcType;
+		this.npcID = npcID;
+		this.unknownByte01 = (byte)0;
+		this.unknownByte02 = unknownByte02;
+		this.itemTypes = itemTypes;
+		this.skillTokens = skillTokens;
+		this.unknownArray02 = unknownArray02;
+		this.unknown01 = 0;
+		this.unknown02 = 0;
+		this.unknown03 = 10000;
+		this.unknown04 = 0;
+		this.unknown05 = unknown05;
+		this.unknown06 = unknown06;
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the ByteBuffer into a message. If a BufferUnderflow occurs (based on reading past the limit) then this constructor Throws that Exception to the caller.
+	 */
+	public SellToNPCWindowMsg(AbstractConnection origin, ByteBufferReader reader)
+			 {
+		super(Protocol.SHOPINFO, origin, reader);
+	}
+
+	/**
+	 * Deserializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)
+			 {
+
+		this.itemTypes = new ArrayList<>();
+		this.skillTokens = new ArrayList<>();
+		this.unknownArray02 = new ArrayList<>();
+
+		this.npcType = reader.getInt();
+		this.npcID = reader.getInt();
+		this.unknownByte01 = reader.get();
+		this.unknownByte02 = reader.get();
+		int cnt = reader.getInt();
+		for (int i=0; i<cnt; i++)
+			this.itemTypes.add(reader.getInt());
+		cnt = reader.getInt();
+		for (int i=0; i<cnt; i++)
+			this.skillTokens.add(reader.getInt());
+		cnt = reader.getInt();
+		for (int i=0; i<cnt; i++)
+			this.unknownArray02.add(reader.getInt());
+		this.unknown01 = reader.getInt();
+		this.unknown02 = reader.getInt();
+		this.unknown03 = reader.getInt();
+		this.unknown04 = reader.getInt();
+		this.unknown05 = reader.getFloat();
+		this.unknown06 = reader.getInt();
+		this.unknown07 = reader.getInt();
+	}
+
+	/**
+	 * Serializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer)
+			throws SerializationException {
+		writer.putInt(this.npcType);
+		writer.putInt(this.npcID);
+		writer.put(this.unknownByte01);
+		writer.put(this.unknownByte02);
+		writer.putInt(this.itemTypes.size());
+		for (Integer i : this.itemTypes)
+			writer.putInt(i);
+		writer.putInt(this.skillTokens.size());
+		for (Integer i : this.skillTokens)
+			writer.putInt(i);
+		writer.putInt(this.unknownArray02.size());
+		for (Integer i : this.unknownArray02)
+			writer.putInt(i);
+		writer.putInt(this.unknown01);
+		writer.putInt(this.unknown02);
+		writer.putInt(this.unknown03);
+		writer.putInt(this.unknown04);
+		writer.putFloat(this.unknown05);
+		writer.putInt(this.unknown06);
+		writer.putInt(this.unknown07);
+	}
+
+	public int getNPCType() {
+		return this.npcType;
+	}
+
+	public int getNPCID() {
+		return this.npcID;
+	}
+
+	public byte getUnknownByte01() {
+		return this.unknownByte01;
+	}
+
+	public byte getUnknownByte02() {
+		return this.unknownByte02;
+	}
+
+	public ArrayList<Integer> getItemTypes() {
+		return this.itemTypes;
+	}
+
+	public ArrayList<Integer> getSkillTokens() {
+		return this.skillTokens;
+	}
+
+	public ArrayList<Integer> getUnknownArray02() {
+		return this.unknownArray02;
+	}
+
+	public int getUnknown01() {
+		return unknown01;
+	}
+
+	public int getUnknown02() {
+		return unknown02;
+	}
+
+	public int getUnknown03() {
+		return unknown03;
+	}
+
+	public int getUnknown04() {
+		return unknown04;
+	}
+
+	public float getUnknown05() {
+		return unknown05;
+	}
+
+	public int getUnknown06() {
+		return unknown06;
+	}
+
+	public int getUnknown07() {
+		return unknown07;
+	}
+
+	public void setNPCType(int value) {
+		this.npcType = value;
+	}
+
+	public void setNPCID(int value) {
+		this.npcID = value;
+	}
+
+	public void setUnknownByte01(byte value) {
+		this.unknownByte01 = value;
+	}
+
+	public void setUnknownByte02(byte value) {
+		this.unknownByte02 = value;
+	}
+
+	public void setItemTypes(ArrayList<Integer> value) {
+		this.itemTypes = value;
+	}
+
+	public void setskillTokens(ArrayList<Integer> value) {
+		this.skillTokens = value;
+	}
+
+	public void setUnknownArray02(ArrayList<Integer> value) {
+		this.unknownArray02 = value;
+	}
+
+	public void setUnknown01(int value) {
+		this.unknown01 = value;
+	}
+
+	public void setUnknown02(int value) {
+		this.unknown02 = value;
+	}
+
+	public void setUnknown03(int value) {
+		this.unknown03 = value;
+	}
+
+	public void setUnknown04(int value) {
+		this.unknown04 = value;
+	}
+
+	public void setUnknown05(float value) {
+		this.unknown05 = value;
+	}
+
+	public void setUnknown06(int value) {
+		this.unknown06 = value;
+	}
+
+	public void setUnknown07(int value) {
+		this.unknown07 = value;
+	}
+
+	public void addItemType(int value) {
+		this.itemTypes.add(value);
+	}
+
+	public void addSkillToken(int value) {
+		this.skillTokens.add(value);
+	}
+
+	public void addUnknownArray02(int value) {
+		this.unknownArray02.add(value);
+	}
+
+	public void setItemType(ArrayList<Integer> value) {
+		this.itemTypes = value;
+	}
+
+	public void setSkillTokens(ArrayList<Integer> value) {
+		this.skillTokens = value;
+	}
+
+	public void setUnknownArray(ArrayList<Integer> value) {
+		this.unknownArray02 = value;
+	}
+
+	public void setupOutput() {
+		this.unknownByte01 = (byte)0;
+		this.unknownByte02 = (byte)0;
+		this.unknown01 = 0;
+		this.unknown02 = 0;
+		this.unknown03 = 10000;
+		this.unknown04 = 0;
+		this.unknown07 = 0;
+	}
+}
diff --git a/src/engine/net/client/msg/SendBallEntryMessage.java b/src/engine/net/client/msg/SendBallEntryMessage.java
new file mode 100644
index 00000000..8c32c9c3
--- /dev/null
+++ b/src/engine/net/client/msg/SendBallEntryMessage.java
@@ -0,0 +1,107 @@
+/*
+HashSet<Integer> playerFriendSet = PlayerFriendsMap.get(playerUID);
+			playerFriendSet.add(friendUID); * Copyright 2013 MagicBane Emulator Project
+ * All Rights Reserved
+ */
+package engine.net.client.msg;
+
+
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.objects.PlayerCharacter;
+
+
+public class SendBallEntryMessage extends ClientNetMsg {
+
+	public int playerID;
+	public String description;
+	public int msgType;
+	public int ballColor;
+	public static final int ADDBALL = 4;
+	public static final int WHITEBALL = 2;
+	public static final int BLACKBALL = 1;
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public SendBallEntryMessage(PlayerCharacter pc) {
+		super(Protocol.SENDBALLENTRY);
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public SendBallEntryMessage(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.SENDBALLENTRY, origin, reader);
+	}
+
+	/**
+	 * Copy constructor
+	 */
+	public SendBallEntryMessage(SendBallEntryMessage msg) {
+		super(Protocol.SENDBALLENTRY);
+	}
+
+	
+	
+	/**
+	 * Deserializes the subclass specific items from the supplied ByteBufferReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		this.msgType = reader.getInt();
+		
+		switch (this.msgType){
+		case ADDBALL:
+			this.readAddBall(reader);
+			break;
+			default:
+				break;
+		}
+		
+	}
+	
+	public void readAddBall(ByteBufferReader reader){
+		reader.getInt();
+		this.playerID = reader.getInt();
+		
+		reader.getInt();
+		reader.getInt();
+		reader.getInt();
+		reader.getInt();
+		
+		reader.getInt(); // 1 ?
+		
+		reader.getInt();
+		reader.getInt();
+		
+		reader.getInt(); // source player type
+		reader.getInt(); // source player ID
+		
+		reader.getInt(); // targetType
+		reader.getInt(); // targetID (same as this.playerID)
+		this.description = reader.getString();
+		reader.get();
+		reader.get();
+		reader.getInt(); // 100, max ball mark
+		reader.get();
+		reader.get();
+		reader.get();
+		this.ballColor = reader.getInt();
+		reader.get();
+		
+		
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied ByteBufferWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+	}
+}
diff --git a/src/engine/net/client/msg/SendOwnPlayerMsg.java b/src/engine/net/client/msg/SendOwnPlayerMsg.java
new file mode 100644
index 00000000..75b8362b
--- /dev/null
+++ b/src/engine/net/client/msg/SendOwnPlayerMsg.java
@@ -0,0 +1,123 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+
+import engine.exception.SerializationException;
+import engine.net.AbstractConnection;
+import engine.net.AbstractNetMsg;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.objects.Building;
+import engine.objects.PlayerCharacter;
+import engine.objects.Regions;
+import engine.session.Session;
+import org.pmw.tinylog.Logger;
+
+public class SendOwnPlayerMsg extends ClientNetMsg {
+
+	private PlayerCharacter ch;
+
+	/**
+	 * This is the general purpose constructor.
+	 *
+	 * @param s
+	 *            Session from which the PlayerCharacter is obtained
+	 */
+	public SendOwnPlayerMsg(Session s) {
+		super(Protocol.PLAYERDATA);
+
+		// Get all the character records for this account
+		ch = s.getPlayerCharacter();
+	}
+
+	/**
+	 * This is the general purpose constructor.
+	 *
+	 * @param pc
+	 *            Playercharacter
+	 */
+	public SendOwnPlayerMsg(PlayerCharacter pc) {
+		super(Protocol.PLAYERDATA);
+		this.ch = pc;
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public SendOwnPlayerMsg(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.PLAYERDATA, origin, reader);
+	}
+
+	/**
+	 * @see AbstractNetMsg#getPowerOfTwoBufferSize()
+	 */
+	@Override
+	protected int getPowerOfTwoBufferSize() {
+		//Larger size for historically larger opcodes
+		return (17); // 2^17 == 131,072
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+		
+		Regions region = this.ch.getRegion();
+		//region loading seralization. serialzes building level and floor. -1 = not in building.
+		if (region == null){
+			writer.putInt(-1);
+			writer.putInt(-1);
+		}else{
+			Building regionBuilding = Regions.GetBuildingForRegion(region);
+			if (regionBuilding == null){
+				writer.putInt(-1);
+				writer.putInt(-1);
+			}else{
+				writer.putInt(region.getLevel());
+				writer.putInt(region.getRoom());
+			}
+		}
+		writer.putVector3f(ch.getLoc());
+		try {
+			PlayerCharacter.serializeForClientMsgFull(this.ch,writer);
+		} catch (SerializationException e) {
+			Logger.error(e);
+		}
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+
+		int unknown1 = reader.getInt();
+		int unknown2 = reader.getInt();
+
+		int unknown3 = reader.getInt();
+		int unknown4 = reader.getInt();
+
+		int unknown5 = reader.getInt();
+		int unknown6 = reader.getInt();
+		int unknown7 = reader.getInt();
+
+		// TODO finish deserialization implementation.
+	}
+
+	public PlayerCharacter getChar() {
+		return this.ch;
+	}
+}
diff --git a/src/engine/net/client/msg/SendSummonsRequestMsg.java b/src/engine/net/client/msg/SendSummonsRequestMsg.java
new file mode 100644
index 00000000..d5991d56
--- /dev/null
+++ b/src/engine/net/client/msg/SendSummonsRequestMsg.java
@@ -0,0 +1,106 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+
+public class SendSummonsRequestMsg extends ClientNetMsg {
+
+	private int powerToken;
+	private int sourceType;
+	private int sourceID;
+	private String targetName;
+	private int trains;
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public SendSummonsRequestMsg() {
+		super(Protocol.POWERTARGNAME);
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public SendSummonsRequestMsg(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.POWERTARGNAME, origin, reader);
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+		writer.putInt(this.powerToken);
+		writer.putInt(this.sourceType);
+		writer.putInt(this.sourceID);
+		writer.putString(this.targetName);
+		writer.putInt(this.trains);
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		this.powerToken = reader.getInt();
+		this.sourceType = reader.getInt();
+		this.sourceID = reader.getInt();
+		this.targetName = reader.getString();
+		this.trains = reader.getInt();
+	}
+
+	public int getPowerToken() {
+		return this.powerToken;
+	}
+
+	public int getSourceType() {
+		return this.sourceType;
+	}
+
+	public int getSourceID() {
+		return this.sourceID;
+	}
+
+	public String getTargetName() {
+		return this.targetName;
+	}
+
+	public int getTrains() {
+		return this.trains;
+	}
+
+	public void setPowerToken(int value) {
+		this.powerToken = value;
+	}
+
+	public void setSourceType(int value) {
+		this.sourceType = value;
+	}
+
+	public void setSourceID(int value) {
+		this.sourceID = value;
+	}
+
+	public void setTargetName(String value) {
+		this.targetName = value;
+	}
+
+	public void setTrains(int value) {
+		this.trains = value;
+	}
+}
diff --git a/src/engine/net/client/msg/ServerInfoMsg.java b/src/engine/net/client/msg/ServerInfoMsg.java
new file mode 100644
index 00000000..0dd387ab
--- /dev/null
+++ b/src/engine/net/client/msg/ServerInfoMsg.java
@@ -0,0 +1,84 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+
+import engine.gameManager.ConfigManager;
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.server.MBServerStatics;
+import engine.server.login.LoginServer;
+
+
+public class ServerInfoMsg extends ClientNetMsg {
+
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public ServerInfoMsg() {
+		super(Protocol.SELECTSERVER);
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public ServerInfoMsg(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.SELECTSERVER, origin, reader);
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+		// writer.putInt(this.servers.size());
+		// for (WorldServerInfoSnapshot wsis : this.servers) {
+		// wsis.serializeForClientMsg(writer);
+		// }
+		writer.putInt(1);
+		
+		writer.putInt(MBServerStatics.worldMapID);
+		writer.putString(ConfigManager.MB_WORLD_NAME.getValue());
+		if (LoginServer.population < MBServerStatics.LOW_POPULATION)
+			writer.putInt(0); //Land Rush
+		else if (LoginServer.population < MBServerStatics.NORMAL_POPULATION)
+			writer.putInt(1); //Low pop
+		else if (LoginServer.population < MBServerStatics.HIGH_POPULATION)
+			writer.putInt(2); //Normal pop
+		else if (LoginServer.population < MBServerStatics.VERY_OVERPOPULATED_POPULATION)
+			writer.putInt(3); //High Pop
+		else if (LoginServer.population < MBServerStatics.FULL_POPULATION)
+			writer.putInt(4); //Very overpopulated pop
+		else
+			writer.putInt(5); //Full pop
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		int size = reader.getInt();
+		for (int i = 0; i < size; i++) {
+			int ID = reader.getInt();
+			String name = reader.getString();
+			int pop = reader.getInt();
+		}
+	}
+
+
+
+}
diff --git a/src/engine/net/client/msg/SetCombatModeMsg.java b/src/engine/net/client/msg/SetCombatModeMsg.java
new file mode 100644
index 00000000..e1ee8e18
--- /dev/null
+++ b/src/engine/net/client/msg/SetCombatModeMsg.java
@@ -0,0 +1,97 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+
+
+/**
+ * Attack from outside of combat mode.
+ *
+ * @author Eighty
+ */
+public class SetCombatModeMsg extends ClientNetMsg {
+
+	private long playerCompID;
+	private boolean toggle;
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public SetCombatModeMsg(long playerCompID, boolean toggle) {
+		super(Protocol.ARCCOMBATMODEATTACKING);
+		this.playerCompID = playerCompID;
+		this.toggle = toggle;
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public SetCombatModeMsg(AbstractConnection origin, ByteBufferReader reader)
+			 {
+		super(Protocol.ARCCOMBATMODEATTACKING, origin, reader);
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+		writer.putLong(playerCompID);
+		writer.put(toggle ? (byte) 0x01 : (byte) 0x00);
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)
+			 {
+		this.playerCompID = reader.getLong();
+		this.toggle = (reader.get() == 0x01) ? true : false;
+	}
+
+	/**
+	 * @return the playerCompID
+	 */
+	public long getPlayerCompID() {
+		return playerCompID;
+	}
+
+	/**
+	 * @return the toggle
+	 */
+	public boolean getToggle() {
+		return toggle;
+	}
+
+	/**
+	 * @param playerCompID
+	 *            the playerCompID to set
+	 */
+	public void setPlayerCompID(long playerCompID) {
+		this.playerCompID = playerCompID;
+	}
+
+	/**
+	 * @param toggle
+	 *            the toggle to set
+	 */
+	public void setToggle(boolean toggle) {
+		this.toggle = toggle;
+	}
+}
diff --git a/src/engine/net/client/msg/SetObjectValueMsg.java b/src/engine/net/client/msg/SetObjectValueMsg.java
new file mode 100644
index 00000000..92bad166
--- /dev/null
+++ b/src/engine/net/client/msg/SetObjectValueMsg.java
@@ -0,0 +1,98 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+
+import engine.exception.SerializationException;
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.objects.AbstractGameObject;
+
+public class SetObjectValueMsg extends ClientNetMsg {
+	private int targetType;
+	private int targetID;
+	private int msgType;
+	private AbstractGameObject ago;
+	
+	
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public SetObjectValueMsg(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.SETOBJVAL, origin, reader);
+	}
+	
+	public SetObjectValueMsg() {
+		super(Protocol.SETOBJVAL);
+	}
+	public SetObjectValueMsg(AbstractGameObject ago,int type) {
+		super(Protocol.SETOBJVAL);
+		if (ago == null)
+			return;
+		this.msgType = type;
+		this.targetType = ago.getObjectType().ordinal();
+		this.targetID = ago.getObjectUUID();
+		this.ago = ago;
+		
+	}
+	/**
+	 * Deserializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) throws SerializationException {
+		writer.putInt(this.targetType);
+		writer.putInt(this.targetID);
+		writer.putInt(0);
+		writer.putInt(0);
+		writer.putInt(msgType);
+		writer.putInt(0);
+	}
+
+
+	
+	public AbstractGameObject getAgo() {
+		return ago;
+	}
+
+	public void setAgo(AbstractGameObject ago) {
+		this.ago = ago;
+	}
+
+	public int getTargetType() {
+		return targetType;
+	}
+
+	public void setTargetType(int targetType) {
+		this.targetType = targetType;
+	}
+
+	public int getTargetID() {
+		return targetID;
+	}
+
+	public void setTargetID(int targetID) {
+		this.targetID = targetID;
+	}
+
+}
diff --git a/src/engine/net/client/msg/ShowBankInventoryMsg.java b/src/engine/net/client/msg/ShowBankInventoryMsg.java
new file mode 100644
index 00000000..dbd25f43
--- /dev/null
+++ b/src/engine/net/client/msg/ShowBankInventoryMsg.java
@@ -0,0 +1,115 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+
+import engine.exception.SerializationException;
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.objects.AbstractCharacter;
+import engine.objects.Item;
+import engine.objects.PlayerCharacter;
+
+import java.util.ArrayList;
+
+
+/**
+ * Bank inventory contents
+ *
+ * @author Burfo
+ */
+public class ShowBankInventoryMsg extends ClientNetMsg {
+
+	PlayerCharacter pc;
+	long unknown01;
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public ShowBankInventoryMsg(PlayerCharacter pc, long unknown01) {
+		super(Protocol.BANKINVENTORY);
+		this.pc = pc;
+		this.unknown01 = unknown01;
+	}
+
+	/**
+	 * Serializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) throws SerializationException {
+
+		ArrayList<Item> bank = pc.getCharItemManager().getBank();
+
+		writer.put((byte) 1); // static value
+		Item.putList(writer, bank, false, pc.getObjectUUID());
+		writer.putInt(AbstractCharacter.getBankCapacity());
+
+		// TODO: Gold is sent last and has a slightly different structure.
+		// Everything is static except the 3 labeled lines
+		//TODO: f/Eighty: I don't think gold is sent separately.
+		//		will need to check once transfer to bank is working.
+		/*
+		00:00:00:00:
+		07:00:00:00:
+		00:00:20:0A:5C:46:29:14: comp id
+		00:00:00:00:00:00:00:00:00:00:00:00:
+		00:00:80:3F:00:00:80:3F:00:00:80:3F:00:00:80:3F:
+		00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:
+		FF:FF:FF:FF:FF:FF:FF:FF:
+		00:00:00:00:
+		01:
+		00:00:00:00:
+		01:
+		00:00:00:00:00:00:00:00:
+		01:
+		00:00:00:00:00:00:00:00:
+		9C:1B:04:00 quantity of gold?
+		00:00:00:00:
+		00:00:00:00:
+		00:00:00:00:
+		04:00:00:00:
+		00:00:00:00:
+		00:00:00:00:
+		01:00:00:00:
+		00:00:
+		00:
+		58:02:00:00: unknown?
+		*/
+
+                writer.putInt(pc.getObjectType().ordinal());
+                writer.putInt(pc.getObjectUUID());
+		writer.putLong(unknown01);
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public ShowBankInventoryMsg(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.BANKINVENTORY, origin, reader);
+	}
+
+	/**
+	 * Deserializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+	}
+
+	@Override
+	protected int getPowerOfTwoBufferSize() {
+		// Larger size for historically larger opcodes
+		return 17; // 2^15 == 32,768
+	}
+}
diff --git a/src/engine/net/client/msg/ShowMsg.java b/src/engine/net/client/msg/ShowMsg.java
new file mode 100644
index 00000000..24701d53
--- /dev/null
+++ b/src/engine/net/client/msg/ShowMsg.java
@@ -0,0 +1,140 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+
+import engine.math.Vector3fImmutable;
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+
+public class ShowMsg extends ClientNetMsg {
+
+	private int targetType;
+	private int targetID;
+	private Vector3fImmutable unknown01;
+	private Vector3fImmutable unknown02;
+	private float range01;
+	private Vector3fImmutable unknown03;
+	private Vector3fImmutable unknown04;
+	private float range02;
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public ShowMsg() {
+		super(Protocol.SHOWCOMBATINFO);
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public ShowMsg(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.SHOWCOMBATINFO, origin, reader);
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+		writer.putInt(this.targetType);
+		writer.putInt(this.targetID);
+		writer.putVector3f(this.unknown01);
+		writer.putVector3f(this.unknown02);
+		writer.putFloat(this.range01);
+		writer.putVector3f(this.unknown03);
+		writer.putVector3f(this.unknown04);
+		writer.putFloat(this.range02);
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		this.targetType = reader.getInt();
+		this.targetID = reader.getInt();
+		this.unknown01 = reader.getVector3fImmutable();
+		this.unknown02 = reader.getVector3fImmutable();
+		this.range01 = reader.getFloat();
+		this.unknown03 = reader.getVector3fImmutable();
+		this.unknown04 = reader.getVector3fImmutable();
+		this.range02 = reader.getFloat();
+	}
+
+	public int getTargetType() {
+		return this.targetType;
+	}
+
+	public int getTargetID() {
+		return this.targetID;
+	}
+
+	public Vector3fImmutable getUnknown01() {
+		return this.unknown01;
+	}
+
+	public Vector3fImmutable getUnknown02() {
+		return this.unknown02;
+	}
+
+	public Vector3fImmutable getUnknown03() {
+		return this.unknown03;
+	}
+
+	public Vector3fImmutable getUnknown04() {
+		return this.unknown04;
+	}
+
+	public float getRange01() {
+		return this.range01;
+	}
+
+	public float getRange02() {
+		return this.range02;
+	}
+
+	public void setTargetType(int value) {
+		this.targetType = value;
+	}
+
+	public void setTargetID(int value) {
+		this.targetID = value;
+	}
+
+	public void setUnknown01(Vector3fImmutable value) {
+		this.unknown01 = value;
+	}
+
+	public void setUnknown02(Vector3fImmutable value) {
+		this.unknown02 = value;
+	}
+
+	public void setUnknown03(Vector3fImmutable value) {
+		this.unknown03 = value;
+	}
+
+	public void setUnknown04(Vector3fImmutable value) {
+		this.unknown04 = value;
+	}
+
+	public void setRange01(float value) {
+		this.range01 = value;
+	}
+
+	public void setRange02(float value) {
+		this.range02 = value;
+	}
+}
diff --git a/src/engine/net/client/msg/ShowVaultInventoryMsg.java b/src/engine/net/client/msg/ShowVaultInventoryMsg.java
new file mode 100644
index 00000000..fc0d1be6
--- /dev/null
+++ b/src/engine/net/client/msg/ShowVaultInventoryMsg.java
@@ -0,0 +1,86 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+
+import engine.exception.SerializationException;
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.objects.*;
+
+import java.util.ArrayList;
+
+/**
+ * Vault inventory contents
+ * @author Eighty
+ */
+public class ShowVaultInventoryMsg extends ClientNetMsg {
+
+	PlayerCharacter pc;
+	int accountType;
+	int accountID;
+
+	int npcType;
+	int npcID;
+
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public ShowVaultInventoryMsg(PlayerCharacter pc, Account account, NPC npc) {
+		super(Protocol.SHOWVAULTINVENTORY);
+		this.pc = pc;
+		this.accountType = account.getObjectType().ordinal();
+		this.accountID = account.getObjectUUID();
+		this.npcType = npc.getObjectType().ordinal();
+		this.npcID = npc.getObjectUUID();
+	}
+
+	/**
+	 * Serializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer)
+			throws SerializationException {
+
+		writer.putInt(accountType);
+		writer.putInt(accountID);
+		writer.putInt(npcType);
+		writer.putInt(npcID);
+		writer.putString(pc.getFirstName());
+
+		ArrayList<Item> vault = pc.getAccount().getVault();
+
+		Item.putList(writer, vault, false, pc.getObjectUUID());
+		writer.putInt(AbstractCharacter.getVaultCapacity());
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the ByteBuffer into a message. If a BufferUnderflow occurs (based on reading past the limit) then this constructor Throws that Exception to the caller.
+	 */
+	public ShowVaultInventoryMsg(AbstractConnection origin,
+			ByteBufferReader reader)  {
+		super(Protocol.SHOWVAULTINVENTORY, origin, reader);
+	}
+
+	/**
+	 * Deserializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+	}
+
+	@Override
+	protected int getPowerOfTwoBufferSize() {
+		return 17;
+	}
+}
diff --git a/src/engine/net/client/msg/SocialMsg.java b/src/engine/net/client/msg/SocialMsg.java
new file mode 100644
index 00000000..fbeba2d3
--- /dev/null
+++ b/src/engine/net/client/msg/SocialMsg.java
@@ -0,0 +1,178 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+
+public class SocialMsg extends ClientNetMsg {
+
+	private int sourceType;
+	private int sourceID;
+	private int unknown01;
+	private int unknown02;
+	private int targetType;
+	private int targetID;
+	private int social;
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public SocialMsg() {
+		super(Protocol.SOCIALCHANNEL);
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public SocialMsg(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.SOCIALCHANNEL, origin, reader);
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+		writer.putInt(this.sourceType);
+		writer.putInt(this.sourceID);
+		writer.putInt(this.unknown01);
+		writer.putInt(this.unknown02);
+		writer.putInt(this.targetType);
+		writer.putInt(this.targetID);
+		writer.putInt(this.social);
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		this.sourceType = reader.getInt();
+		this.sourceID = reader.getInt();
+		this.unknown01 = reader.getInt();
+		this.unknown02 = reader.getInt();
+		this.targetType = reader.getInt();
+		this.targetID = reader.getInt();
+		this.social = reader.getInt();
+	}
+
+	/**
+	 * @return the sourceType
+	 */
+	public int getSourceType() {
+		return sourceType;
+	}
+
+	/**
+	 * @param sourceType
+	 *            the sourceType to set
+	 */
+	public void setSourceType(int sourceType) {
+		this.sourceType = sourceType;
+	}
+
+	/**
+	 * @return the sourceID
+	 */
+	public int getSourceID() {
+		return sourceID;
+	}
+
+	/**
+	 * @param sourceID
+	 *            the sourceID to set
+	 */
+	public void setSourceID(int sourceID) {
+		this.sourceID = sourceID;
+	}
+
+	/**
+	 * @return the unknown01
+	 */
+	public int getUnknown01() {
+		return unknown01;
+	}
+
+	/**
+	 * @param unknown01
+	 *            the unknown01 to set
+	 */
+	public void setUnknown01(int unknown01) {
+		this.unknown01 = unknown01;
+	}
+
+	/**
+	 * @return the unknown02
+	 */
+	public int getUnknown02() {
+		return unknown02;
+	}
+
+	/**
+	 * @param unknown02
+	 *            the unknown02 to set
+	 */
+	public void setUnknown02(int unknown02) {
+		this.unknown02 = unknown02;
+	}
+
+	/**
+	 * @return the targetType
+	 */
+	public int getTargetType() {
+		return targetType;
+	}
+
+	/**
+	 * @param targetType
+	 *            the targetType to set
+	 */
+	public void setTargetType(int targetType) {
+		this.targetType = targetType;
+	}
+
+	/**
+	 * @return the targetID
+	 */
+	public int getTargetID() {
+		return targetID;
+	}
+
+	/**
+	 * @param targetID
+	 *            the targetID to set
+	 */
+	public void setTargetID(int targetID) {
+		this.targetID = targetID;
+	}
+
+	/**
+	 * @return the social
+	 */
+	public int getSocial() {
+		return social;
+	}
+
+	/**
+	 * @param social
+	 *            the social to set
+	 */
+	public void setSocial(int social) {
+		this.social = social;
+	}
+
+}
diff --git a/src/engine/net/client/msg/StuckCommandMsg.java b/src/engine/net/client/msg/StuckCommandMsg.java
new file mode 100644
index 00000000..e089221c
--- /dev/null
+++ b/src/engine/net/client/msg/StuckCommandMsg.java
@@ -0,0 +1,52 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+
+
+public class StuckCommandMsg extends ClientNetMsg {
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public StuckCommandMsg() {
+		super(Protocol.STUCK);
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public StuckCommandMsg(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.STUCK, origin, reader);
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+	}
+
+}
diff --git a/src/engine/net/client/msg/SyncMessage.java b/src/engine/net/client/msg/SyncMessage.java
new file mode 100644
index 00000000..2d50e353
--- /dev/null
+++ b/src/engine/net/client/msg/SyncMessage.java
@@ -0,0 +1,146 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+
+import engine.exception.SerializationException;
+import engine.gameManager.BuildingManager;
+import engine.gameManager.DbManager;
+import engine.gameManager.ZoneManager;
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.objects.Building;
+import engine.objects.Zone;
+import org.pmw.tinylog.Logger;
+
+import java.util.ArrayList;
+
+public class SyncMessage extends ClientNetMsg {
+	
+	private int type;
+	private int size;
+	private int pad = 0;
+	private int objectType;
+	private int objectUUID;
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public SyncMessage(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.CITYASSET, origin, reader);
+	}
+	/**
+	 * Deserializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+	//none yet
+	}
+
+	public SyncMessage() {
+		super(Protocol.CITYASSET);
+	}
+	/**
+	 * Serializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) throws SerializationException {
+		//lets do returns before writing so we don't send improper structures to the client
+		
+		Building tol = BuildingManager.getBuilding(this.objectUUID);
+                
+		if (tol == null){
+			Logger.debug("TOL is null");
+			return;
+		}
+		Zone zone = ZoneManager.findSmallestZone(tol.getLoc());
+		if (zone == null){
+			Logger.debug( "Zone is null");
+			return;
+		}
+		ArrayList<Building> allCityAssets = DbManager.BuildingQueries.GET_ALL_BUILDINGS_FOR_ZONE(zone);
+		
+                // *** Refactor: collection created but never used?
+                        
+                ArrayList<Building> canProtectAssets = new ArrayList<>();
+
+                for (Building b: allCityAssets){
+                        if (b.getBlueprintUUID() != 0)
+				canProtectAssets.add(b);
+		}
+                
+                // *** Refactor : Not sure what this synch message does
+                // Get the feeling it should be looping over upgradable
+                // assets.
+		writer.putInt(0);
+		writer.putInt(0);
+                writer.putInt(this.objectType);
+                writer.putInt(this.objectUUID);
+		writer.putInt(allCityAssets.size());
+		for (Building b: allCityAssets){
+			String name = b.getName();
+		//	if (name.equals(""))
+		//		name = b.getBuildingSet().getName();
+                        writer.putInt(b.getObjectType().ordinal());
+                        writer.putInt(b.getObjectUUID());
+			
+			writer.putString(b.getName()); // Blueprint name?
+			writer.putString(b.getGuild().getName());
+			writer.putInt(20);//  \/ Temp \/
+			writer.putInt(b.getRank());
+			writer.putInt(1);  // symbol
+			writer.putInt(7);  //TODO identify these Guild tags??
+			writer.putInt(17);
+			writer.putInt(14);
+			writer.putInt(14);
+			writer.putInt(98);// /\ Temp /\
+			
+		}		
+	}
+
+	public int getObjectType() {
+		return objectType;
+	}
+
+	public void setObjectType(int value) {
+		this.objectType = value;
+	}
+
+	public void setPad(int value) {
+		this.pad = value;
+	}
+
+	public int getUUID() {
+		return objectUUID;
+
+	}
+
+	public int getPad() {
+		return pad;
+	}
+	public int getType() {
+		return type;
+	}
+	public void setType(int type) {
+		this.type = type;
+	}
+
+	public int getSize() {
+		return size;
+	}
+	public void setSize(int size) {
+		this.size = size;
+	}
+}
diff --git a/src/engine/net/client/msg/TargetObjectMsg.java b/src/engine/net/client/msg/TargetObjectMsg.java
new file mode 100644
index 00000000..3c567887
--- /dev/null
+++ b/src/engine/net/client/msg/TargetObjectMsg.java
@@ -0,0 +1,81 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+
+public class TargetObjectMsg extends ClientNetMsg {
+
+	private int targetType;
+	private int targetID;
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public TargetObjectMsg(int targetType, int targetID) {
+		super(Protocol.SETSELECTEDOBECT);
+		this.targetType = targetType;
+		this.targetID = targetID;
+	}
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public TargetObjectMsg() {
+		super(Protocol.SETSELECTEDOBECT);
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public TargetObjectMsg(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.SETSELECTEDOBECT, origin, reader);
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+		writer.putInt(this.targetType);
+		writer.putInt(this.targetID);
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		this.targetType = reader.getInt();
+		this.targetID = reader.getInt();
+	}
+
+	/**
+	 * @return the targetType
+	 */
+	public int getTargetType() {
+		return targetType;
+	}
+
+	/**
+	 * @return the targetID
+	 */
+	public int getTargetID() {
+		return targetID;
+	}
+
+}
diff --git a/src/engine/net/client/msg/TargetedActionMsg.java b/src/engine/net/client/msg/TargetedActionMsg.java
new file mode 100644
index 00000000..5f01002a
--- /dev/null
+++ b/src/engine/net/client/msg/TargetedActionMsg.java
@@ -0,0 +1,346 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+
+
+
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.objects.AbstractCharacter;
+import engine.objects.AbstractWorldObject;
+import engine.objects.PlayerCharacter;
+
+public class TargetedActionMsg extends ClientNetMsg {
+
+	public static int un2cnt = 65;
+
+	//attack animations
+	//64: overhead RH swing											1h RH axe?
+	//65: overhead LH swing
+	//66: underhand RH uppercut
+	//67: shoulder high RH swing
+	//68: underhand RH swing
+	//69: sweeping LH swing
+	//70: overhead circle RH swing
+	//71: RH across body and back swing
+	//72: RH 1h overhead to 2h swing
+	//73: RH overhead to cross body swing (bm?)
+	//74: 2h low stab to cross body slash
+	//75: unarmed punch												unarmed RH
+	//76: unarmed RH punch LH punch
+	//77: unarmed LH jab
+	//78: unarmed LH jab, RH uppercut
+	//79: kick
+	//80: roundhouse kick
+	//81: dagger RH stab											dagger RH
+	//82: dagger LH stab
+	//83: dagger slash
+	//84: dagger hard stab
+	//85: Polearm/staff overhead swing								Polearm, Staff
+	//86: Polearm/staff side swing
+	//87: Polearm/staff step into overhead swing
+	//88: swinging RH stab
+	//89: swinging LF stab
+	//90: swinging RH stab (faster)
+	//91: 1H slash across body and back (sword?)
+	//92: spear stab												spear
+	//93: spear low stab step into
+	//94: spear swing leg stab
+	//95: unarmed overhead swing RH, underhand swing LH
+	//96: inverted weapon across body followed by roundhouse LH swing
+	//97: step back followed by overhead roundhouse swing 2H
+	//98: underhand slash (1h sword)								1H RH Sword
+	//99: fast LH swing (dagger or sword?)
+	//100: 1h swing RH (sword)										1h axe?
+	//101: 1h overhead swing (club or sword)
+	//102: fast 1h underhand swing (club or sword)
+	//103: step into RH slash 1h
+	//104: 1h overhead to cross body slash RH
+	//105: 2h overhead swing (axe, hammer, sword)					2H Axe, Hammer, Sword
+	//106: step into 2h swing
+	//107: step int 2h overhead swing
+	//108: step into 2h swing
+	//109: bow draw and fire										bow
+	//110: crossbow draw and fire									crossbow
+	//115: throwing axe/hammer?
+	//116: overhand throwing dagger?
+	//117: throwing dagger
+
+	private int sourceType;
+	private int sourceID;
+	private int targetType;
+	private int targetID;
+	private float locX;
+	private float locZ;
+	private int unknown01 = 14;
+	private int unknown02 = 100; //source animation
+	private float unknown03 = 1f;
+	private float sourceStamina = 1f; // attackers stamina after attack
+	private int unknown05 = 6;	//signify passive defense
+	private int unknown06 = 10; //target animation
+	private float newHealth = 10f; // health after damage
+	private float damage = 0f; // damage, 0 for miss
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public TargetedActionMsg(int sourceType, int sourceID, int targetType, int targetID, float locX, float locZ, float sourceStamina,
+			float newHealth, float damage) {
+		super(Protocol.TARGETEDACTION);
+		this.sourceType = sourceType;
+		this.sourceID = sourceID;
+		this.targetType = targetType;
+		this.targetID = targetID;
+		this.sourceStamina = sourceStamina;
+		this.locX = locX;
+		this.locZ = locZ;
+		this.newHealth = newHealth;
+		this.damage = damage;
+		//this.unknown02 = TargetedActionMsg.un2cnt;
+	}
+
+	/**
+	 * This is a helper constructor. Designed to send an UPDATE only. Damage for
+	 * this constructor is hard coded to ZERO.
+	 */
+	public TargetedActionMsg(PlayerCharacter pc) {
+		super(Protocol.TARGETEDACTION);
+		this.sourceType = pc.getObjectType().ordinal();
+		this.sourceID = pc.getObjectUUID();
+		this.targetType = pc.getObjectType().ordinal();
+		this.targetID = pc.getObjectUUID();
+		this.sourceStamina = pc.getStamina();
+		this.locX = pc.getLoc().x;
+		this.locZ = pc.getLoc().z;
+		this.newHealth = pc.getCurrentHitpoints();
+		this.damage = 0.0f;
+	}
+	
+	public TargetedActionMsg(PlayerCharacter pc,int unknown06) {
+		super(Protocol.TARGETEDACTION);
+		this.sourceType = pc.getObjectType().ordinal();
+		this.sourceID = pc.getObjectUUID();
+		this.targetType = pc.getObjectType().ordinal();
+		this.targetID = pc.getObjectUUID();
+		this.sourceStamina = pc.getStamina();
+		this.locX = pc.getLoc().x;
+		this.locZ = pc.getLoc().z;
+		this.newHealth = pc.getCurrentHitpoints();
+		this.damage = 0.0f;
+	}
+
+	public TargetedActionMsg(AbstractCharacter source, AbstractWorldObject target, Float damage, int swingAnimation) {
+		super(Protocol.TARGETEDACTION);
+		if (source != null) {
+			this.sourceType = source.getObjectType().ordinal();
+			this.sourceID = source.getObjectUUID();
+			this.sourceStamina = source.getStamina();
+		} else {
+			this.sourceType = 0;
+			this.sourceID = 0;
+			this.sourceStamina = 0;
+		}
+		if (target != null) {
+			this.targetType = target.getObjectType().ordinal();
+			this.targetID = target.getObjectUUID();
+			this.locX = target.getLoc().x;
+			this.locZ = target.getLoc().z;
+			this.newHealth = target.getHealth();
+			this.damage = damage;
+			
+		} else {
+			this.targetType = 0;
+			this.targetID = 0;
+			this.locX = 50000f;
+			this.locZ = -50000f;
+			this.newHealth = 1f;
+			this.damage = damage;
+		}
+		this.unknown02 = swingAnimation;
+		//this.unknown02 = TargetedActionMsg.un2cnt;
+	}
+	
+	public TargetedActionMsg(AbstractCharacter source, AbstractWorldObject target, Float damage, int swingAnimation, int dead) {
+		super(Protocol.TARGETEDACTION);
+		if (source != null) {
+			this.sourceType = source.getObjectType().ordinal();
+			this.sourceID = source.getObjectUUID();
+			this.sourceStamina = source.getStamina();
+		} else {
+			this.sourceType = 0;
+			this.sourceID = 0;
+			this.sourceStamina = 0;
+		}
+		if (target != null) {
+			this.targetType = target.getObjectType().ordinal();
+			this.targetID = target.getObjectUUID();
+			this.locX = target.getLoc().x;
+			this.locZ = target.getLoc().z;
+			this.newHealth = target.getCurrentHitpoints();
+			this.damage = damage;
+			this.unknown06 = dead;
+			
+		} else {
+			this.targetType = 0;
+			this.targetID = 0;
+			this.locX = 50000f;
+			this.locZ = -50000f;
+			this.newHealth = 1f;
+			this.damage = damage;
+		}
+		this.unknown02 = swingAnimation;
+		//this.unknown02 = TargetedActionMsg.un2cnt;
+	}
+
+	/**
+	 * Added in an attempt to have mobs fall over after death.
+	 */
+	
+	public TargetedActionMsg(AbstractWorldObject target, boolean kill) {
+			this(null, target, 0f, 75);
+			if (kill){ 
+				this.newHealth = -1f;
+				this.sourceType = 0x101;
+				this.sourceID = 0x101;
+			}
+	}
+	
+	/**
+	 * This constructor can be used to create CombatMessages that indicate a block or parry has occurred.<br>
+	 * <br>
+	 * Set passiveAnimation to 21 for block.<br>
+	 * Set passiveAnimation to 22 for parry.<br>
+	 */
+	public TargetedActionMsg(AbstractCharacter source, int swingAnimation, AbstractWorldObject target,  int passiveAnimation) {
+		this(source, target, 0.0f, swingAnimation);
+		this.unknown05 = passiveAnimation;
+		this.unknown06 = 0;
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public TargetedActionMsg(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.TARGETEDACTION, origin, reader);
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+		writer.putInt(this.sourceType);
+		writer.putInt(this.sourceID);
+		writer.putInt(this.targetType);
+		writer.putInt(this.targetID);
+		writer.putFloat(this.locX);
+		writer.putFloat(this.locZ);
+		writer.putInt(this.unknown01);
+		writer.putInt(this.unknown02);
+		writer.putFloat(this.unknown03);
+		writer.putFloat(this.sourceStamina);
+		writer.putInt(this.unknown05);
+		// writer.putInt(this.unknown06);
+
+		if (this.newHealth < 0)
+			writer.putInt(55);
+		else if(damage != 0 && this.unknown05 < 20)
+			writer.putInt(60);
+		else
+			writer.putInt(this.unknown06);
+		writer.putFloat(this.newHealth);
+		writer.putFloat(this.damage);
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		this.sourceType = reader.getInt();
+		this.sourceID = reader.getInt();
+		this.targetType = reader.getInt();
+		this.targetID = reader.getInt();
+		this.locX = reader.getFloat();
+		this.locZ = reader.getFloat();
+		this.unknown01 = reader.getInt();
+		this.unknown02 = reader.getInt();
+		this.unknown03 = reader.getFloat();
+		this.sourceStamina = reader.getFloat();
+		this.unknown05 = reader.getInt();
+		this.unknown06 = reader.getInt();
+		this.newHealth = reader.getFloat();
+		this.damage = reader.getFloat();
+	}
+
+	/**
+	 * @return the sourceType
+	 */
+	public int getSourceType() {
+		return sourceType;
+	}
+
+	/**
+	 * @return the sourceID
+	 */
+	public int getSourceID() {
+		return sourceID;
+	}
+
+	/**
+	 * @return the targetType
+	 */
+	public int getTargetType() {
+		return targetType;
+	}
+
+	/**
+	 * @return the targetID
+	 */
+	public int getTargetID() {
+		return targetID;
+	}
+
+	public void setSourceType(int value) {
+		this.sourceType = value;
+	}
+
+	public void setSourceID(int value) {
+		this.sourceID = value;
+	}
+
+	public void setTargetType(int value) {
+		this.targetType = value;
+	}
+
+	public void setTargetID(int value) {
+		this.targetID = value;
+	}
+
+	public float getDamage() {
+		return damage;
+	}
+}
diff --git a/src/engine/net/client/msg/TaxCityMsg.java b/src/engine/net/client/msg/TaxCityMsg.java
new file mode 100644
index 00000000..3a7a9859
--- /dev/null
+++ b/src/engine/net/client/msg/TaxCityMsg.java
@@ -0,0 +1,115 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+import engine.Enum.GameObjectType;
+import engine.exception.SerializationException;
+import engine.gameManager.BuildingManager;
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.objects.Building;
+import engine.objects.GuildTag;
+
+
+public class TaxCityMsg extends ClientNetMsg {
+
+
+	private int buildingID;
+	private int msgType;
+
+
+	public TaxCityMsg(Building building, int msgType) {
+		super(Protocol.TAXCITY);
+		this.buildingID = building.getObjectUUID();
+		this.msgType = msgType;
+
+	}
+
+	public TaxCityMsg() {
+		super(Protocol.TAXCITY);
+	}
+
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public TaxCityMsg(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.TAXCITY, origin, reader);
+	}
+	//CALL THIS AFTER SANITY CHECKS AND BEFORE UPDATING HEALTH/GOLD.
+
+
+	/**
+	 * Deserializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		this.msgType = reader.getInt();
+		reader.getInt(); //object Type.. always building
+		this.buildingID = reader.getInt();
+		reader.getInt();
+		reader.getInt();
+
+		reader.getInt();
+		reader.getInt();
+		reader.getInt();
+		reader.getInt();
+		reader.getInt();
+
+		reader.getInt();
+		reader.getInt();
+		reader.getInt();
+		reader.getInt();
+		reader.getInt();
+
+	}
+
+
+	// Precache and configure this message before we serialize it
+
+
+	/**
+	 * Serializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) throws SerializationException {
+
+		writer.putInt(0);
+		writer.putInt(GameObjectType.Building.ordinal());
+		writer.putInt(this.buildingID);
+		Building building = BuildingManager.getBuildingFromCache(this.buildingID);
+
+		writer.putInt(0);
+		writer.putFloat(.2f);
+		GuildTag._serializeForDisplay(building.getGuild().getGuildTag(),writer);
+		GuildTag._serializeForDisplay(building.getGuild().getGuildTag(),writer);
+
+
+
+	}
+
+	public int getGuildID() {
+		return buildingID;
+	}
+
+	public int getMsgType() {
+		return msgType;
+	}
+
+	public void setMsgType(int msgType) {
+		this.msgType = msgType;
+	}
+
+}
diff --git a/src/engine/net/client/msg/TaxResourcesMsg.java b/src/engine/net/client/msg/TaxResourcesMsg.java
new file mode 100644
index 00000000..a6a91650
--- /dev/null
+++ b/src/engine/net/client/msg/TaxResourcesMsg.java
@@ -0,0 +1,123 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+import engine.Enum.GameObjectType;
+import engine.exception.SerializationException;
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.objects.Building;
+
+import java.util.HashMap;
+
+
+public class TaxResourcesMsg extends ClientNetMsg {
+
+
+	private int buildingID;
+	private int msgType;
+	private HashMap<Integer,Integer> resources;
+	private float taxPercent;
+
+
+	public TaxResourcesMsg(Building building, int msgType) {
+		super(Protocol.TAXRESOURCES);
+		this.buildingID = building.getObjectUUID();
+		this.msgType = msgType;
+
+	}
+
+	public TaxResourcesMsg() {
+		super(Protocol.TAXRESOURCES);
+	}
+
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public TaxResourcesMsg(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.TAXRESOURCES, origin, reader);
+	}
+	//CALL THIS AFTER SANITY CHECKS AND BEFORE UPDATING HEALTH/GOLD.
+
+
+	/**
+	 * Deserializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		this.msgType = reader.getInt();
+		reader.getInt(); //object Type.. always building
+		this.buildingID = reader.getInt();
+		HashMap<Integer,Integer> resourcesTemp = new HashMap<>();
+		int size = reader.getInt();
+		for (int i=0;i<size;i++){
+			int resourceHash = reader.getInt();
+			resourcesTemp.put(resourceHash,0);
+		}
+		this.resources = resourcesTemp;
+		taxPercent = reader.getFloat();
+		reader.getInt();
+
+
+	}
+
+
+	// Precache and configure this message before we serialize it
+
+
+	/**
+	 * Serializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) throws SerializationException {
+
+		writer.putInt(0);
+		writer.putInt(GameObjectType.Building.ordinal());
+		writer.putInt(this.buildingID);
+		writer.putInt(this.resources.size());
+		for (int resource:resources.keySet())
+			writer.putInt(resource);
+		writer.putFloat(this.taxPercent);
+		writer.putInt(this.resources.size());
+		for (int resource:resources.keySet()){
+			writer.putInt(resource);
+			writer.putInt(0);
+			writer.putInt(resources.get(resource));
+		}
+			
+	}
+
+	public int getBuildingID() {
+		return buildingID;
+	}
+
+	public int getMsgType() {
+		return msgType;
+	}
+
+	public void setMsgType(int msgType) {
+		this.msgType = msgType;
+	}
+
+	public HashMap<Integer,Integer> getResources() {
+		return resources;
+	}
+
+	public float getTaxPercent() {
+		return taxPercent;
+	}
+
+}
diff --git a/src/engine/net/client/msg/TeleportRepledgeListMsg.java b/src/engine/net/client/msg/TeleportRepledgeListMsg.java
new file mode 100644
index 00000000..2379bdd2
--- /dev/null
+++ b/src/engine/net/client/msg/TeleportRepledgeListMsg.java
@@ -0,0 +1,112 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+
+import engine.net.AbstractConnection;
+import engine.net.AbstractNetMsg;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.objects.City;
+import engine.objects.PlayerCharacter;
+
+import java.util.ArrayList;
+
+
+public class TeleportRepledgeListMsg extends ClientNetMsg {
+
+	private PlayerCharacter player;
+	private boolean isTeleport;
+    ArrayList<City> cities;
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public TeleportRepledgeListMsg(PlayerCharacter player, boolean isTeleport) {
+		super(Protocol.SENDCITYENTRY);
+		this.player = player;
+		this.isTeleport = isTeleport;
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public TeleportRepledgeListMsg(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.SENDCITYENTRY, origin, reader);
+	}
+
+	/**
+	 * Copy constructor
+	 */
+	public TeleportRepledgeListMsg(TeleportRepledgeListMsg msg) {
+		super(Protocol.SENDCITYENTRY);
+        this.player = msg.player;
+        this.isTeleport = msg.isTeleport;
+	}
+
+	/**
+	 * @see AbstractNetMsg#getPowerOfTwoBufferSize()
+	 */
+	@Override
+	protected int getPowerOfTwoBufferSize() {
+		// Larger size for historically larger opcodes
+		return (16);
+	}
+	/**
+	 * Deserializes the subclass specific items from the supplied ByteBufferReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		//Do we even want to try this?
+	}
+
+    // Pre-caches and configures message so data is avaiable
+    // when we serialize.
+
+    public void configure() {
+
+        if (isTeleport)
+            cities = City.getCitiesToTeleportTo(player);
+        else
+            cities = City.getCitiesToRepledgeTo(player);
+    }
+
+	/**
+	 * Serializes the subclass specific items to the supplied ByteBufferWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+		if (isTeleport)
+			writer.putInt(2); //teleport?
+		else
+			writer.putInt(1); //repledge?
+
+		for (int i=0;i<3;i++)
+			writer.putInt(0);
+
+		writer.putInt(cities.size());
+
+		for (City city : cities)
+			City.serializeForClientMsg(city,writer);
+	}
+
+	public PlayerCharacter getPlayer() {
+		return this.player;
+	}
+
+	public boolean isTeleport() {
+		return this.isTeleport;
+	}
+
+}
diff --git a/src/engine/net/client/msg/TeleportToPointMsg.java b/src/engine/net/client/msg/TeleportToPointMsg.java
new file mode 100644
index 00000000..c94de917
--- /dev/null
+++ b/src/engine/net/client/msg/TeleportToPointMsg.java
@@ -0,0 +1,222 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+import engine.Enum.GameObjectType;
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.objects.AbstractWorldObject;
+
+public class TeleportToPointMsg extends ClientNetMsg {
+
+	private int sourceType;
+	private int sourceUUID;
+	private float endLat;
+	private float endLon;
+	private float endAlt;
+	private int targetType;
+	private int targetUUID;
+	private int unknown01;
+	private int unknown02;
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public TeleportToPointMsg(AbstractWorldObject ago, float endLat, float endAlt, float endLon, long targetID, int unknown01, int unknown02) {
+		super(Protocol.TELEPORT);
+
+		this.sourceType = ago.getObjectType().ordinal();
+		this.sourceUUID = ago.getObjectUUID();
+		this.endLat = endLat;
+		this.endAlt = endAlt;
+		this.endLon = endLon;
+		if (targetID != 0){
+			this.targetType = GameObjectType.Building.ordinal();
+			this.targetUUID = (int) targetID;
+		}else{
+			this.targetType = 0;
+			this.targetUUID = 0;
+		}
+		
+		
+
+		this.unknown01 = unknown01;
+		this.unknown02 = unknown02;
+		
+		if (ago.getRegion() != null){
+			this.targetType = GameObjectType.Building.ordinal();
+			this.targetUUID = ago.getRegion().parentBuildingID;
+			this.unknown01 = ago.getRegion().level;
+			this.unknown02 = ago.getRegion().room;
+		}
+	}
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public TeleportToPointMsg() {
+		super(Protocol.TELEPORT);
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public TeleportToPointMsg(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.TELEPORT, origin, reader);
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+		writer.putInt(this.sourceType);
+		writer.putInt(this.sourceUUID);
+		writer.putFloat(this.endLat);
+		writer.putFloat(this.endAlt);
+		writer.putFloat(this.endLon);
+		writer.putInt(this.targetType);
+		writer.putInt(this.targetUUID);
+		writer.putInt(this.unknown01);
+		writer.putInt(this.unknown02);
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		this.sourceType = reader.getInt();
+		this.sourceUUID = reader.getInt();
+		this.endLat = reader.getInt();
+		this.endAlt = reader.getInt();
+		this.endLon = reader.getInt();
+		this.targetType = reader.getInt();
+		this.targetUUID = reader.getInt();
+		this.unknown01 = reader.getInt();
+		this.unknown02 = reader.getInt();
+	}
+
+
+	/**
+	 * @return the endLat
+	 */
+	public float getEndLat() {
+		return endLat;
+	}
+
+	/**
+	 * @param endLat
+	 *            the endLat to set
+	 */
+	public void setEndLat(float endLat) {
+		this.endLat = endLat;
+	}
+
+	/**
+	 * @return the endLon
+	 */
+	public float getEndLon() {
+		return endLon;
+	}
+
+	/**
+	 * @param endLon
+	 *            the endLon to set
+	 */
+	public void setEndLon(float endLon) {
+		this.endLon = endLon;
+	}
+
+	/**
+	 * @return the endAlt
+	 */
+	public float getEndAlt() {
+		return endAlt;
+	}
+
+	/**
+	 * @param endAlt
+	 *            the endAlt to set
+	 */
+	public void setEndAlt(float endAlt) {
+		this.endAlt = endAlt;
+	}
+
+
+
+	/**
+	 * @return the unknown01
+	 */
+	public int getUnknown01() {
+		return unknown01;
+	}
+
+	/**
+	 * @param unknown01
+	 *            the unknown01 to set
+	 */
+	public void setUnknown01(int unknown01) {
+		this.unknown01 = unknown01;
+	}
+
+	/**
+	 * @return the unknown02
+	 */
+	public int getUnknown02() {
+		return unknown02;
+	}
+
+	/**
+	 * @param unknown02
+	 *            the unknown02 to set
+	 */
+	public void setUnknown02(int unknown02) {
+		this.unknown02 = unknown02;
+	}
+
+	public int getSourceType() {
+		return sourceType;
+	}
+
+	public void setSourceType(int sourceType) {
+		this.sourceType = sourceType;
+	}
+
+	public int getSourceUUID() {
+		return sourceUUID;
+	}
+
+	public void setSourceUUID(int sourceUUID) {
+		this.sourceUUID = sourceUUID;
+	}
+
+	public int getTargetType() {
+		return targetType;
+	}
+
+	public void setTargetType(int targetType) {
+		this.targetType = targetType;
+	}
+
+	public int getTargetUUID() {
+		return targetUUID;
+	}
+
+	public void setTargetUUID(int targetUUID) {
+		this.targetUUID = targetUUID;
+	}
+
+}
diff --git a/src/engine/net/client/msg/TerritoryChangeMessage.java b/src/engine/net/client/msg/TerritoryChangeMessage.java
new file mode 100644
index 00000000..57f74369
--- /dev/null
+++ b/src/engine/net/client/msg/TerritoryChangeMessage.java
@@ -0,0 +1,95 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+
+import engine.exception.SerializationException;
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.objects.PlayerCharacter;
+import engine.objects.Realm;
+
+public class TerritoryChangeMessage extends ClientNetMsg {
+
+
+
+	private Realm realm;
+	private PlayerCharacter realmOwner;
+
+
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public TerritoryChangeMessage(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.TERRITORYCHANGE, origin, reader);
+	}
+	/**
+	 * Deserializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+
+
+	}
+
+	public TerritoryChangeMessage() {
+		super(Protocol.TERRITORYCHANGE);
+	}
+
+	public TerritoryChangeMessage(PlayerCharacter guildLeader, Realm realm) {
+		super(Protocol.TERRITORYCHANGE);
+
+		this.realm = realm;
+		this.realmOwner = guildLeader;
+	}
+	/**
+	 * Serializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) throws SerializationException {
+
+		writer.putString(realm.getRealmName());
+		if(this.realmOwner != null){
+			writer.putString(this.realmOwner.getCombinedName());
+			writer.putInt(PlayerCharacter.GetPlayerRealmTitle(this.realmOwner));
+			writer.putInt(1);
+			writer.put((byte)1);
+			writer.put((byte)1);
+			writer.putInt(realm.getCharterType());
+			if (this.realmOwner != null && this.realmOwner.getGuild() != null)
+				writer.putString(this.realmOwner.getGuild().getName());
+			else
+				writer.putString("None");
+			writer.put((byte)0);
+		}else{
+			writer.putInt(0);
+			writer.putInt(0);
+			writer.putInt(0);
+			writer.put((byte)1);
+			writer.put((byte)1);
+			writer.putInt(0);
+			writer.putInt(0);
+			writer.put((byte)0);
+		}
+
+
+
+	}
+
+
+
+
+}
diff --git a/src/engine/net/client/msg/ToggleCombatMsg.java b/src/engine/net/client/msg/ToggleCombatMsg.java
new file mode 100644
index 00000000..56e7de20
--- /dev/null
+++ b/src/engine/net/client/msg/ToggleCombatMsg.java
@@ -0,0 +1,85 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+
+
+public class ToggleCombatMsg extends ClientNetMsg {
+
+	private int sourceType;
+	private int sourceID;
+	private boolean toggleCombat;
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public ToggleCombatMsg() {
+		super(Protocol.COMBATMODE);
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public ToggleCombatMsg(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.COMBATMODE, origin, reader);
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+		writer.putInt(this.sourceType);
+		writer.putInt(this.sourceID);
+		writer.put(this.toggleCombat ? (byte) 0x01 : (byte) 0x00);
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		this.sourceType = reader.getInt();
+		this.sourceID = reader.getInt();
+		this.toggleCombat = (reader.get() == (byte) 0x01) ? true : false;
+	}
+
+	public int getSourceType() {
+		return this.sourceType;
+	}
+
+	public int getSourceID() {
+		return this.sourceID;
+	}
+
+	public boolean toggleCombat() {
+		return this.toggleCombat;
+	}
+
+	public void setSourceType(int value) {
+		this.sourceType = value;
+	}
+
+	public void setSourceID(int value) {
+		this.sourceID = value;
+	}
+
+	public void setToggleCombat(boolean value) {
+		this.toggleCombat = value;
+	}
+}
diff --git a/src/engine/net/client/msg/ToggleLfgRecruitingMsg.java b/src/engine/net/client/msg/ToggleLfgRecruitingMsg.java
new file mode 100644
index 00000000..2dfd611c
--- /dev/null
+++ b/src/engine/net/client/msg/ToggleLfgRecruitingMsg.java
@@ -0,0 +1,96 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+
+public class ToggleLfgRecruitingMsg extends ClientNetMsg {
+
+	private int sourceType;
+	private int sourceID;
+	private byte toggleLfgRecruiting;
+	private byte unknown01;
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public ToggleLfgRecruitingMsg() {
+		super(Protocol.MODIFYGUILDSTATE);
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public ToggleLfgRecruitingMsg(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.MODIFYGUILDSTATE, origin, reader);
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+		writer.putInt(this.sourceType);
+		writer.putInt(this.sourceID);
+		writer.put(this.toggleLfgRecruiting);
+		writer.put(this.unknown01);
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		this.sourceType = reader.getInt();
+		this.sourceID = reader.getInt();
+		this.toggleLfgRecruiting = reader.get();
+		this.unknown01 = reader.get();
+	}
+
+	public int getSourceType() {
+		return this.sourceType;
+	}
+
+	public int getSourceID() {
+		return this.sourceID;
+	}
+
+	public byte toggleLfgRecruiting() {
+		return this.toggleLfgRecruiting;
+	}
+
+	public byte getUnknown01() {
+		return this.unknown01;
+	}
+
+	public void setSourceType(int value) {
+		this.sourceType = value;
+	}
+
+	public void setSourceID(int value) {
+		this.sourceID = value;
+	}
+
+	public void setToggleLfgRecruiting(byte value) {
+		this.toggleLfgRecruiting = value;
+	}
+
+	public void setUnknown01(byte value) {
+		this.unknown01 = value;
+	}
+
+}
diff --git a/src/engine/net/client/msg/ToggleSitStandMsg.java b/src/engine/net/client/msg/ToggleSitStandMsg.java
new file mode 100644
index 00000000..90446503
--- /dev/null
+++ b/src/engine/net/client/msg/ToggleSitStandMsg.java
@@ -0,0 +1,84 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+
+public class ToggleSitStandMsg extends ClientNetMsg {
+
+	private int sourceType;
+	private int sourceID;
+	private boolean toggleSitStand;
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public ToggleSitStandMsg() {
+		super(Protocol.TOGGLESITSTAND);
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public ToggleSitStandMsg(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.TOGGLESITSTAND, origin, reader);
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+		writer.putInt(this.sourceType);
+		writer.putInt(this.sourceID);
+		writer.put(this.toggleSitStand ? (byte) 0x01 : (byte) 0x00);
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		this.sourceType = reader.getInt();
+		this.sourceID = reader.getInt();
+		this.toggleSitStand = (reader.get() == (byte) 0x01) ? true : false;
+	}
+
+	public int getSourceType() {
+		return this.sourceType;
+	}
+
+	public int getSourceID() {
+		return this.sourceID;
+	}
+
+	public boolean toggleSitStand() {
+		return this.toggleSitStand;
+	}
+
+	public void setSourceType(int value) {
+		this.sourceType = value;
+	}
+
+	public void setSourceID(int value) {
+		this.sourceID = value;
+	}
+
+	public void setToggleSitStand(boolean value) {
+		this.toggleSitStand = value;
+	}
+}
diff --git a/src/engine/net/client/msg/TrackArrowMsg.java b/src/engine/net/client/msg/TrackArrowMsg.java
new file mode 100644
index 00000000..9655be18
--- /dev/null
+++ b/src/engine/net/client/msg/TrackArrowMsg.java
@@ -0,0 +1,61 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+
+
+public class TrackArrowMsg extends ClientNetMsg {
+
+	protected float direction;
+
+	public TrackArrowMsg(float direction) {
+		super(Protocol.ARCTRACKOBJECT);
+		this.direction = direction;
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public TrackArrowMsg(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.ARCTRACKOBJECT, origin, reader);
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+		writer.putFloat(this.direction);
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		this.direction = reader.getFloat();
+	}
+
+	public float getDirection() {
+		return this.direction;
+	}
+
+	public void setDirection(float value) {
+		this.direction = value;
+	}
+}
diff --git a/src/engine/net/client/msg/TrackWindowMsg.java b/src/engine/net/client/msg/TrackWindowMsg.java
new file mode 100644
index 00000000..1241320b
--- /dev/null
+++ b/src/engine/net/client/msg/TrackWindowMsg.java
@@ -0,0 +1,148 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+import engine.Enum.GameObjectType;
+import engine.net.AbstractConnection;
+import engine.net.AbstractNetMsg;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.objects.*;
+
+import java.util.HashSet;
+
+
+public class TrackWindowMsg extends ClientNetMsg {
+
+	private int powerToken;
+	private PlayerCharacter source = null;
+	private HashSet<AbstractCharacter> characters = new HashSet<>();
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public TrackWindowMsg(int powerToken, HashSet<AbstractCharacter> characters) {
+		super(Protocol.ARCTRACKINGLIST);
+		this.powerToken = powerToken;
+		this.characters = characters;
+	}
+
+	public TrackWindowMsg(TrackWindowMsg trackWindowMsg) {
+		super(Protocol.ARCTRACKINGLIST);
+		this.powerToken = trackWindowMsg.powerToken;
+		this.source = trackWindowMsg.source;
+		this.characters = trackWindowMsg.characters;
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public TrackWindowMsg(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.ARCTRACKINGLIST, origin, reader);
+	}
+
+	/**
+	 * @see AbstractNetMsg#getPowerOfTwoBufferSize()
+	 */
+	@Override
+	protected int getPowerOfTwoBufferSize() {
+		// Larger size for historically larger opcodes
+		return (13);
+	}
+	/**
+	 * Serializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+		writer.putInt(this.powerToken);
+		writer.putInt(characters.size());
+		for (AbstractCharacter ac : characters) {
+			boolean isGroup = false;
+			if (this.source != null && ac.getObjectType().equals(GameObjectType.PlayerCharacter)) {
+				if (Group.sameGroup((PlayerCharacter)ac, this.source))
+					isGroup = true;
+			}
+			AbstractCharacter.serializeForTrack(ac,writer, isGroup);
+		}
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		this.powerToken = reader.getInt();
+
+		int size = reader.getInt();
+		for (int i=0;i<size;i++) {
+			int objectType = reader.getInt();
+			int objectID = reader.getInt();
+			this.source = PlayerCharacter.getFromCache(objectID);
+			reader.getString(); //name
+			reader.get(); //always 00?
+			reader.getInt(); //guildObjectType
+			reader.getInt(); //guildID
+			reader.get(); //always 01?
+			for (int j=0;j<5;j++)
+				reader.getInt(); //guild tags
+			reader.getInt(); //nation ObjectType
+			reader.getInt(); //nation ID
+			reader.get(); //always 01?
+			for (int j=0;j<5;j++)
+				reader.getInt(); //nation tags
+
+			//Get the Character from it's Object Type and ID
+			AbstractCharacter ac = null;
+			if (objectType == GameObjectType.PlayerCharacter.ordinal())
+				ac = PlayerCharacter.getFromCache(objectID);
+			else if (objectType == GameObjectType.NPC.ordinal())
+				ac = NPC.getFromCache(objectID);
+			else if (objectType == GameObjectType.Mob.ordinal())
+				ac = Mob.getFromCache(objectID);
+
+			//If found, add to message list
+			if (ac != null)
+				characters.add(ac);
+		}
+	}
+
+	public int getPowerToken() {
+		return this.powerToken;
+	}
+
+	public HashSet<AbstractCharacter> getCharacters() {
+		return this.characters;
+	}
+
+	public void setPowerToken(int value) {
+		this.powerToken = value;
+	}
+
+	public void setCharacters(HashSet<AbstractCharacter> value) {
+		this.characters = value;
+	}
+
+	public void addCharacter(PlayerCharacter value) {
+		if (value != null)
+			this.characters.add(value);
+	}
+
+	public void clearChracters() {
+		this.characters.clear();
+	}
+
+	public void setSource(PlayerCharacter value) {
+		this.source = value;
+	}
+}
diff --git a/src/engine/net/client/msg/TradeRequestMsg.java b/src/engine/net/client/msg/TradeRequestMsg.java
new file mode 100644
index 00000000..25e64ab4
--- /dev/null
+++ b/src/engine/net/client/msg/TradeRequestMsg.java
@@ -0,0 +1,130 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+
+import engine.exception.SerializationException;
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.objects.AbstractGameObject;
+
+/**
+ * Request trade
+ *
+ * @author Eighty
+ */
+public class TradeRequestMsg extends ClientNetMsg {
+
+	private int unknown01; //pad?
+	private int playerType;
+	private int playerID;
+	private int sourceType;
+	private int sourceID;
+
+
+	/**
+	 * This is the general purpose constructor
+	 */
+	public TradeRequestMsg(int unknown01, AbstractGameObject player, AbstractGameObject target) {
+		super(Protocol.REQUESTTOTRADE);
+		this.unknown01 = unknown01;
+		this.sourceType = player.getObjectType().ordinal();
+		this.sourceID = player.getObjectUUID();
+		this.playerType = target.getObjectType().ordinal();
+		this.playerID = target.getObjectUUID();
+		
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the ByteBuffer into a message. If a BufferUnderflow occurs (based on reading past the limit) then this constructor Throws that Exception to the caller.
+	 */
+	public TradeRequestMsg(AbstractConnection origin, ByteBufferReader reader)
+			 {
+		super(Protocol.REQUESTTOTRADE, origin, reader);
+	}
+
+	/**
+	 * Deserializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)
+			 {
+		unknown01 = reader.getInt();
+		this.sourceType = reader.getInt();
+		this.sourceID = reader.getInt();
+		this.playerType = reader.getInt();
+		this.playerID = reader.getInt();
+	}
+
+	/**
+	 * Serializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer)
+			throws SerializationException {
+		writer.putInt(unknown01);
+		writer.putInt(this.sourceType);
+		writer.putInt(this.sourceID);
+		writer.putInt(this.playerType);
+		writer.putInt(this.playerID);
+	
+		
+	}
+
+	/**
+	 * @return the unknown01
+	 */
+	public int getUnknown01() {
+		return unknown01;
+	}
+
+	/**
+	 * @param unknown01 the unknown01 to set
+	 */
+	public void setUnknown01(int unknown01) {
+		this.unknown01 = unknown01;
+	}
+
+	
+	public int getPlayerType() {
+		return playerType;
+	}
+
+	public void setPlayerType(int playerType) {
+		this.playerType = playerType;
+	}
+
+	public int getPlayerID() {
+		return playerID;
+	}
+
+	public void setPlayerID(int playerID) {
+		this.playerID = playerID;
+	}
+
+	public int getSourceType() {
+		return sourceType;
+	}
+
+	public void setSourceType(int sourceType) {
+		this.sourceType = sourceType;
+	}
+
+	public int getSourceID() {
+		return sourceID;
+	}
+
+	public void setSourceID(int sourceID) {
+		this.sourceID = sourceID;
+	}
+
+}
diff --git a/src/engine/net/client/msg/TrainMsg.java b/src/engine/net/client/msg/TrainMsg.java
new file mode 100644
index 00000000..0c81450a
--- /dev/null
+++ b/src/engine/net/client/msg/TrainMsg.java
@@ -0,0 +1,361 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+import engine.Enum;
+import engine.Enum.ProtectionState;
+import engine.exception.MsgSendException;
+import engine.gameManager.ChatManager;
+import engine.gameManager.DbManager;
+import engine.gameManager.PowersManager;
+import engine.gameManager.SessionManager;
+import engine.net.*;
+import engine.net.client.ClientConnection;
+import engine.net.client.Protocol;
+import engine.objects.*;
+import engine.powers.PowersBase;
+import engine.server.MBServerStatics;
+import org.pmw.tinylog.Logger;
+
+import java.util.concurrent.ConcurrentHashMap;
+
+
+public class TrainMsg extends ClientNetMsg {
+
+	private int npcType;
+	private int npcID;
+	private int unknown01 = 1;
+	private int trainCost01; //why two trainer costs?
+	private int trainCost02; //why two trainer costs?
+	private boolean isSkill; //true: skill; false: power
+	private int token;
+	private boolean unknown02 = true;
+	private String ok = "";
+	private int unknown03 = 0;
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public TrainMsg() {
+		super(Protocol.TRAINSKILL);
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public TrainMsg(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.TRAINSKILL, origin, reader);
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+		writer.putInt(this.npcType);
+		writer.putInt(this.npcID);
+		writer.putInt(this.unknown01);
+		writer.putInt(trainCost01);
+		writer.putInt(trainCost02);
+		writer.put((this.isSkill == true) ? (byte)0x01 : (byte)0x00);
+		writer.putInt(this.token);
+		writer.put((this.unknown02 == true) ? (byte)0x01 : (byte)0x00);
+		writer.putString(this.ok);
+		writer.putInt(this.unknown03);
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		this.npcType = reader.getInt();
+		this.npcID = reader.getInt();
+		this.unknown01 = reader.getInt();
+		this.trainCost01 = reader.getInt();
+		this.trainCost02 = reader.getInt();
+		this.isSkill = (reader.get() == (byte)0x01) ? true : false;
+		this.token = reader.getInt();
+		this.unknown02 = (reader.get() == (byte)0x01) ? true : false;
+		this.ok = reader.getString();
+		this.unknown03 = reader.getInt();
+	}
+
+	public int getToken() {
+		return this.token;
+	}
+
+	public boolean isSkill() {
+		return this.isSkill;
+	}
+
+	public int getNpcType() {
+		return this.npcType;
+	}
+
+	public int getNpcID() {
+		return this.npcID;
+	}
+
+	public static void train(TrainMsg msg, ClientConnection origin) throws MsgSendException {
+
+		PlayerCharacter playerCharacter = SessionManager.getPlayerCharacter(origin);
+		Dispatch dispatch;
+
+		if (playerCharacter == null)
+			return;
+
+        NPC npc = NPC.getFromCache(msg.npcID);
+
+		if (npc == null)
+			return;
+
+		if (origin.trainLock.tryLock()){
+			try{
+				Item gold = playerCharacter.getCharItemManager().getGoldInventory();
+
+				if (gold == null)
+					return;
+
+				if (!gold.validForInventory(origin, playerCharacter, playerCharacter.getCharItemManager()))
+					return;
+
+				boolean canTrain = false;
+                if (msg.isSkill) {
+
+					//Get skill
+                    SkillsBase sb = DbManager.SkillsBaseQueries.GET_BASE_BY_TOKEN(msg.token);
+					ConcurrentHashMap<String, CharacterSkill> skills = playerCharacter.getSkills();
+
+					if (sb == null || skills == null)
+						return;
+
+					CharacterSkill sk = skills.get(sb.getName());
+
+					if (sk == null)
+						return;
+
+					if (sk.getSkillsBase().getToken() == 40661438){
+						int maxValue = 15;
+
+
+						if (MaxSkills.MaxSkillsSet.get(252647) != null)
+							for (MaxSkills maxSkills: MaxSkills.MaxSkillsSet.get(252647)){
+								if (maxSkills.getSkillToken() != sk.getToken())
+									continue;
+
+								if (maxSkills.getSkillLevel() > npc.getLevel())
+									continue;
+								maxValue += maxSkills.getMaxSkillPercent();
+							}
+						if (maxValue> sk.getModifiedAmountBeforeMods()){
+							canTrain = true;
+						}
+
+					}
+							if (canTrain == false)
+						if  (npc.getContract() != null && npc.getContract().getExtraRune() != 0){
+							int maxValue = 15;
+
+
+							if (MaxSkills.MaxSkillsSet.get(npc.getContract().getExtraRune()) != null)
+								for (MaxSkills maxSkills: MaxSkills.MaxSkillsSet.get(npc.getContract().getExtraRune())){
+									if (maxSkills.getSkillToken() != sk.getToken())
+										continue;
+
+									if (maxSkills.getSkillLevel() > npc.getLevel())
+										continue;
+									maxValue += maxSkills.getMaxSkillPercent();
+								}
+							if (maxValue > sk.getModifiedAmountBeforeMods()){
+								canTrain = true;
+							}
+
+
+						}
+							if (canTrain == false){
+							int maxValue = 15;
+							if (MaxSkills.MaxSkillsSet.get(npc.getContractID()) != null)
+								for (MaxSkills maxSkills: MaxSkills.MaxSkillsSet.get(npc.getContractID())){
+									if (maxSkills.getSkillToken() != sk.getToken())
+										continue;
+
+									if (maxSkills.getSkillLevel() > npc.getLevel())
+										continue;
+									maxValue += maxSkills.getMaxSkillPercent();
+								}
+							if (maxValue > sk.getModifiedAmountBeforeMods()){
+								canTrain = true;
+							}
+						}
+							
+							if (canTrain == false){
+								int maxValue = 15;
+								if (MaxSkills.MaxSkillsSet.get(npc.getContract().getClassID()) != null)
+									for (MaxSkills maxSkills: MaxSkills.MaxSkillsSet.get(npc.getContract().getClassID())){
+										if (maxSkills.getSkillToken() != sk.getToken())
+											continue;
+
+										if (maxSkills.getSkillLevel() > npc.getLevel())
+											continue;
+										maxValue += maxSkills.getMaxSkillPercent();
+									}
+								if (maxValue > sk.getModifiedAmountBeforeMods()){
+									canTrain = true;
+								}
+							}
+							
+							if (canTrain == false){
+								int maxValue = 15;
+								if (MaxSkills.MaxSkillsSet.get(npc.extraRune2) != null)
+									for (MaxSkills maxSkills: MaxSkills.MaxSkillsSet.get(npc.getContract().getClassID())){
+										if (maxSkills.getSkillToken() != sk.getToken())
+											continue;
+
+										if (maxSkills.getSkillLevel() > npc.getLevel())
+											continue;
+										maxValue += maxSkills.getMaxSkillPercent();
+									}
+								if (maxValue > sk.getModifiedAmountBeforeMods()){
+									canTrain = true;
+								}
+							}
+							
+							if (canTrain == false){
+								ChatManager.chatSystemError(playerCharacter, "NPC cannot train that skill any higher");
+								return;
+							}
+
+					float cost =  sk.getTrainingCost(playerCharacter, npc);
+					float profitCost =  cost * npc.getSellPercent(playerCharacter);
+
+					profitCost += .5f;
+					if (profitCost > playerCharacter.getCharItemManager().getGoldInventory().getNumOfItems())
+						return;
+					Building b = npc.getBuilding();
+					if (b != null && b.getProtectionState().equals(ProtectionState.NPC))
+						b = null;
+
+					if (b != null && b.getStrongboxValue() + (profitCost - cost) > b.getMaxGold()){
+						ErrorPopupMsg.sendErrorPopup(playerCharacter, 206);
+						return;
+					}
+
+					if (playerCharacter.getCharItemManager().getGoldInventory().getNumOfItems() - profitCost < 0)
+						return;
+
+					if (playerCharacter.getCharItemManager().getGoldInventory().getNumOfItems() - profitCost > MBServerStatics.PLAYER_GOLD_LIMIT)
+						return;
+
+
+					//attempt to train
+					if (sk.train(playerCharacter)) {
+						playerCharacter.getCharItemManager().buyFromNPC(b, (int)profitCost, (int)(profitCost - cost));
+
+						dispatch = Dispatch.borrow(playerCharacter, msg);
+						DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+
+						//update trainer window
+
+						if (npc != null) {
+                            TrainerInfoMsg tim = new TrainerInfoMsg(msg.npcType, msg.npcID, npc.getSellPercent(playerCharacter));
+							tim.setTrainPercent(npc.getSellPercent(playerCharacter));
+							dispatch = Dispatch.borrow(playerCharacter, tim);
+							DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+						}
+					}
+
+				} else {
+					//Get Power
+                    int token = msg.token;
+
+					if (MBServerStatics.POWERS_DEBUG) {
+                        ChatManager.chatSayInfo(playerCharacter, "Training Power: " +
+								Integer.toHexString(msg.token) + " (" + msg.token + ')');
+                        System.out.println("Training Power: " +
+								Integer.toHexString(msg.token) + " (" + msg.token + ')');
+					}
+
+					PowersBase pb = PowersManager.getPowerByToken(token);
+					ConcurrentHashMap<Integer, CharacterPower> powers = playerCharacter.getPowers();
+					if (pb == null || powers == null)
+						return;
+					
+					if (pb.isWeaponPower)
+						return;
+					CharacterPower cp = null;
+					if (powers.containsKey(token))
+						cp = powers.get(token);
+					if (cp == null)
+						return;
+
+					//attempt to train
+					float cost = (int) cp.getTrainingCost(playerCharacter, npc);
+					float profitCost = cost * npc.getSellPercent(playerCharacter);
+					profitCost += .5f;
+					if (profitCost > playerCharacter.getCharItemManager().getGoldInventory().getNumOfItems()){
+						//	ChatManager.chatSystemError(pc, "You do not have enough gold to train this skill.");
+						return;
+					}
+					
+					Building b = npc.getBuilding();
+					
+					if (b != null && b.getProtectionState().equals(ProtectionState.NPC))
+						b = null;
+
+					if (b != null && b.getStrongboxValue() + (profitCost - cost) > b.getMaxGold()){
+						ErrorPopupMsg.sendErrorPopup(playerCharacter, 206);
+						return;
+					}
+					if (cp.train(playerCharacter)) {
+
+						if (!playerCharacter.getCharItemManager().buyFromNPC(b, (int)profitCost, (int)(profitCost - cost)))
+							ChatManager.chatSystemError(playerCharacter, "Failed to Withdrawl gold from inventory. Contact CCR");
+
+						//train succeeded
+
+						dispatch = Dispatch.borrow(playerCharacter, msg);
+						DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+
+						//update trainer window
+
+						if (npc != null) {
+                            TrainerInfoMsg tim = new TrainerInfoMsg(msg.npcType, msg.npcID, npc.getSellPercent(playerCharacter));
+							tim.setTrainPercent(npc.getSellPercent(playerCharacter));
+							dispatch = Dispatch.borrow(playerCharacter, tim);
+							DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+						}
+					}
+				}
+			}catch(Exception e){
+				Logger.error(e);
+			}finally{
+			origin.trainLock.unlock();
+			}
+
+
+		}
+
+
+
+
+
+
+
+
+	}
+
+	public static float getTrainPercent(NPC npc) {
+		return 0f;
+	}
+}
diff --git a/src/engine/net/client/msg/TrainerInfoMsg.java b/src/engine/net/client/msg/TrainerInfoMsg.java
new file mode 100644
index 00000000..f44712dd
--- /dev/null
+++ b/src/engine/net/client/msg/TrainerInfoMsg.java
@@ -0,0 +1,101 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+
+public class TrainerInfoMsg extends ClientNetMsg {
+
+	private int objectType;
+	private int objectID;
+	private float trainPercent;
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public TrainerInfoMsg(int objectType, int objectID, float trainPercent) {
+		super(Protocol.TRAINERLIST);
+		this.objectType = objectType;
+		this.objectID = objectID;
+		this.trainPercent = trainPercent;
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public TrainerInfoMsg(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.TRAINERLIST, origin, reader);
+	}
+
+	/**
+	 * Copy constructor
+	 */
+	public TrainerInfoMsg(TrainerInfoMsg msg) {
+		super(Protocol.TRAINERLIST);
+		this.objectType = msg.objectType;
+		this.objectID = msg.objectID;
+		this.trainPercent = msg.trainPercent;
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied ByteBufferReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		reader.get();
+		this.objectType = reader.getInt();
+		this.objectID = reader.getInt();
+		this.trainPercent = reader.getFloat();
+		reader.getInt();
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied ByteBufferWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+		writer.put((byte)0);
+		writer.putInt(this.objectType);
+		writer.putInt(this.objectID);
+		writer.putFloat(this.trainPercent);
+		writer.putInt(0);
+	}
+
+	public int getObjectType() {
+		return this.objectType;
+	}
+
+	public int getObjectID() {
+		return this.objectID;
+	}
+
+	public float getTrainPercent() {
+		return this.trainPercent;
+	}
+
+	public void setObjectType(int value) {
+		this.objectType = value;
+	}
+
+	public void setObjectID(int value) {
+		this.objectID = value;
+	}
+
+	public void setTrainPercent(float value) {
+		this.trainPercent = value;
+	}
+}
diff --git a/src/engine/net/client/msg/TransferAssetMsg.java b/src/engine/net/client/msg/TransferAssetMsg.java
new file mode 100644
index 00000000..0f354801
--- /dev/null
+++ b/src/engine/net/client/msg/TransferAssetMsg.java
@@ -0,0 +1,99 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+
+import engine.exception.SerializationException;
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+
+public class TransferAssetMsg extends ClientNetMsg {
+	
+	private int objectType;
+	private int objectID;
+	private int targetType;
+	private int targetID;
+	private int pad = 0;
+	
+/**
+ * This constructor is used by NetMsgFactory. It attempts to deserialize the
+ * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+ * past the limit) then this constructor Throws that Exception to the
+ * caller.
+ */
+public TransferAssetMsg(AbstractConnection origin, ByteBufferReader reader)  {
+	super(Protocol.TRANSFERASSET, origin, reader);
+}
+/**
+ * Deserializes the subclass specific items from the supplied NetMsgReader.
+ */
+@Override
+protected void _deserialize(ByteBufferReader reader)  {
+	this.pad = reader.getInt();
+	this.objectType = reader.getInt();
+	this.objectID = reader.getInt();
+	this.targetType = reader.getInt();
+	this.targetID = reader.getInt();
+	reader.getShort();
+}
+
+/**
+ * Serializes the subclass specific items to the supplied NetMsgWriter.
+ */
+@Override
+protected void _serialize(ByteBufferWriter writer) throws SerializationException {
+	writer.putInt(this.pad);
+	writer.putInt(this.objectType);
+	writer.putInt(this.objectID);
+	writer.putInt(this.targetType);
+	writer.putInt(this.targetID);
+	writer.putShort((short)0);
+	
+}
+
+public int getObjectType() {
+	return objectType;
+}
+
+public void setObjectType(int value) {
+this.objectType = value;
+}
+
+public void setPad(int value) {
+this.pad = value;
+}
+
+public int getPad() {
+return pad;
+}
+
+public int getTargetType() {
+	return targetType;
+}
+public void setTargetObject(int targetObject) {
+	this.targetType = targetObject;
+}
+public int getTargetID() {
+	return targetID;
+}
+public void setTargetID(int targetID) {
+	this.targetID = targetID;
+}
+public int getObjectID() {
+	return objectID;
+}
+public void setObjectID(int objectID) {
+	this.objectID = objectID;
+}
+
+
+}
\ No newline at end of file
diff --git a/src/engine/net/client/msg/TransferBuildingMsg.java b/src/engine/net/client/msg/TransferBuildingMsg.java
new file mode 100644
index 00000000..63a7f408
--- /dev/null
+++ b/src/engine/net/client/msg/TransferBuildingMsg.java
@@ -0,0 +1,97 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+
+import engine.exception.SerializationException;
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+
+public class TransferBuildingMsg extends ClientNetMsg {
+	
+	private int objectType;
+	private int objectID;
+	private int targetType;
+	private int targetID;
+	private int pad = 0;
+	
+/**
+ * This constructor is used by NetMsgFactory. It attempts to deserialize the
+ * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+ * past the limit) then this constructor Throws that Exception to the
+ * caller.
+ */
+public TransferBuildingMsg(AbstractConnection origin, ByteBufferReader reader)  {
+	super(Protocol.TRANSFERASSET, origin, reader);
+}
+/**
+ * Deserializes the subclass specific items from the supplied NetMsgReader.
+ */
+@Override
+protected void _deserialize(ByteBufferReader reader)  {
+	this.pad = reader.getInt();
+	this.objectType = reader.getInt();
+	this.objectID = reader.getInt();
+	this.targetType = reader.getInt();
+	this.targetID = reader.getInt();
+}
+
+/**
+ * Serializes the subclass specific items to the supplied NetMsgWriter.
+ */
+@Override
+protected void _serialize(ByteBufferWriter writer) throws SerializationException {
+	writer.putInt(this.pad);
+	writer.putInt(this.objectType);
+	writer.putInt(this.objectID);
+	writer.putInt(this.targetType);
+	writer.putInt(this.targetID);
+	
+}
+
+public int getObjectType() {
+	return objectType;
+}
+
+public void setObjectType(int value) {
+this.objectType = value;
+}
+
+public void setPad(int value) {
+this.pad = value;
+}
+
+public int getPad() {
+return pad;
+}
+
+public int getTargetType() {
+	return targetType;
+}
+public void setTargetObject(int targetObject) {
+	this.targetType = targetObject;
+}
+public int getTargetID() {
+	return targetID;
+}
+public void setTargetID(int targetID) {
+	this.targetID = targetID;
+}
+public int getObjectID() {
+	return objectID;
+}
+public void setObjectID(int objectID) {
+	this.objectID = objectID;
+}
+
+
+}
\ No newline at end of file
diff --git a/src/engine/net/client/msg/TransferGoldFromInventoryToVaultMsg.java b/src/engine/net/client/msg/TransferGoldFromInventoryToVaultMsg.java
new file mode 100644
index 00000000..de31e72e
--- /dev/null
+++ b/src/engine/net/client/msg/TransferGoldFromInventoryToVaultMsg.java
@@ -0,0 +1,105 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+import engine.Enum.GameObjectType;
+import engine.exception.SerializationException;
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+
+/**
+ * Transfer gold from inventory to vault
+ *
+ * @author Eighty
+ */
+
+public class TransferGoldFromInventoryToVaultMsg extends ClientNetMsg {
+
+	private int playerID;
+	private int accountID;
+	private int npcID;
+
+	private int amount;
+
+	/**
+	 * This is the general purpose constructor
+	 */
+	public TransferGoldFromInventoryToVaultMsg() {
+		super(Protocol.GOLDTOVAULT);
+
+	}
+	public TransferGoldFromInventoryToVaultMsg(int playerID, int npcID, int accountID, int amount) {
+		super(Protocol.GOLDTOVAULT);
+		this.playerID = playerID;
+		this.npcID = npcID;
+		this.accountID = accountID;
+		this.amount = amount;
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public TransferGoldFromInventoryToVaultMsg(AbstractConnection origin, ByteBufferReader reader)
+			 {
+		super(Protocol.GOLDTOVAULT, origin, reader);
+	}
+
+	/**
+	 * Deserializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)
+			 {
+		reader.getInt();
+		this.playerID = reader.getInt();
+		reader.getInt();
+		this.accountID = reader.getInt();
+		reader.getInt();
+		this.npcID = reader.getInt();
+		this.amount = reader.getInt();
+	}
+
+	/**
+	 * Serializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer)
+			throws SerializationException {
+		writer.putInt(GameObjectType.PlayerCharacter.ordinal());
+		writer.putInt(this.playerID);
+		writer.putInt(GameObjectType.Account.ordinal());
+		writer.putInt(this.accountID);
+		writer.putInt(GameObjectType.NPC.ordinal());
+		writer.putInt(this.npcID);
+
+		writer.putInt(this.amount);
+	}
+
+
+    /**
+	 * @return the amount
+	 */
+	public int getAmount() {
+		return amount;
+	}
+
+	/**
+	 * @param amount the amount to set
+	 */
+	public void setAmount(int amount) {
+		this.amount = amount;
+	}
+
+}
diff --git a/src/engine/net/client/msg/TransferGoldFromVaultToInventoryMsg.java b/src/engine/net/client/msg/TransferGoldFromVaultToInventoryMsg.java
new file mode 100644
index 00000000..580b8769
--- /dev/null
+++ b/src/engine/net/client/msg/TransferGoldFromVaultToInventoryMsg.java
@@ -0,0 +1,132 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+
+import engine.exception.SerializationException;
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+
+/**
+ * Transfer gold from vault to inventory
+ *
+ * @author Eighty
+ */
+public class TransferGoldFromVaultToInventoryMsg extends ClientNetMsg {
+
+	private long unknown01;
+	private long playerCompID;
+	private long accountCompID;
+	private int amount;
+
+	/**
+	 * This is the general purpose constructor
+	 */
+	public TransferGoldFromVaultToInventoryMsg(long unknown01, long playerCompID, long accountCompID, int amount) {
+		super(Protocol.TRANSFERGOLDFROMVAULTTOINVENTORY);
+		this.unknown01 = unknown01;
+		this.playerCompID = playerCompID;
+		this.accountCompID = accountCompID;
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public TransferGoldFromVaultToInventoryMsg(AbstractConnection origin, ByteBufferReader reader)
+			 {
+		super(Protocol.TRANSFERGOLDFROMVAULTTOINVENTORY, origin, reader);
+	}
+
+	/**
+	 * Deserializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)
+			 {
+		unknown01 = reader.getLong();
+		playerCompID = reader.getLong();
+		accountCompID = reader.getLong();
+		amount = reader.getInt();
+	}
+
+	/**
+	 * Serializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer)
+			throws SerializationException {
+		writer.putLong(unknown01);
+		writer.putLong(playerCompID);
+		writer.putLong(accountCompID);
+		writer.putInt(amount);
+	}
+
+	/**
+	 * @return the unknown01
+	 */
+	public long getUnknown01() {
+		return unknown01;
+	}
+
+	/**
+	 * @param unknown01 the unknown01 to set
+	 */
+	public void setUnknown01(long unknown01) {
+		this.unknown01 = unknown01;
+	}
+
+	/**
+	 * @return the playerCompID
+	 */
+	public long getPlayerCompID() {
+		return playerCompID;
+	}
+
+	/**
+	 * @param playerCompID the playerCompID to set
+	 */
+	public void setPlayerCompID(long playerCompID) {
+		this.playerCompID = playerCompID;
+	}
+
+	/**
+	 * @return the unknown02
+	 */
+	public long getAccountCompID() {
+		return accountCompID;
+	}
+
+	/**
+	 * @param unknown02 the unknown02 to set
+	 */
+	public void setAccountCompID(long accountCompID) {
+		this.accountCompID = accountCompID;
+	}
+
+	/**
+	 * @return the amount
+	 */
+	public int getAmount() {
+		return amount;
+	}
+
+	/**
+	 * @param amount the amount to set
+	 */
+	public void setAmount(int amount) {
+		this.amount = amount;
+	}
+
+}
diff --git a/src/engine/net/client/msg/TransferGoldToFromBuildingMsg.java b/src/engine/net/client/msg/TransferGoldToFromBuildingMsg.java
new file mode 100644
index 00000000..488c8627
--- /dev/null
+++ b/src/engine/net/client/msg/TransferGoldToFromBuildingMsg.java
@@ -0,0 +1,149 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+
+import engine.exception.SerializationException;
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+
+
+/**
+ * Transfer gold to/from building strongbox/reserve
+ *
+ * @author Eighty
+ */
+
+public class TransferGoldToFromBuildingMsg extends ClientNetMsg {
+
+	private int direction; //Maybe? 1 and 2
+	private int failReason;
+	private int objectType;
+	private int objectID;
+	private int amount;
+	private int unknown01;
+	private int unknown02;
+
+	/**
+	 * This is the general purpose constructor
+	 */
+	public TransferGoldToFromBuildingMsg() {
+		super(Protocol.TRANSFERGOLDTOFROMBUILDING);
+		this.direction = 0;
+		this.failReason = 0;
+		this.objectType = 0;
+		this.objectID = 0;
+		this.amount = 0;
+		this.unknown01 = 0;
+		this.unknown02 = 0;
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public TransferGoldToFromBuildingMsg(AbstractConnection origin, ByteBufferReader reader)
+			 {
+		super(Protocol.TRANSFERGOLDTOFROMBUILDING, origin, reader);
+	}
+
+	/**
+	 * Deserializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)
+			 {
+		this.direction = reader.getInt();
+		this.failReason = reader.getInt();
+		this.objectType = reader.getInt();
+		this.objectID = reader.getInt();
+		this.amount = reader.getInt();
+		this.unknown01 = reader.getInt();
+		this.unknown02 = reader.getInt();
+	}
+
+	/**
+	 * Serializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer)
+			throws SerializationException {
+		writer.putInt(this.direction);
+		writer.putInt(this.failReason);
+		writer.putInt(this.objectType);
+		writer.putInt(this.objectID);
+		writer.putInt(this.amount);
+		writer.putInt(this.unknown01);
+		writer.putInt(this.unknown02);
+
+	}
+
+	public int getDirection() {
+		return this.direction;
+	}
+
+	public int getAmount() {
+		return this.amount;
+	}
+
+	public int getUnknown01() {
+		return this.unknown01;
+	}
+
+	public int getUnknown02() {
+		return this.unknown02;
+	}
+
+	public void setDirection(int value) {
+		this.direction = value;
+	}
+
+
+	public void setAmount(int value) {
+		this.amount = value;
+	}
+
+	public void setUnknown01(int value) {
+		this.unknown01 = value;
+	}
+
+	public void setUnknown02(int value) {
+		this.unknown02 = value;
+	}
+
+	public void setFailReason(int failReason) {
+		this.failReason = failReason;
+	}
+
+	public int getFailReason() {
+		return failReason;
+	}
+
+
+	public int getObjectType() {
+		return objectType;
+	}
+
+	public void setObjectType(int objectType) {
+		this.objectType = objectType;
+	}
+
+	public int getObjectID() {
+		return objectID;
+	}
+
+	public void setObjectID(int objectID) {
+		this.objectID = objectID;
+	}
+}
diff --git a/src/engine/net/client/msg/TransferItemFromBankToInventoryMsg.java b/src/engine/net/client/msg/TransferItemFromBankToInventoryMsg.java
new file mode 100644
index 00000000..b68c7df4
--- /dev/null
+++ b/src/engine/net/client/msg/TransferItemFromBankToInventoryMsg.java
@@ -0,0 +1,220 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+
+import engine.exception.SerializationException;
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+
+/**
+ * Transfer item from bank to inventory
+ *
+ * @author Eighty
+ */
+
+public class TransferItemFromBankToInventoryMsg extends ClientNetMsg {
+
+	private long playerCompID1;
+	private long playerCompID2;
+	private int type;
+	private int objectUUID;
+	private int unknown1;
+	private int unknown2;
+	private int numItems;
+	private byte unknown4;
+
+	/**
+	 * This is the general purpose constructor
+	 */
+	public TransferItemFromBankToInventoryMsg(long playerCompID1,
+			long playerCompID2, int type, int objectUUID, int unknown1,
+			int unknown2, int numItems, byte unknown4) {
+		super(Protocol.TRANSFERITEMFROMBANK);
+		this.playerCompID1 = playerCompID1;
+		this.playerCompID2 = playerCompID2;
+		this.type = type;
+		this.objectUUID = objectUUID;
+		this.unknown1 = unknown1;
+		this.unknown2 = unknown2;
+		this.numItems = numItems;
+		this.unknown4 = unknown4;
+	}
+
+	public TransferItemFromBankToInventoryMsg(TransferItemFromInventoryToBankMsg msg) {
+		super(Protocol.TRANSFERITEMFROMBANK);
+		this.playerCompID1 = msg.getPlayerCompID1();
+		this.playerCompID2 = msg.getPlayerCompID2();
+		this.type = msg.getType();
+		this.objectUUID = msg.getUUID();
+		this.unknown1 = msg.getUnknown1();
+		this.unknown2 = msg.getUnknown2();
+		this.numItems = msg.getNumItems();
+		this.unknown4 = msg.getUnknown4();
+	}
+
+	/**
+	 * This is the general purpose constructor
+	 */
+	public TransferItemFromBankToInventoryMsg() {
+		super(Protocol.TRANSFERITEMFROMBANK);
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public TransferItemFromBankToInventoryMsg(AbstractConnection origin,
+			ByteBufferReader reader)  {
+		super(Protocol.TRANSFERITEMFROMBANK, origin, reader);
+	}
+
+	/**
+	 * Deserializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)
+			 {
+		playerCompID1 = reader.getLong();
+		playerCompID2 = reader.getLong();
+		type = reader.getInt();
+		objectUUID = reader.getInt();
+		unknown1 = reader.getInt();
+		unknown2 = reader.getInt();
+		numItems = reader.getInt();
+		unknown4 = reader.get();
+	}
+
+	/**
+	 * Serializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer)
+			throws SerializationException {
+		writer.putLong(playerCompID1);
+		writer.putLong(playerCompID2);
+		writer.putInt(type);
+		writer.putInt(objectUUID);
+		writer.putInt(unknown1);
+		writer.putInt(unknown2);
+		writer.putInt(numItems);
+		writer.put(unknown4);
+	}
+
+	/**
+	 * @return the playerCompID1
+	 */
+	public long getPlayerCompID1() {
+		return playerCompID1;
+	}
+
+	/**
+	 * @param playerCompID1 the playerCompID1 to set
+	 */
+	public void setPlayerCompID1(long playerCompID1) {
+		this.playerCompID1 = playerCompID1;
+	}
+
+	/**
+	 * @return the playerCompID2
+	 */
+	public long getPlayerCompID2() {
+		return playerCompID2;
+	}
+
+	/**
+	 * @param playerCompID2 the playerCompID2 to set
+	 */
+	public void setPlayerCompID2(long playerCompID2) {
+		this.playerCompID2 = playerCompID2;
+	}
+
+	/**
+	 * @return the type
+	 */
+	public int getType() {
+		return type;
+	}
+
+	/**
+	 * @param type the type to set
+	 */
+	public void setType(int type) {
+		this.type = type;
+	}
+
+	/**
+	 * @return the objectUUID
+	 */
+	public int getUUID() {
+		return objectUUID;
+	}
+
+	/**
+	 * @return the unknown1
+	 */
+	public int getUnknown1() {
+		return unknown1;
+	}
+
+	/**
+	 * @param unknown1 the unknown1 to set
+	 */
+	public void setUnknown1(int unknown1) {
+		this.unknown1 = unknown1;
+	}
+
+	/**
+	 * @return the unknown2
+	 */
+	public int getUnknown2() {
+		return unknown2;
+	}
+
+	/**
+	 * @param unknown2 the unknown2 to set
+	 */
+	public void setUnknown2(int unknown2) {
+		this.unknown2 = unknown2;
+	}
+
+	/**
+	 * @return the numItems
+	 */
+	public int getNumItems() {
+		return numItems;
+	}
+
+	/**
+	 * @param numItems the numItems to set
+	 */
+	public void setNumItems(int numItems) {
+		this.numItems = numItems;
+	}
+
+	/**
+	 * @return the unknown4
+	 */
+	public byte getUnknown4() {
+		return unknown4;
+	}
+
+	/**
+	 * @param unknown4 the unknown4 to set
+	 */
+	public void setUnknown4(byte unknown4) {
+		this.unknown4 = unknown4;
+	}
+
+}
diff --git a/src/engine/net/client/msg/TransferItemFromEquipToInventoryMsg.java b/src/engine/net/client/msg/TransferItemFromEquipToInventoryMsg.java
new file mode 100644
index 00000000..7aef05b3
--- /dev/null
+++ b/src/engine/net/client/msg/TransferItemFromEquipToInventoryMsg.java
@@ -0,0 +1,91 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+
+import engine.exception.SerializationException;
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.objects.AbstractGameObject;
+
+public class TransferItemFromEquipToInventoryMsg extends ClientNetMsg {
+
+	private int playerType;
+	private int playerUUID;
+	private int slotNumber;
+
+    /**
+	 * This is the general purpose constructor.
+	 */
+	public TransferItemFromEquipToInventoryMsg(AbstractGameObject ago, int slotNumber) {
+		super(Protocol.UNEQUIP);
+		this.playerType = ago.getObjectType().ordinal();
+		this.playerUUID = ago.getObjectUUID();
+		this.slotNumber = slotNumber;
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public TransferItemFromEquipToInventoryMsg(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.UNEQUIP, origin, reader);
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		this.playerType = reader.getInt();
+		this.playerUUID = reader.getInt();
+		this.slotNumber = reader.getInt();
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) throws SerializationException {
+		writer.putInt(this.playerType);
+		writer.putInt(this.playerUUID);
+		writer.putInt(this.slotNumber);
+	}
+
+	
+
+	/**
+	 * @return the slotNumber
+	 */
+	public int getSlotNumber() {
+		return slotNumber;
+	}
+
+	public int getPlayerType() {
+		return playerType;
+	}
+
+	public void setPlayerType(int playerType) {
+		this.playerType = playerType;
+	}
+
+	public int getPlayerUUID() {
+		return playerUUID;
+	}
+
+	public void setPlayerUUID(int playerUUID) {
+		this.playerUUID = playerUUID;
+	}
+
+}
diff --git a/src/engine/net/client/msg/TransferItemFromInventoryToBankMsg.java b/src/engine/net/client/msg/TransferItemFromInventoryToBankMsg.java
new file mode 100644
index 00000000..124f684e
--- /dev/null
+++ b/src/engine/net/client/msg/TransferItemFromInventoryToBankMsg.java
@@ -0,0 +1,220 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+
+import engine.exception.SerializationException;
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+
+/**
+ * Transfer item from inventory to bank
+ *
+ * @author Eighty
+ */
+
+public class TransferItemFromInventoryToBankMsg extends ClientNetMsg {
+
+	private long playerCompID1;
+	private long playerCompID2;
+	private int type;
+	private int objectUUID;
+	private int unknown1;
+	private int unknown2;
+	private int numItems;
+	private byte unknown4;
+
+	/**
+	 * This is the general purpose constructor
+	 */
+	public TransferItemFromInventoryToBankMsg(long playerCompID1,
+			long playerCompID2, int type, int objectUUID, int unknown1,
+			int unknown2, int numItems, byte unknown4) {
+		super(Protocol.TRANSFERITEMTOBANK);
+		this.playerCompID1 = playerCompID1;
+		this.playerCompID2 = playerCompID2;
+		this.type = type;
+		this.objectUUID = objectUUID;
+		this.unknown1 = unknown1;
+		this.unknown2 = unknown2;
+		this.numItems = numItems;
+		this.unknown4 = unknown4;
+	}
+
+	public TransferItemFromInventoryToBankMsg(TransferItemFromBankToInventoryMsg msg) {
+		super(Protocol.TRANSFERITEMTOBANK);
+		this.playerCompID1 = msg.getPlayerCompID1();
+		this.playerCompID2 = msg.getPlayerCompID2();
+		this.type = msg.getType();
+		this.objectUUID = msg.getUUID();
+		this.unknown1 = msg.getUnknown1();
+		this.unknown2 = msg.getUnknown2();
+		this.numItems = msg.getNumItems();
+		this.unknown4 = msg.getUnknown4();
+	}
+
+	/**
+	 * This is the general purpose constructor
+	 */
+	public TransferItemFromInventoryToBankMsg() {
+		super(Protocol.TRANSFERITEMTOBANK);
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public TransferItemFromInventoryToBankMsg(AbstractConnection origin,
+			ByteBufferReader reader)  {
+		super(Protocol.TRANSFERITEMTOBANK, origin, reader);
+	}
+
+	/**
+	 * Deserializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)
+			 {
+		playerCompID1 = reader.getLong();
+		playerCompID2 = reader.getLong();
+		type = reader.getInt();
+		objectUUID = reader.getInt();
+		unknown1 = reader.getInt();
+		unknown2 = reader.getInt();
+		numItems = reader.getInt();
+		unknown4 = reader.get();
+	}
+
+	/**
+	 * Serializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer)
+			throws SerializationException {
+		writer.putLong(playerCompID1);
+		writer.putLong(playerCompID2);
+		writer.putInt(type);
+		writer.putInt(objectUUID);
+		writer.putInt(unknown1);
+		writer.putInt(unknown2);
+		writer.putInt(numItems);
+		writer.put(unknown4);
+	}
+
+	/**
+	 * @return the playerCompID1
+	 */
+	public long getPlayerCompID1() {
+		return playerCompID1;
+	}
+
+	/**
+	 * @param playerCompID1 the playerCompID1 to set
+	 */
+	public void setPlayerCompID1(long playerCompID1) {
+		this.playerCompID1 = playerCompID1;
+	}
+
+	/**
+	 * @return the playerCompID2
+	 */
+	public long getPlayerCompID2() {
+		return playerCompID2;
+	}
+
+	/**
+	 * @param playerCompID2 the playerCompID2 to set
+	 */
+	public void setPlayerCompID2(long playerCompID2) {
+		this.playerCompID2 = playerCompID2;
+	}
+
+	/**
+	 * @return the type
+	 */
+	public int getType() {
+		return type;
+	}
+
+	/**
+	 * @param type the type to set
+	 */
+	public void setType(int type) {
+		this.type = type;
+	}
+
+	/**
+	 * @return the objectUUID
+	 */
+	public int getUUID() {
+		return objectUUID;
+	}
+
+	/**
+	 * @return the unknown1
+	 */
+	public int getUnknown1() {
+		return unknown1;
+	}
+
+	/**
+	 * @param unknown1 the unknown1 to set
+	 */
+	public void setUnknown1(int unknown1) {
+		this.unknown1 = unknown1;
+	}
+
+	/**
+	 * @return the unknown2
+	 */
+	public int getUnknown2() {
+		return unknown2;
+	}
+
+	/**
+	 * @param unknown2 the unknown2 to set
+	 */
+	public void setUnknown2(int unknown2) {
+		this.unknown2 = unknown2;
+	}
+
+	/**
+	 * @return the numItems
+	 */
+	public int getNumItems() {
+		return numItems;
+	}
+
+	/**
+	 * @param numItems the numItems to set
+	 */
+	public void setNumItems(int numItems) {
+		this.numItems = numItems;
+	}
+
+	/**
+	 * @return the unknown4
+	 */
+	public byte getUnknown4() {
+		return unknown4;
+	}
+
+	/**
+	 * @param unknown4 the unknown4 to set
+	 */
+	public void setUnknown4(byte unknown4) {
+		this.unknown4 = unknown4;
+	}
+
+}
diff --git a/src/engine/net/client/msg/TransferItemFromInventoryToEquipMsg.java b/src/engine/net/client/msg/TransferItemFromInventoryToEquipMsg.java
new file mode 100644
index 00000000..223d7a54
--- /dev/null
+++ b/src/engine/net/client/msg/TransferItemFromInventoryToEquipMsg.java
@@ -0,0 +1,221 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+
+import engine.exception.SerializationException;
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.objects.AbstractCharacter;
+
+public class TransferItemFromInventoryToEquipMsg extends ClientNetMsg {
+
+	private int sourceType;
+	private int sourceID;
+	private int pad1;
+	private int itemBase;
+	private int type;
+	private int objectUUID;
+	private int slotNumber;
+	private int pad2;
+	private float unknown1, unknown2;
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public TransferItemFromInventoryToEquipMsg() {
+		super(Protocol.EQUIP);
+	}
+
+	public TransferItemFromInventoryToEquipMsg(AbstractCharacter source, int slot, int itemBaseID) {
+		super(Protocol.EQUIP);
+		this.sourceType = source.getObjectType().ordinal();
+		this.sourceID = source.getObjectUUID();
+		this.slotNumber = slot;
+		this.itemBase = itemBaseID;
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public TransferItemFromInventoryToEquipMsg(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.EQUIP, origin, reader);
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		this.sourceType = reader.getInt();
+		this.sourceID = reader.getInt();
+		pad1 = reader.getInt();
+		itemBase = reader.getInt();
+		type = reader.getInt();
+		objectUUID = reader.getInt();
+		slotNumber = reader.getInt();
+		pad2 = reader.getInt();
+		unknown1 = reader.getFloat();
+		unknown2 = reader.getFloat();
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) throws SerializationException {
+		writer.putInt(this.sourceType);
+		writer.putInt(this.sourceID);
+		writer.putInt(pad1);
+		writer.putInt(itemBase);
+		writer.putInt(type);
+		writer.putInt(objectUUID);
+		writer.putInt(slotNumber);
+		writer.putInt(pad2);
+		writer.putFloat(unknown1);
+		writer.putFloat(unknown1);
+	}
+
+
+
+	/**
+	 * @return the pad1
+	 */
+	public int getPad1() {
+		return pad1;
+	}
+
+	/**
+	 * @param pad1
+	 *            the pad1 to set
+	 */
+	public void setPad1(int pad1) {
+		this.pad1 = pad1;
+	}
+
+	/**
+	 * @return the itemBase
+	 */
+	public int getItemBase() {
+		return itemBase;
+	}
+
+	/**
+	 * @param itemBase
+	 *            the itemBase to set
+	 */
+	public void setItemBase(int itemBase) {
+		this.itemBase = itemBase;
+	}
+
+	/**
+	 * @return the type
+	 */
+	public int getType() {
+		return type;
+	}
+
+	/**
+	 * @param type
+	 *            the type to set
+	 */
+	public void setType(int type) {
+		this.type = type;
+	}
+
+	/**
+	 * @return the objectUUID
+	 */
+	public int getUUID() {
+		return objectUUID;
+	}
+
+	/**
+	 * @return the slotNumber
+	 */
+	public int getSlotNumber() {
+		return slotNumber;
+	}
+
+	/**
+	 * @param slotNumber
+	 *            the slotNumber to set
+	 */
+	public void setSlotNumber(int slotNumber) {
+		this.slotNumber = slotNumber;
+	}
+
+	/**
+	 * @return the pad2
+	 */
+	public int getPad2() {
+		return pad2;
+	}
+
+	/**
+	 * @param pad2
+	 *            the pad2 to set
+	 */
+	public void setPad2(int pad2) {
+		this.pad2 = pad2;
+	}
+
+	/**
+	 * @return the unknown1
+	 */
+	public float getUnknown1() {
+		return unknown1;
+	}
+
+	/**
+	 * @param unknown1
+	 *            the unknown1 to set
+	 */
+	public void setUnknown1(float unknown1) {
+		this.unknown1 = unknown1;
+	}
+
+	/**
+	 * @return the unknown2
+	 */
+	public float getUnknown2() {
+		return unknown2;
+	}
+
+	/**
+	 * @param unknown2
+	 *            the unknown2 to set
+	 */
+	public void setUnknown2(float unknown2) {
+		this.unknown2 = unknown2;
+	}
+
+	public int getSourceType() {
+		return sourceType;
+	}
+
+	public void setSourceType(int sourceType) {
+		this.sourceType = sourceType;
+	}
+
+	public int getSourceID() {
+		return sourceID;
+	}
+
+	public void setSourceID(int sourceID) {
+		this.sourceID = sourceID;
+	}
+
+}
diff --git a/src/engine/net/client/msg/TransferItemFromInventoryToVaultMsg.java b/src/engine/net/client/msg/TransferItemFromInventoryToVaultMsg.java
new file mode 100644
index 00000000..e1c5bc47
--- /dev/null
+++ b/src/engine/net/client/msg/TransferItemFromInventoryToVaultMsg.java
@@ -0,0 +1,207 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+
+import engine.exception.SerializationException;
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+
+/**
+ * Transfer item from inventory to vault
+ *
+ * @author Eighty
+ */
+
+public class TransferItemFromInventoryToVaultMsg extends ClientNetMsg {
+
+	private int unknown01;
+	private int unknown02;
+	private long playerCompID;
+	private int type;
+	private int objectUUID;
+	private int unknown03;
+	private int unknown04;
+
+	/**
+	 * This is the general purpose constructor
+	 */
+	public TransferItemFromInventoryToVaultMsg(long playerCompID,
+			int unknown01, int unknown02, int type, int objectUUID, int unknown03,
+			int unknown04) {
+		super(Protocol.ITEMTOVAULT);
+		this.playerCompID = playerCompID;
+		this.unknown01 = unknown01;
+		this.unknown02 = unknown02;
+		this.type = type;
+		this.objectUUID = objectUUID;
+		this.unknown03 = unknown03;
+		this.unknown04 = unknown04;
+	}
+
+	public TransferItemFromInventoryToVaultMsg(TransferItemFromVaultToInventoryMsg msg) {
+		super(Protocol.ITEMTOVAULT);
+		this.playerCompID = msg.getPlayerCompID();
+		this.unknown01 = msg.getUnknown01();
+		this.unknown02 = msg.getUnknown02();
+		this.type = msg.getType();
+		this.objectUUID = msg.getUUID();
+		this.unknown03 = msg.getUnknown03();
+		this.unknown04 = msg.getUnknown04();
+	}
+
+	/**
+	 * This is the general purpose constructor
+	 */
+	public TransferItemFromInventoryToVaultMsg() {
+		super(Protocol.ITEMTOVAULT);
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public TransferItemFromInventoryToVaultMsg(AbstractConnection origin,
+			ByteBufferReader reader)  {
+		super(Protocol.ITEMTOVAULT, origin, reader);
+	}
+
+	/**
+	 * Deserializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)
+			 {
+		playerCompID = reader.getLong();
+		unknown01 = reader.getInt();
+		unknown02 = reader.getInt();
+		type = reader.getInt();
+		objectUUID = reader.getInt();
+		unknown03 = reader.getInt();
+		unknown04 = reader.getInt();
+	}
+
+	/**
+	 * Serializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer)
+			throws SerializationException {
+		writer.putLong(playerCompID);
+		writer.putInt(unknown01);
+		writer.putInt(unknown02);
+		writer.putInt(type);
+		writer.putInt(objectUUID);
+		writer.putInt(unknown03);
+		writer.putInt(unknown04);
+	}
+
+	/**
+	 * @return the unknown01
+	 */
+	public int getUnknown01() {
+		return unknown01;
+	}
+
+	/**
+	 * @param unknown01
+	 *            the unknown01 to set
+	 */
+	public void setUnknown01(int unknown01) {
+		this.unknown01 = unknown01;
+	}
+
+	/**
+	 * @return the unknown02
+	 */
+	public int getUnknown02() {
+		return unknown02;
+	}
+
+	/**
+	 * @param unknown02
+	 *            the unknown02 to set
+	 */
+	public void setUnknown02(int unknown02) {
+		this.unknown02 = unknown02;
+	}
+
+	/**
+	 * @return the playerCompID
+	 */
+	public long getPlayerCompID() {
+		return playerCompID;
+	}
+
+	/**
+	 * @param playerCompID
+	 *            the playerCompID to set
+	 */
+	public void setPlayerCompID(long playerCompID) {
+		this.playerCompID = playerCompID;
+	}
+
+	/**
+	 * @return the type
+	 */
+	public int getType() {
+		return type;
+	}
+
+	/**
+	 * @param type
+	 *            the type to set
+	 */
+	public void setType(int type) {
+		this.type = type;
+	}
+
+	/**
+	 * @return the objectUUID
+	 */
+	public int getUUID() {
+		return objectUUID;
+	}
+        
+	/**
+	 * @return the unknown03
+	 */
+	public int getUnknown03() {
+		return unknown03;
+	}
+
+	/**
+	 * @param unknown03
+	 *            the unknown03 to set
+	 */
+	public void setUnknown03(int unknown03) {
+		this.unknown03 = unknown03;
+	}
+
+	/**
+	 * @return the unknown04
+	 */
+	public int getUnknown04() {
+		return unknown04;
+	}
+
+	/**
+	 * @param unknown04
+	 *            the unknown04 to set
+	 */
+	public void setUnknown04(int unknown04) {
+		this.unknown04 = unknown04;
+	}
+
+}
diff --git a/src/engine/net/client/msg/TransferItemFromVaultToInventoryMsg.java b/src/engine/net/client/msg/TransferItemFromVaultToInventoryMsg.java
new file mode 100644
index 00000000..dcef2821
--- /dev/null
+++ b/src/engine/net/client/msg/TransferItemFromVaultToInventoryMsg.java
@@ -0,0 +1,200 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+
+import engine.exception.SerializationException;
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+
+/**
+ * Transfer item from Vault to inventory
+ *
+ * @author Eighty
+ */
+
+public class TransferItemFromVaultToInventoryMsg extends ClientNetMsg {
+
+	private int unknown01;
+	private int unknown02;
+	private long playerCompID;
+	private int type;
+	private int objectUUID;
+	private int unknown03;
+	private int unknown04;
+
+	/**
+	 * This is the general purpose constructor
+	 */
+	public TransferItemFromVaultToInventoryMsg(int unknown01, int unknown02,
+			long playerCompID, int type, int objectUUID, int unknown03, int unknown04) {
+		super(Protocol.TRANSFERITEMFROMVAULTTOINVENTORY);
+		this.unknown01 = unknown01;
+		this.unknown02 = unknown02;
+		this.playerCompID = playerCompID;
+		this.type = type;
+		this.objectUUID = objectUUID;
+		this.unknown03 = unknown03;
+		this.unknown04 = unknown04;
+	}
+
+	public TransferItemFromVaultToInventoryMsg(TransferItemFromInventoryToVaultMsg msg) {
+		super(Protocol.TRANSFERITEMFROMVAULTTOINVENTORY);
+		this.playerCompID = msg.getPlayerCompID();
+		this.unknown01 = msg.getUnknown01();
+		this.unknown02 = msg.getUnknown02();
+		this.type = msg.getType();
+		this.objectUUID = msg.getUUID();
+		this.unknown03 = msg.getUnknown03();
+		this.unknown04 = msg.getUnknown04();
+	}
+
+	/**
+	 * This is the general purpose constructor
+	 */
+	public TransferItemFromVaultToInventoryMsg() {
+		super(Protocol.TRANSFERITEMFROMVAULTTOINVENTORY);
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public TransferItemFromVaultToInventoryMsg(AbstractConnection origin,
+			ByteBufferReader reader)  {
+		super(Protocol.TRANSFERITEMFROMVAULTTOINVENTORY, origin, reader);
+	}
+
+	/**
+	 * Deserializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)
+			 {
+		unknown01 = reader.getInt();
+		unknown02 = reader.getInt();
+		playerCompID = reader.getLong();
+		type = reader.getInt();
+		objectUUID = reader.getInt();
+		unknown03 = reader.getInt();
+		unknown04 = reader.getInt();
+	}
+
+	/**
+	 * Serializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer)
+			throws SerializationException {
+		writer.putInt(unknown01);
+		writer.putInt(unknown02);
+		writer.putLong(playerCompID);
+		writer.putInt(type);
+		writer.putInt(objectUUID);
+		writer.putInt(unknown03);
+		writer.putInt(unknown04);
+	}
+
+	/**
+	 * @return the unknown01
+	 */
+	public int getUnknown01() {
+		return unknown01;
+	}
+
+	/**
+	 * @param unknown01 the unknown01 to set
+	 */
+	public void setUnknown01(int unknown01) {
+		this.unknown01 = unknown01;
+	}
+
+	/**
+	 * @return the unknown02
+	 */
+	public int getUnknown02() {
+		return unknown02;
+	}
+
+	/**
+	 * @param unknown02 the unknown02 to set
+	 */
+	public void setUnknown02(int unknown02) {
+		this.unknown02 = unknown02;
+	}
+
+	/**
+	 * @return the playerCompID
+	 */
+	public long getPlayerCompID() {
+		return playerCompID;
+	}
+
+	/**
+	 * @param playerCompID the playerCompID to set
+	 */
+	public void setPlayerCompID(long playerCompID) {
+		this.playerCompID = playerCompID;
+	}
+
+	/**
+	 * @return the type
+	 */
+	public int getType() {
+		return type;
+	}
+
+	/**
+	 * @param type the type to set
+	 */
+	public void setType(int type) {
+		this.type = type;
+	}
+
+	/**
+	 * @return the objectUUID
+	 */
+	public int getUUID() {
+		return objectUUID;
+	}
+
+	/**
+	 * @return the unknown03
+	 */
+	public int getUnknown03() {
+		return unknown03;
+	}
+
+	/**
+	 * @param unknown03 the unknown03 to set
+	 */
+	public void setUnknown03(int unknown03) {
+		this.unknown03 = unknown03;
+	}
+
+	/**
+	 * @return the unknown04
+	 */
+	public int getUnknown04() {
+		return unknown04;
+	}
+
+	/**
+	 * @param unknown04 the unknown04 to set
+	 */
+	public void setUnknown04(int unknown04) {
+		this.unknown04 = unknown04;
+	}
+
+}
diff --git a/src/engine/net/client/msg/UncommitToTradeMsg.java b/src/engine/net/client/msg/UncommitToTradeMsg.java
new file mode 100644
index 00000000..d3429f29
--- /dev/null
+++ b/src/engine/net/client/msg/UncommitToTradeMsg.java
@@ -0,0 +1,95 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+
+import engine.exception.SerializationException;
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+
+
+/**
+ * Uncommit to trade
+ *
+ * @author Eighty
+ */
+public class UncommitToTradeMsg extends ClientNetMsg {
+
+	private int unknown01;
+	private long playerCompID;
+
+	/**
+	 * This is the general purpose constructor
+	 */
+	public UncommitToTradeMsg(int unknown01, long playerCompID) {
+		super(Protocol.TRADEUNCONFIRM);
+		this.unknown01 = unknown01;
+		this.playerCompID = playerCompID;
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the ByteBuffer into a message. If a BufferUnderflow occurs (based on reading past the limit) then this constructor Throws that Exception to the caller.
+	 */
+	public UncommitToTradeMsg(AbstractConnection origin, ByteBufferReader reader)
+			 {
+		super(Protocol.TRADEUNCONFIRM, origin, reader);
+	}
+
+	/**
+	 * Deserializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)
+			 {
+		unknown01 = reader.getInt();
+		playerCompID = reader.getLong();
+	}
+
+	/**
+	 * Serializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer)
+			throws SerializationException {
+		writer.putInt(unknown01);
+		writer.putLong(playerCompID);
+	}
+
+	/**
+	 * @return the unknown01
+	 */
+	public int getUnknown01() {
+		return unknown01;
+	}
+
+	/**
+	 * @param unknown01 the unknown01 to set
+	 */
+	public void setUnknown01(int unknown01) {
+		this.unknown01 = unknown01;
+	}
+
+	/**
+	 * @return the playerCompID
+	 */
+	public long getPlayerCompID() {
+		return playerCompID;
+	}
+
+	/**
+	 * @param playerCompID the playerCompID to set
+	 */
+	public void setPlayerCompID(long playerCompID) {
+		this.playerCompID = playerCompID;
+	}
+
+}
diff --git a/src/engine/net/client/msg/Unknown1Msg.java b/src/engine/net/client/msg/Unknown1Msg.java
new file mode 100644
index 00000000..29fa9ad5
--- /dev/null
+++ b/src/engine/net/client/msg/Unknown1Msg.java
@@ -0,0 +1,79 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+
+public class Unknown1Msg extends ClientNetMsg {
+
+	private int targetType;
+	private int targetID;
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public Unknown1Msg(int targetType, int targetID) {
+		super(Protocol.UNKNOWN1);
+		this.targetType = targetType;
+		this.targetID = targetID;
+	}
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public Unknown1Msg() {
+		super(Protocol.UNKNOWN1);
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public Unknown1Msg(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.UNKNOWN1, origin, reader);
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		this.targetType = reader.getInt();
+		this.targetID = reader.getInt();
+	}
+
+	/**
+	 * @return the targetType
+	 */
+	public int getTargetType() {
+		return targetType;
+	}
+
+	/**
+	 * @return the targetID
+	 */
+	public int getTargetID() {
+		return targetID;
+	}
+
+}
diff --git a/src/engine/net/client/msg/UnknownMsg.java b/src/engine/net/client/msg/UnknownMsg.java
new file mode 100644
index 00000000..f806125f
--- /dev/null
+++ b/src/engine/net/client/msg/UnknownMsg.java
@@ -0,0 +1,155 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+
+public class UnknownMsg extends ClientNetMsg {
+
+	private int unknown01;
+	private int unknown02;
+	private int unknown03;
+	private short unknown04;
+	private byte unknown05;
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public UnknownMsg() {
+		super(Protocol.UNKNOWN);
+		this.unknown01 = 0x40A5BDB0;
+		this.unknown02 = 0x342AA9F0;
+		this.unknown03 = 0;
+		this.unknown04 = (short) 0;
+		this.unknown05 = (byte) 0;
+
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public UnknownMsg(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.UNKNOWN, origin, reader);
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+		writer.putInt(this.unknown01);
+		writer.putInt(this.unknown02);
+		writer.putInt(this.unknown03);
+		writer.putShort(this.unknown04);
+		writer.put(this.unknown05);
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		reader.getFloat();
+		reader.getString();
+		reader.getString();
+		reader.getInt();
+		reader.get();
+		reader.getInt();
+		reader.getInt();
+		int unknownSize =reader.getInt();
+		for (int i = 0; i < unknownSize;i++)
+			reader.getInt();
+		reader.getInt();
+		reader.getString();
+	}
+
+	/**
+	 * @return the unknown01
+	 */
+	public int getUnknown01() {
+		return unknown01;
+	}
+
+	/**
+	 * @param unknown01
+	 *            the unknown01 to set
+	 */
+	public void setUnknown01(int unknown01) {
+		this.unknown01 = unknown01;
+	}
+
+	/**
+	 * @return the unknown02
+	 */
+	public int getUnknown02() {
+		return unknown02;
+	}
+
+	/**
+	 * @param unknown02
+	 *            the unknown02 to set
+	 */
+	public void setUnknown02(int unknown02) {
+		this.unknown02 = unknown02;
+	}
+
+	/**
+	 * @return the unknown03
+	 */
+	public int getUnknown03() {
+		return unknown03;
+	}
+
+	/**
+	 * @param unknown03
+	 *            the unknown03 to set
+	 */
+	public void setUnknown03(int unknown03) {
+		this.unknown03 = unknown03;
+	}
+
+	/**
+	 * @return the unknown04
+	 */
+	public short getUnknown04() {
+		return unknown04;
+	}
+
+	/**
+	 * @param unknown04
+	 *            the unknown04 to set
+	 */
+	public void setUnknown04(short unknown04) {
+		this.unknown04 = unknown04;
+	}
+
+	/**
+	 * @return the unknown05
+	 */
+	public byte getUnknown05() {
+		return unknown05;
+	}
+
+	/**
+	 * @param unknown05
+	 *            the unknown05 to set
+	 */
+	public void setUnknown05(byte unknown05) {
+		this.unknown05 = unknown05;
+	}
+
+}
diff --git a/src/engine/net/client/msg/UnloadObjectsMsg.java b/src/engine/net/client/msg/UnloadObjectsMsg.java
new file mode 100644
index 00000000..4e2c0770
--- /dev/null
+++ b/src/engine/net/client/msg/UnloadObjectsMsg.java
@@ -0,0 +1,96 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.objects.AbstractGameObject;
+import org.pmw.tinylog.Logger;
+
+import java.util.HashMap;
+
+
+public class UnloadObjectsMsg extends ClientNetMsg {
+
+	private HashMap<Integer,Integer> objectList = new HashMap<>();
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public UnloadObjectsMsg() {
+		super(Protocol.FORGETOBJECTS);
+		init();
+	}
+
+	private void init() {
+		objectList = new HashMap<>();
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public UnloadObjectsMsg(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.FORGETOBJECTS, origin, reader);
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+		
+		if (this.objectList == null){
+			writer.putInt(0);
+			return;
+		}
+			
+		writer.putInt(this.objectList.size());
+		for (int objectUUID: this.objectList.keySet()){
+			writer.putInt(this.objectList.get(objectUUID));
+			writer.putInt(objectUUID);
+		}
+			
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		init();
+		int size = reader.getInt();
+//		for (int i = 0; i < size; i++)
+//			this.objectList.add(reader.getLong());
+		Logger.info( "Client telling server to unload objects.. ??");
+	}
+
+	public HashMap<Integer,Integer> getObjectList() {
+		return this.objectList;
+	}
+
+	public void addObject(AbstractGameObject value) {
+		this.objectList.put(value.getObjectUUID(), value.getObjectType().ordinal());
+	}
+
+	public int size() {
+		return this.objectList.size();
+	}
+
+	@Override
+	protected int getPowerOfTwoBufferSize() {
+		return 13;
+	}
+}
diff --git a/src/engine/net/client/msg/UpdateCharOrMobMessage.java b/src/engine/net/client/msg/UpdateCharOrMobMessage.java
new file mode 100644
index 00000000..7607ba5d
--- /dev/null
+++ b/src/engine/net/client/msg/UpdateCharOrMobMessage.java
@@ -0,0 +1,178 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+
+import engine.exception.SerializationException;
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.objects.AbstractCharacter;
+
+public class UpdateCharOrMobMessage extends ClientNetMsg {
+
+	private int type;
+	private int npcType;
+	private int npcID;
+
+	private int playerType;
+	private int playerID;
+	private int size;
+	private int subRace;
+
+	private int pad = 0;
+	private int objectType;
+	private int objectUUID;
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public UpdateCharOrMobMessage(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.UPDATECHARORMOB, origin, reader);
+	}
+	/**
+	 * Deserializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+
+	}
+
+	public UpdateCharOrMobMessage(AbstractCharacter tar, int type, int subRaceID) {
+		super(Protocol.UPDATECHARORMOB);
+		this.playerType = tar.getObjectType().ordinal();
+		this.playerID = tar.getObjectUUID();
+		this.type = type;
+		this.subRace = subRaceID;
+	}
+	/**
+	 * Serializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) throws SerializationException {
+		writer.putInt(this.type);
+		if (this.type == 2){
+			writer.putInt(this.playerType);
+			writer.putInt(this.playerID);
+			writer.putInt(this.subRace);
+			writer.putInt(-600065291);
+			return;
+		}
+		writer.putInt(this.playerType);
+		writer.putInt(this.playerID);
+		writer.put((byte)1);
+		writer.putInt(0);
+		writer.putInt(this.subRace);
+		writer.putInt(this.playerType);
+		writer.putInt(this.playerID);
+		writer.putInt(0);
+		writer.putInt(0);
+		writer.putInt(0);
+		writer.putFloat(1);
+		writer.putFloat(1);
+		writer.putFloat(1);
+		writer.putFloat(1);
+		writer.putInt(0);
+		writer.putInt(0);
+		writer.putInt(0);
+		writer.putInt(0);
+		writer.putInt(0);
+		writer.putLong(-1);
+		writer.putInt(0);
+		writer.put((byte)1);
+		writer.putInt(0);
+		writer.put((byte)1);
+		writer.putInt(0);
+		writer.putInt(0);
+		writer.put((byte)1);
+		writer.putInt(0);
+		writer.putInt(0);
+		writer.putInt(0);
+		writer.putInt(0);
+		writer.putInt(0);
+		writer.putInt(0);
+		writer.putInt(4);
+		writer.putInt(0);
+		writer.putInt(0);
+		writer.putInt(1);
+		writer.putShort((short)0);
+		writer.put((byte)0);
+
+
+	}
+
+	public int getObjectType() {
+		return objectType;
+	}
+
+	public void setObjectType(int value) {
+		this.objectType = value;
+	}
+
+	public void setPad(int value) {
+		this.pad = value;
+	}
+
+	public int getUUID() {
+		return objectUUID;
+
+	}
+
+	public int getPad() {
+		return pad;
+	}
+	public int getType() {
+		return type;
+	}
+	public void setType(int type) {
+		this.type = type;
+	}
+	public int getSize() {
+		return size;
+	}
+	public void setSize(int size) {
+		this.size = size;
+	}
+
+	public int getSubRace() {
+		return subRace;
+	}
+	public void setSubRace(int subRace) {
+		this.subRace = subRace;
+	}
+	public int getNpcType() {
+		return npcType;
+	}
+	public void setNpcType(int npcType) {
+		this.npcType = npcType;
+	}
+	public int getNpcID() {
+		return npcID;
+	}
+	public void setNpcID(int npcID) {
+		this.npcID = npcID;
+	}
+	public int getPlayerType() {
+		return playerType;
+	}
+	public void setPlayerType(int playerType) {
+		this.playerType = playerType;
+	}
+	public int getPlayerID() {
+		return playerID;
+	}
+	public void setPlayerID(int playerID) {
+		this.playerID = playerID;
+	}
+}
diff --git a/src/engine/net/client/msg/UpdateClientAlliancesMsg.java b/src/engine/net/client/msg/UpdateClientAlliancesMsg.java
new file mode 100644
index 00000000..ed74bbaa
--- /dev/null
+++ b/src/engine/net/client/msg/UpdateClientAlliancesMsg.java
@@ -0,0 +1,95 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+import engine.Enum.GameObjectType;
+import engine.exception.SerializationException;
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.objects.Guild;
+
+
+public class UpdateClientAlliancesMsg extends ClientNetMsg {
+
+
+	private int guildID;
+
+
+	public UpdateClientAlliancesMsg(Guild guild) {
+		super(Protocol.UPDATECLIENTALLIANCES);
+		this.guildID = guild.getObjectUUID();
+
+	}
+
+	public UpdateClientAlliancesMsg() {
+		super(Protocol.UPDATECLIENTALLIANCES);
+	}
+
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public UpdateClientAlliancesMsg(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.UPDATECLIENTALLIANCES, origin, reader);
+	}
+	//CALL THIS AFTER SANITY CHECKS AND BEFORE UPDATING HEALTH/GOLD.
+
+
+	/**
+	 * Deserializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		reader.getInt();
+		reader.getInt();
+		reader.getInt();
+		reader.getInt();
+		reader.get();
+	}
+
+
+	// Precache and configure this message before we serialize it
+
+
+	/**
+	 * Serializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) throws SerializationException {
+
+		Guild guild = Guild.getGuild(this.guildID);
+
+		writer.putInt(guild.getAllyList().size());
+
+		for (Guild allies: guild.getAllyList()){
+			writer.putInt(GameObjectType.Guild.ordinal());
+			writer.putInt(allies.getObjectUUID());
+		}
+
+		writer.putInt(guild.getEnemyList().size());
+		for (Guild enemies: guild.getEnemyList()){
+			writer.putInt(GameObjectType.Guild.ordinal());
+			writer.putInt(enemies.getObjectUUID());
+		}
+		writer.putInt(0);
+		writer.putInt(0);
+		writer.put((byte)1);
+	}
+
+	public int getGuildID() {
+		return guildID;
+	}
+
+}
diff --git a/src/engine/net/client/msg/UpdateEffectsMsg.java b/src/engine/net/client/msg/UpdateEffectsMsg.java
new file mode 100644
index 00000000..b5248cac
--- /dev/null
+++ b/src/engine/net/client/msg/UpdateEffectsMsg.java
@@ -0,0 +1,86 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.objects.AbstractWorldObject;
+import engine.objects.Effect;
+
+import java.util.ArrayList;
+
+public class UpdateEffectsMsg extends ClientNetMsg {
+
+	AbstractWorldObject awo;
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public UpdateEffectsMsg() {
+		super(Protocol.UPDATEEFFECTS);
+		this.awo = null;
+	}
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public UpdateEffectsMsg(AbstractWorldObject awo) {
+		super(Protocol.UPDATEEFFECTS);
+		this.awo = awo;
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public UpdateEffectsMsg(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.UPDATEEFFECTS, origin, reader);
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+		if (awo == null) {
+			writer.putInt(0);
+			writer.putInt(0);
+			writer.putInt(0);
+		} else {
+			writer.putInt(awo.getObjectType().ordinal());
+			writer.putInt(awo.getObjectUUID());
+
+			ArrayList<Effect> effects = new ArrayList<>(awo.getEffects().values());
+			writer.putInt(effects.size());
+			for (Effect effect : effects)
+				effect.serializeForClientMsg(writer);
+		}
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+	}
+
+	public AbstractWorldObject getAwo() {
+		return this.awo;
+	}
+
+	public void setAwo(AbstractWorldObject awo) {
+		this.awo = awo;
+	}
+}
diff --git a/src/engine/net/client/msg/UpdateFriendStatusMessage.java b/src/engine/net/client/msg/UpdateFriendStatusMessage.java
new file mode 100644
index 00000000..b0f6bfb7
--- /dev/null
+++ b/src/engine/net/client/msg/UpdateFriendStatusMessage.java
@@ -0,0 +1,78 @@
+/*
+HashSet<Integer> playerFriendSet = PlayerFriendsMap.get(playerUID);
+			playerFriendSet.add(friendUID); * Copyright 2013 MagicBane Emulator Project
+ * All Rights Reserved
+ */
+package engine.net.client.msg;
+
+
+import engine.Enum.GameObjectType;
+import engine.gameManager.SessionManager;
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.objects.PlayerCharacter;
+
+
+public class UpdateFriendStatusMessage extends ClientNetMsg {
+	
+	public int statusType;
+	public PlayerCharacter player;
+	public boolean online = true;
+
+
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public UpdateFriendStatusMessage(PlayerCharacter player) {
+		super(Protocol.UPDATEFRIENDSTATUS);
+		this.player = player;
+		this.online = SessionManager.getPlayerCharacterByID(player.getObjectUUID()) != null ? true : false;
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public UpdateFriendStatusMessage(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.UPDATEFRIENDSTATUS, origin, reader);
+	}
+
+	/**
+	 * Copy constructor
+	 */
+	public UpdateFriendStatusMessage(UpdateFriendStatusMessage msg) {
+		super(Protocol.UPDATEFRIENDSTATUS);
+	}
+
+	
+	
+	/**
+	 * Deserializes the subclass specific items from the supplied ByteBufferReader.
+	 */
+	//message is serialize only, no need for deserialize.
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		reader.getInt();
+		reader.getInt();
+		reader.getInt();
+		reader.getInt();
+		this.statusType = reader.getInt();
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied ByteBufferWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+	writer.putInt(GameObjectType.PlayerCharacter.ordinal());
+	writer.putInt(player.getObjectUUID());
+	writer.putString(player.getCombinedName());
+	writer.putInt(online ? 0 : 1);
+	writer.putInt(player.friendStatus.ordinal());
+	}
+}
diff --git a/src/engine/net/client/msg/UpdateGoldMsg.java b/src/engine/net/client/msg/UpdateGoldMsg.java
new file mode 100644
index 00000000..cb3fc5be
--- /dev/null
+++ b/src/engine/net/client/msg/UpdateGoldMsg.java
@@ -0,0 +1,119 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+import engine.Enum.GameObjectType;
+import engine.exception.SerializationException;
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.objects.AbstractWorldObject;
+import engine.objects.CharacterItemManager;
+import engine.objects.Item;
+import engine.objects.PlayerCharacter;
+
+/**
+ * Update gold in inventory and/or bank
+ *
+ * @author Eighty
+ */
+
+public class UpdateGoldMsg extends ClientNetMsg {
+
+	private AbstractWorldObject looter;
+    CharacterItemManager itemManager;
+	private Item goldInventory;
+	private Item goldBank;
+	private int tradeGold = 0;
+
+
+    /**
+     * This is the general purpose constructor
+     */
+    public UpdateGoldMsg(AbstractWorldObject player) {
+        super(Protocol.UPDATEGOLDVALUE);
+        this.looter = player;
+    }
+
+
+	/**
+	 * This is the general purpose constructor
+	 */
+	public UpdateGoldMsg() {
+		super(Protocol.UPDATEGOLDVALUE);
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public UpdateGoldMsg(AbstractConnection origin,
+			ByteBufferReader reader)  {
+		super(Protocol.UPDATEGOLDVALUE, origin, reader);
+	}
+
+	/**
+	 * Deserializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)
+			 {
+
+	}
+
+    // Pre-cache and set values so they are available when we
+    // serialize the data.
+
+    public void configure() {
+
+    	if (this.looter != null && this.looter.getObjectType() == GameObjectType.PlayerCharacter){
+    		itemManager = ((PlayerCharacter)looter).getCharItemManager();
+            goldInventory = itemManager.getGoldInventory();
+            this.tradeGold = itemManager.getGoldTrading();
+            goldBank = itemManager.getGoldBank();
+    	}else{
+    		itemManager = null;
+    		goldInventory = null;
+    		goldBank = null;
+    	}
+    }
+
+	/**
+	 * Serializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer)
+			throws SerializationException {
+
+    	if (looter == null){
+            writer.putInt(0);
+            writer.putInt(0);
+    	}else{
+            writer.putInt(looter.getObjectType().ordinal());
+            writer.putInt(looter.getObjectUUID());
+    	}
+
+        if (goldInventory != null && goldInventory.getNumOfItems() - this.tradeGold > 0) {
+            writer.put((byte) 1);
+            Item.serializeForClientMsgWithoutSlot(goldInventory,writer);
+        } else
+            writer.put((byte) 0);
+
+        if (goldBank != null && goldBank.getNumOfItems() != 0) {
+            writer.put((byte) 1);
+            Item.serializeForClientMsgWithoutSlot(goldBank,writer);
+        } else
+            writer.put((byte) 0);
+    }
+
+}
diff --git a/src/engine/net/client/msg/UpdateInventoryMsg.java b/src/engine/net/client/msg/UpdateInventoryMsg.java
new file mode 100644
index 00000000..c9480eb8
--- /dev/null
+++ b/src/engine/net/client/msg/UpdateInventoryMsg.java
@@ -0,0 +1,108 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+import engine.Enum.ItemType;
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.objects.Item;
+
+import java.util.ArrayList;
+
+public class UpdateInventoryMsg extends ClientNetMsg {
+
+	private ArrayList<Item> toAdd;
+	private ArrayList<Item> bank;
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public UpdateInventoryMsg(ArrayList<Item> inventory,ArrayList<Item> bank, Item gold, boolean add) {
+		super(Protocol.UPDATECLIENTINVENTORIES);
+		toAdd = inventory;
+		if (gold != null)
+			toAdd.add(gold);
+
+		this.bank = bank;
+
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public UpdateInventoryMsg(AbstractConnection origin, ByteBufferReader reader)
+			 {
+		super(Protocol.UPDATECLIENTINVENTORIES, origin, reader);
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+		putList(writer, this.toAdd);
+		if (bank != null)
+			putList(writer, this.bank);
+		else
+			writer.putInt(0);
+	}
+
+	public static void putList(ByteBufferWriter writer, ArrayList<Item> list) {
+		int indexPosition = writer.position();
+		writer.putInt(0);
+
+		int serialized = 0;
+
+
+		for (Item item : list) {
+			if (item.getItemBase().getType().equals(ItemType.GOLD)) {
+				if (item.getNumOfItems() == 0)
+					continue;
+			}
+			Item.serializeForClientMsgWithoutSlot(item,writer);
+			++serialized;
+		}
+
+		writer.putIntAt(serialized, indexPosition);
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)
+			 {
+	}
+
+	public ArrayList<Item> getToAdd() {
+		return this.toAdd;
+	}
+
+
+	public void setToAdd(ArrayList<Item> value) {
+		this.toAdd = value;
+	}
+
+
+	public void addToInventory(Item value) {
+		this.toAdd.add(value);
+	}
+
+
+	@Override
+	protected int getPowerOfTwoBufferSize() {
+		return 20;
+	}
+}
diff --git a/src/engine/net/client/msg/UpdateObjectMsg.java b/src/engine/net/client/msg/UpdateObjectMsg.java
new file mode 100644
index 00000000..364ac4f4
--- /dev/null
+++ b/src/engine/net/client/msg/UpdateObjectMsg.java
@@ -0,0 +1,136 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+
+import engine.exception.SerializationException;
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.objects.*;
+
+public class UpdateObjectMsg extends ClientNetMsg {
+	private int msgType;
+	private AbstractWorldObject ago;
+	
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public UpdateObjectMsg(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.UPDATEOBJECT, origin, reader);
+	}
+	
+	public UpdateObjectMsg() {
+		super(Protocol.UPDATEOBJECT);
+	}
+
+	public UpdateObjectMsg(AbstractWorldObject ago,int type) {
+		super(Protocol.UPDATEOBJECT);
+		if (ago == null)
+			return;
+		this.msgType = type;
+		this.ago = ago;
+
+		
+	}
+	/**
+	 * Deserializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) throws SerializationException {
+		writer.putInt(this.msgType);
+		switch (this.msgType){
+		case 2:
+			updateName(writer);
+			break;
+		case 3:
+			updateRank(writer);
+			break;
+		case 4:
+			derank(writer);
+			break;
+		case 5:
+			updateGuild(writer);
+			break;
+			
+		default:
+			break;
+				
+		}
+	}
+	
+	private void updateName(ByteBufferWriter writer){
+            
+                writer.putInt(ago.getObjectType().ordinal());
+                writer.putInt(ago.getObjectUUID());
+		writer.putString(ago.getName());
+		writer.putInt(0);
+	}
+	
+	private void updateRank(ByteBufferWriter writer){
+                writer.putInt(ago.getObjectType().ordinal());
+                writer.putInt(ago.getObjectUUID());
+		writer.putFloat(ago.getHealthMax());
+		writer.putFloat(ago.getCurrentHitpoints());
+		writer.put((byte)1);
+		writer.putInt(0);
+	
+	}
+	
+	private void updateGuild(ByteBufferWriter writer){
+            
+                writer.putInt(ago.getObjectType().ordinal());
+                writer.putInt(ago.getObjectUUID());
+
+		switch (ago.getObjectType()){
+		case Building:
+			Guild guild = ((Building)ago).getGuild();
+			GuildTag._serializeForDisplay(guild.getGuildTag(),writer);
+			GuildTag._serializeForDisplay(guild.getNation().getGuildTag(),writer);
+			writer.putInt(0);
+			
+			break;
+		default:
+			break;
+		}
+		
+	
+	}
+	
+	private void derank(ByteBufferWriter writer){
+		writer.putInt(0);
+		writer.putInt(0);
+		writer.put((byte)0);
+                writer.putInt(ago.getObjectType().ordinal());
+                writer.putInt(ago.getObjectUUID());
+		writer.putInt(0);
+	}
+
+
+	
+	public AbstractGameObject getAgo() {
+		return ago;
+	}
+
+
+}
diff --git a/src/engine/net/client/msg/UpdateStateMsg.java b/src/engine/net/client/msg/UpdateStateMsg.java
new file mode 100644
index 00000000..184234d9
--- /dev/null
+++ b/src/engine/net/client/msg/UpdateStateMsg.java
@@ -0,0 +1,222 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+
+
+
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+import engine.Enum.GameObjectType;
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.objects.AbstractCharacter;
+import engine.objects.PlayerCharacter;
+
+public class UpdateStateMsg extends ClientNetMsg {
+
+	private int charType;
+	private int charUUID;
+	private int activity; //1 dead,2 unconscious, 3 Sleeping,  4 resting,5 casting,6 IDLE,7 casting? , 8 nothing 9 unknown, 
+	private int speed; // 1 low, 2 high (walk,run)
+	private int aware; // 1 low, 2 high (combat off,combat on)
+
+	private int mode; // 0 unknown, 1 water, 2 ground, 3 flight.
+	private int fighting; // 1 disengaged, 2 engaged.
+	private int headlights; // LFGroup/LFGuild/Recruiting Icons
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public UpdateStateMsg() {
+		super(Protocol.UPDATESTATE);
+		this.fighting = 1;
+		this.headlights = 0;
+	}
+
+	public UpdateStateMsg(AbstractCharacter ac) {
+		super(Protocol.UPDATESTATE);
+		this.fighting = 1;
+		this.headlights = 0;
+		setPlayer(ac);
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public UpdateStateMsg(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.UPDATESTATE, origin, reader);
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+		writer.putInt(this.charType);
+		writer.putInt(this.charUUID);
+		writer.putInt(this.activity);
+		writer.putInt(this.speed);
+		writer.putInt(this.aware);
+		writer.putInt(this.mode);
+		writer.putInt(this.fighting);
+		writer.putInt(this.headlights);
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		this.charType = reader.getInt();
+		this.charUUID = reader.getInt();
+		this.activity = reader.getInt();
+		this.speed = reader.getInt();
+		this.aware = reader.getInt();
+
+		this.mode = reader.getInt();
+		this.fighting = reader.getInt();
+		this.headlights = reader.getInt();
+	}
+
+	/**
+	 * Sets this Msg's charUUID, sitStand, walkRun and combatToggle parameters
+	 * based on supplied AbstractCharacter
+	 *
+	 * @param ac
+	 */
+	public final void setPlayer(AbstractCharacter ac) {
+
+		PlayerCharacter player;
+
+		this.charType = ac.getObjectType().ordinal();
+		this.charUUID = ac.getObjectUUID();
+		this.activity = ac.getIsSittingAsInt();
+		this.speed = ac.getIsWalkingAsInt();
+		this.aware = ac.getIsCombatAsInt();
+
+		if (ac.getObjectType() == GameObjectType.PlayerCharacter) {
+			player = (PlayerCharacter)ac;
+			this.headlights = player.getHeadlightsAsInt();
+		} else this.headlights = 0;
+
+		this.mode = ac.getIsFlightAsInt();
+	}
+
+	/**
+	 * @return the charUUID
+	 */
+	public int getPlayerUUID() {
+		return charUUID;
+	}
+
+	/**
+	 * @return the sitStand
+	 */
+	public int getActivity() {
+		return activity;
+	}
+
+	/**
+	 * @param activity
+	 *            the sitStand to set
+	 */
+	public void setActivity(int activity) {
+		this.activity = activity;
+	}
+
+	/**
+	 * @return the walkRun
+	 */
+	public int getSpeed() {
+		return speed;
+	}
+
+	/**
+	 * @param speed
+	 *            the walkRun to set
+	 */
+	public void setSpeed(int speed) {
+		this.speed = speed;
+	}
+
+	/**
+	 * @return the combatToggle
+	 */
+	public int getAware() {
+		return aware;
+	}
+
+	/**
+	 * @param aware
+	 *            the combatToggle to set
+	 */
+	public void setAware(int aware) {
+		this.aware = aware;
+	}
+
+	/**
+	 * @return the unknown01
+	 */
+	public int getMode() {
+		return mode;
+	}
+
+	/**
+	 * @param mode
+	 *            the unknown01 to set
+	 */
+	public void setMode(int mode) {
+		this.mode = mode;
+	}
+
+	/**
+	 * @return the unknown02
+	 */
+	public int getFighting() {
+		return fighting;
+	}
+
+	/**
+	 * @param fighting
+	 *            the unknown02 to set
+	 */
+	public void setFighting(int fighting) {
+		this.fighting = fighting;
+	}
+
+	/**
+	 * @return the unknown03
+	 */
+	public int getHeadlights() {
+		return headlights;
+	}
+
+	/**
+	 * @param headlights
+	 *            the headlights to set
+	 */
+	public void setHeadlights(int headlights) {
+		this.headlights = headlights;
+	}
+
+}
diff --git a/src/engine/net/client/msg/UpdateTradeWindowMsg.java b/src/engine/net/client/msg/UpdateTradeWindowMsg.java
new file mode 100644
index 00000000..13c40adc
--- /dev/null
+++ b/src/engine/net/client/msg/UpdateTradeWindowMsg.java
@@ -0,0 +1,95 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+
+import engine.exception.SerializationException;
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.objects.Item;
+import engine.objects.PlayerCharacter;
+
+import java.util.ArrayList;
+
+/**
+ * Update trade window message.  Send item info to other player.
+ * @author Eighty
+ */
+public class UpdateTradeWindowMsg extends ClientNetMsg {
+
+	PlayerCharacter pc1;
+	PlayerCharacter pc2;
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public UpdateTradeWindowMsg(PlayerCharacter pc1, PlayerCharacter pc2) {
+		super(Protocol.UPDATETRADEWINDOW);
+		this.pc1 = pc1;
+		this.pc2 = pc2;
+	}
+
+	/**
+	 * Serializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer)
+			throws SerializationException {
+            
+                writer.putInt(pc1.getObjectType().ordinal());
+                writer.putInt(pc1.getObjectUUID());
+                
+                writer.putInt(pc2.getObjectType().ordinal());
+                writer.putInt(pc2.getObjectUUID());
+                
+         
+
+		ArrayList<Item> trading1 = new ArrayList<>();
+		
+		for (int itemID : pc1.getCharItemManager().getTrading()){
+			Item item = Item.getFromCache(itemID);
+			if (item == null)
+				continue;
+			trading1.add(item);
+		}
+		
+		ArrayList<Item> trading2 = new ArrayList<>();
+		for (int itemID : pc2.getCharItemManager().getTrading()){
+			Item item = Item.getFromCache(itemID);
+			if (item == null)
+				continue;
+			trading2.add(item);
+		}
+		Item.putTradingList(pc1,writer, trading1, false, pc1.getObjectUUID(),false,null);
+		Item.putTradingList(pc2,writer, trading2, false, pc2.getObjectUUID(),false,null);
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the ByteBuffer into a message. If a BufferUnderflow occurs (based on reading past the limit) then this constructor Throws that Exception to the caller.
+	 */
+	public UpdateTradeWindowMsg(AbstractConnection origin,
+			ByteBufferReader reader)  {
+		super(Protocol.UPDATETRADEWINDOW, origin, reader);
+	}
+
+	/**
+	 * Deserializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+	}
+
+	@Override
+	protected int getPowerOfTwoBufferSize() {
+		return 16;
+	}
+}
diff --git a/src/engine/net/client/msg/UpdateVaultMsg.java b/src/engine/net/client/msg/UpdateVaultMsg.java
new file mode 100644
index 00000000..f06c6b25
--- /dev/null
+++ b/src/engine/net/client/msg/UpdateVaultMsg.java
@@ -0,0 +1,69 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+
+
+import engine.exception.SerializationException;
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.objects.Account;
+
+/**
+ * Vault inventory contents
+ * @author Eighty
+ */
+public class UpdateVaultMsg extends ClientNetMsg {
+	
+	private int accountType;
+	private int accountID;
+	
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public UpdateVaultMsg(Account account) {
+		super(Protocol.CLIENTUPDATEVAULT);
+		this.accountType = account.getObjectType().ordinal();
+		this.accountID = account.getObjectUUID();
+	}
+
+	/**
+	 * Serializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer)
+			throws SerializationException {
+		writer.putInt(accountType);
+		writer.putInt(accountID);
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the ByteBuffer into a message. If a BufferUnderflow occurs (based on reading past the limit) then this constructor Throws that Exception to the caller.
+	 */
+	public UpdateVaultMsg(AbstractConnection origin,
+			ByteBufferReader reader)  {
+		super(Protocol.CLIENTUPDATEVAULT, origin, reader);
+	}
+
+	/**
+	 * Deserializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+	}
+
+	@Override
+	protected int getPowerOfTwoBufferSize() {
+		return 17;
+	}
+}
diff --git a/src/engine/net/client/msg/UpgradeAssetMessage.java b/src/engine/net/client/msg/UpgradeAssetMessage.java
new file mode 100644
index 00000000..1091944b
--- /dev/null
+++ b/src/engine/net/client/msg/UpgradeAssetMessage.java
@@ -0,0 +1,92 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+
+import engine.net.AbstractConnection;
+import engine.net.AbstractNetMsg;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+
+
+public class UpgradeAssetMessage extends ClientNetMsg {
+
+	private int unknown01;
+	private int unknown02;
+	private int buildingUUID;
+
+
+    /**
+	 * This is the general purpose constructor.
+	 */
+	public UpgradeAssetMessage() {
+		super(Protocol.UPGRADEASSET);
+	
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public UpgradeAssetMessage(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.UPGRADEASSET, origin, reader);
+	}
+
+	/**
+	 * @see AbstractNetMsg#getPowerOfTwoBufferSize()
+	 */
+	@Override
+	protected int getPowerOfTwoBufferSize() {
+		//Larger size for historically larger opcodes
+		return (16); // 2^16 == 64k
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+	
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		this.unknown01 = reader.getInt();
+		this.unknown02 = reader.getInt();
+                reader.getInt(); // Object Type Padding
+		this.buildingUUID = reader.getInt();
+	}
+
+	public void setUnknown01(int unknown01) {
+		this.unknown01 = unknown01;
+	}
+
+	public int getUnknown01() {
+		return unknown01;
+	}
+
+	public void setUnknown02(int unknown02) {
+		this.unknown02 = unknown02;
+	}
+
+	public int getUnknown02() {
+		return unknown02;
+	}
+
+	public int getBuildingUUID() {
+		return buildingUUID;
+	}
+}
diff --git a/src/engine/net/client/msg/UseCharterMsg.java b/src/engine/net/client/msg/UseCharterMsg.java
new file mode 100644
index 00000000..4387fedd
--- /dev/null
+++ b/src/engine/net/client/msg/UseCharterMsg.java
@@ -0,0 +1,105 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+import engine.Enum.ItemType;
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.objects.Item;
+import engine.objects.PlayerCharacter;
+
+
+public class UseCharterMsg extends ClientNetMsg {
+
+	private int unknown01;
+	private int unknown02;
+	private int unknown03;
+	private String type;
+	private int unknown04;
+	private int unknown05;
+	private int unknown06;
+	private boolean close = false;
+	private PlayerCharacter player;
+    private int charterUUID;
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public UseCharterMsg() {
+		super(Protocol.ACTIVATECHARTER);
+	}
+	
+	public UseCharterMsg(PlayerCharacter player, boolean close) {
+		super(Protocol.ACTIVATECHARTER);
+		this.close = close;
+		this.player = player;
+		
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public UseCharterMsg(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.ACTIVATECHARTER, origin, reader);
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+
+    public void configure() {
+
+        if (close) {
+            for (Item i : player.getInventory()) {
+                if (i.getItemBase().getType().equals(ItemType.GUILDCHARTER)) {
+                    charterUUID = i.getObjectUUID();
+                    break;
+                }
+            }
+        }
+    }
+
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {       
+		writer.putInt(this.unknown01);
+		writer.putInt(this.unknown02);
+		writer.putInt(this.unknown03);
+		writer.putString(this.type);
+		writer.putInt(this.unknown04);
+		writer.putInt(this.unknown05);
+		writer.putInt(this.unknown06);
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		this.unknown01 = reader.getInt();
+		this.unknown02 = reader.getInt();
+		this.unknown03 = reader.getInt();
+		this.type = reader.getString();
+		this.unknown04 = reader.getInt();
+		this.unknown05 = reader.getInt();
+		this.unknown06 = reader.getInt();
+	}
+
+	/**
+	 * @return the unknown01
+	 */
+	public void setUnknown02(int unknown02) {
+		this.unknown02 = unknown02;
+	}
+}
diff --git a/src/engine/net/client/msg/VendorDialogMsg.java b/src/engine/net/client/msg/VendorDialogMsg.java
new file mode 100644
index 00000000..2c3b80cc
--- /dev/null
+++ b/src/engine/net/client/msg/VendorDialogMsg.java
@@ -0,0 +1,905 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+import engine.Enum.DispatchChannel;
+import engine.Enum.GuildHistoryType;
+import engine.exception.MsgSendException;
+import engine.gameManager.BuildingManager;
+import engine.gameManager.DbManager;
+import engine.gameManager.GuildManager;
+import engine.gameManager.SessionManager;
+import engine.math.Vector3fImmutable;
+import engine.net.*;
+import engine.net.client.ClientConnection;
+import engine.net.client.Protocol;
+import engine.objects.*;
+import engine.server.MBServerStatics;
+
+import java.util.ArrayList;
+import java.util.concurrent.ThreadLocalRandom;
+
+public class VendorDialogMsg extends ClientNetMsg {
+
+	public static final int MSG_TYPE_VENDOR = 0;
+	public static final int MSG_TYPE_TRAINER = 1;
+	public static int cnt = 1;
+	private int messageType;
+	private String language;
+	private int vendorObjectType;
+	private int vendorObjectID;
+	private int unknown01;
+	private int unknown02;
+	private int unknown03;
+	private int unknown04;
+	private String dialogType = "TrainerDialog";
+	private String intro = "FighterIntro";
+	private String introCode = " [ FighterIntro ] ";
+	private String merchantCode = " [ Merchant options ] ";
+	private int menuType = 1; // 0: close, 1: normal, 2: train, 3: untrain
+	private VendorDialog vd;
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public VendorDialogMsg(int messageType, int vendorObjectType, int vendorObjectID, String dialogType, String intro, int menuType) {
+		super(Protocol.VENDORDIALOG);
+		this.messageType = messageType;
+		this.vendorObjectType = vendorObjectType;
+		this.vendorObjectID = vendorObjectID;
+		this.dialogType = dialogType;
+		this.intro = intro;
+		this.introCode = " [ " + intro + " ] ";
+		this.menuType = menuType;
+	}
+
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public VendorDialogMsg(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.VENDORDIALOG, origin, reader);
+	}
+
+	public static void replyDialog(VendorDialogMsg msg, ClientConnection origin) throws MsgSendException {
+
+		PlayerCharacter playerCharacter = SessionManager.getPlayerCharacter(origin);
+
+		if (playerCharacter == null)
+			return;
+
+		if (playerCharacter.getTimeStamp("lastvendorwindow") > System.currentTimeMillis()) {
+			return;
+		}
+
+		// Get NPC that player is talking to
+		NPC npc = NPC.getFromCache(msg.vendorObjectID);
+		int npcClassID;
+
+		if (npc == null)
+			return;
+
+		// test within talking range
+
+		if (playerCharacter.getLoc().distanceSquared2D(npc.getLoc()) > MBServerStatics.NPC_TALK_RANGE * MBServerStatics.NPC_TALK_RANGE) {
+			ErrorPopupMsg.sendErrorPopup(playerCharacter, 14);
+			return;
+		}
+
+		// Restrict disc trainers to only characters who have
+		// tht disc applied.
+
+		npcClassID = npc.getContract().getClassID();
+
+		if  (npc.getContract() != null &&
+				ApplyRuneMsg.isDiscipline(npcClassID)) {
+
+			if (playerCharacter.getRune(npcClassID) == null) {
+				ErrorPopupMsg.sendErrorPopup(playerCharacter, 49);
+				return;
+			}
+
+		}
+		playerCharacter.setLastNPCDialog(npc);
+
+		VendorDialog vd = null;
+		Contract contract = npc.getContract();
+
+		if (contract == null)
+			vd = VendorDialog.getHostileVendorDialog();
+		else if (npc.getBuilding() != null) {
+			if (BuildingManager.IsPlayerHostile(npc.getBuilding(), playerCharacter))
+				vd = VendorDialog.getHostileVendorDialog();
+			else vd = contract.getVendorDialog();
+		}
+		else
+			vd = contract.getVendorDialog();
+		if (vd == null)
+			vd = VendorDialog.getHostileVendorDialog();
+
+		if (msg.messageType == 1 || msg.unknown03 == vd.getObjectUUID()) {
+			msg.updateMessage(3, vd);
+		}
+		else {
+			if (VendorDialogMsg.handleSpecialCase(msg, npc, playerCharacter, vd, origin))
+				return;
+
+			vd = VendorDialog.getVendorDialog(msg.unknown03);
+			msg.updateMessage(3, vd);
+		}
+
+		Dispatch dispatch = Dispatch.borrow(playerCharacter, msg);
+		DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
+
+	}
+
+	//	protected void serializeMerchantMenu(ByteBufferWriter writer) {
+	//		writer.putInt(2);
+	//		writer.putInt(14);
+	//		writer.putInt(0);
+	//		writer.putInt(0);
+	//		writer.put((byte) 0);
+	//		writer.put((byte) 1);
+	//		writer.putString(" [ Merchant options ] ");
+	//		for (int i = 0; i < 4; i++)
+	//			writer.putInt(0);
+	//		writer.putInt(10);
+	//		writer.putInt(0);
+	//		writer.putInt(0);
+	//		writer.put((byte) 0);
+	//		writer.put((byte) 1);
+	//		writer.putString("Done");
+	//		for (int i = 0; i < 8; i++)
+	//			writer.putInt(0);
+	//	}
+
+	//	protected void serializeForTrain(ByteBufferWriter writer, boolean train) {
+	//		writer.putInt(0);
+	//		writer.putInt(0x364AF0D0); // 0x325695C0
+	//		writer.putInt(this.unknown03);
+	//		writer.putInt(1);
+	//		writer.put((byte) 0);
+	//		writer.putInt(this.unknown03);
+	//		writer.put((byte) 0);
+	//		writer.putInt(0);
+	//		writer.putInt(0);
+	//		writer.putInt(2); // 2
+	//		if (train)
+	//			writer.putInt(3); // 3=buy/sell/trade, 4=untrain, 5 closes
+	//		else
+	//			writer.putInt(4); //refine
+	//		writer.putInt(10); // 10
+	//		writer.putInt(0);
+	//		writer.putInt(0);
+	//		writer.putInt(0);
+	//	}
+
+	//	protected void serializeClose(ByteBufferWriter writer) {
+	//		writer.putInt(0);
+	//		writer.putInt(0x364AF0D0); // 0x325695C0
+	//		writer.putInt(this.unknown03);
+	//		writer.putInt(1);
+	//		writer.put((byte) 0);
+	//		writer.putInt(this.unknown03);
+	//		writer.put((byte) 0);
+	//		writer.putInt(0);
+	//		writer.putInt(0);
+	//		writer.putInt(2); // 2
+	//		writer.putInt(5); // 3=buy/sell/trade, 4=untrain, 5 closes
+	//		writer.putInt(10); // 10
+	//		writer.putInt(0);
+	//		writer.putInt(0);
+	//		writer.putInt(0);
+	//	}
+
+	// Handles special case menu selections, such as promote, train, ect.
+	private static boolean handleSpecialCase(VendorDialogMsg msg, NPC npc, PlayerCharacter playerCharacter, VendorDialog vd, ClientConnection origin)
+			throws MsgSendException {
+
+		Dispatch dispatch;
+		int menuID = msg.unknown03; // aka menuoptions.optionID
+		Vector3fImmutable loc;
+
+		switch (menuID) {
+		case 0: // Close Dialog
+			msg.updateMessage(4, 0);
+			dispatch = Dispatch.borrow(playerCharacter, msg);
+			DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
+			return true;
+		case 180: // Promote to class
+			VendorDialogMsg.promote(playerCharacter, npc);
+			msg.updateMessage(4, 0); // <-0 closes dialog
+			dispatch = Dispatch.borrow(playerCharacter, msg);
+			DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
+			return true;
+		case 1000: // TrainSkill skills and powers
+			msg.updateMessage(4, 2); // <-2 sends trainer screen
+			dispatch = Dispatch.borrow(playerCharacter, msg);
+			DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
+			return true;
+		case 1001: // Open Bank
+			getBank(playerCharacter, npc, origin);
+			msg.updateMessage(4, 0);
+			dispatch = Dispatch.borrow(playerCharacter, msg);
+			DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
+			return true;
+		case 1002: // Open Vault
+			getVault(playerCharacter, npc, origin);
+			msg.updateMessage(4, 0);
+			dispatch = Dispatch.borrow(playerCharacter, msg);
+			DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
+			return true;
+		case 1003: //Refine
+			msg.updateMessage(4, 3); // <-3 sends refine screen
+			dispatch = Dispatch.borrow(playerCharacter, msg);
+			DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
+			return true;
+
+			// Mainland Teleports
+		case 100011: // teleport me to Aeldreth
+			City city = City.getCity(25);
+			if (city != null)
+				handleTeleport(playerCharacter, city.getLoc(), 10, 75, false);
+			msg.updateMessage(4, 0);
+			dispatch = Dispatch.borrow(playerCharacter, msg);
+			DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
+			return true;
+		case 100012: // teleport me to SDR
+			city = City.getCity(24);
+			if (city != null)
+				handleTeleport(playerCharacter, city.getLoc(), 10, 75, true);
+			msg.updateMessage(4, 0);
+			dispatch = Dispatch.borrow(playerCharacter, msg);
+			DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
+			return true;
+		case 100013: // teleport me to Erkeng Hold
+			city = City.getCity(26);
+			if (city != null)
+				handleTeleport(playerCharacter, city.getLoc(), 10, 75, true);
+			msg.updateMessage(4, 0);
+			dispatch = Dispatch.borrow(playerCharacter, msg);
+			DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
+			return true;
+		case 100014: // teleport me to Khan
+			city = City.getCity(36);
+			if (city != null)
+				handleTeleport(playerCharacter, city.getLoc(), 1, 75, true);
+			msg.updateMessage(4, 0);
+			dispatch = Dispatch.borrow(playerCharacter, msg);
+			DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
+			return true;
+
+		case 100015: // teleport me to Maelstrom
+			loc = new Vector3fImmutable(105100f, 40f, -25650f);
+			handleTeleport(playerCharacter, loc, 10, 75, false);
+			msg.updateMessage(4, 0);
+			dispatch = Dispatch.borrow(playerCharacter, msg);
+			DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
+			return true;
+		case 100016: // teleport me to Oblivion
+			loc = new Vector3fImmutable(108921f, 167f, -51590f);
+			handleTeleport(playerCharacter, loc, 10, 75, false);
+			msg.updateMessage(4, 0);
+			dispatch = Dispatch.borrow(playerCharacter, msg);
+			DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
+			return true;
+		case 100017: // teleport me to Vander's Doom
+			loc = new Vector3fImmutable(42033f, 46f, -54471f);
+			handleTeleport(playerCharacter, loc, 10, 75, false);
+			msg.updateMessage(4, 0);
+			dispatch = Dispatch.borrow(playerCharacter, msg);
+			DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
+			return true;
+		case 100018: // teleport me to The Sinking Isle
+			loc = new Vector3fImmutable(67177f, 36f, -31940f);
+			handleTeleport(playerCharacter, loc, 10, 75, false);
+			msg.updateMessage(4, 0);
+			dispatch = Dispatch.borrow(playerCharacter, msg);
+			DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
+			return true;
+
+			// MainLand Repledges
+		case 100030: // repledge me to Aeldreth
+			city = City.getCity(25);
+			if (city != null)
+				handleRepledge(playerCharacter, city, 10, 55, false);
+			msg.updateMessage(4, 0);
+			dispatch = Dispatch.borrow(playerCharacter, msg);
+			DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
+			return true;
+		case 100031: // repledge me to Sea Dog's Rest
+			city = City.getCity(24);
+			if (city != null)
+				handleRepledge(playerCharacter, city, 10, 75, true);
+			msg.updateMessage(4, 0);
+			dispatch = Dispatch.borrow(playerCharacter, msg);
+			DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
+			return true;
+		case 100032: // repledge me to Erkeng Hold
+			city = City.getCity(26);
+			if (city != null)
+				handleRepledge(playerCharacter, city, 10, 55, false);
+			msg.updateMessage(4, 0);
+			dispatch = Dispatch.borrow(playerCharacter, msg);
+			DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
+			return true;
+		case 100033: // repledge me to Khan\'Of Srekel
+			city = City.getCity(36);
+			if (city != null)
+				handleRepledge(playerCharacter, city, 1, 75, true);
+			msg.updateMessage(4, 0);
+			dispatch = Dispatch.borrow(playerCharacter, msg);
+			DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
+			return true;
+		case 100035: // repledge me to Starkholm
+			city = City.getCity(27);
+			if (city != null)
+				handleRepledge(playerCharacter, city, 1, 20, false);
+			msg.updateMessage(4, 0);
+			dispatch = Dispatch.borrow(playerCharacter, msg);
+			DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
+			return true;
+
+			// Noob Isle Teleports
+		case 100040: // teleport me to Starkholm
+			city = City.getCity(27);
+			if (city != null)
+				handleTeleport(playerCharacter, city.getLoc(), 1, 20, true);
+			msg.updateMessage(4, 0);
+			dispatch = Dispatch.borrow(playerCharacter, msg);
+			DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
+			return true;
+		case 100041: // teleport me to All-Father's Rest
+			city = City.getCity(28);
+			if (city != null)
+				handleTeleport(playerCharacter, city.getLoc(), 1, 20, true);
+			msg.updateMessage(4, 0);
+			dispatch = Dispatch.borrow(playerCharacter, msg);
+			DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
+			return true;
+		case 100042: // teleport me to Hengest
+			city = City.getCity(33);
+			if (city != null)
+				handleTeleport(playerCharacter, city.getLoc(), 1, 20, true);
+			msg.updateMessage(4, 0);
+			dispatch = Dispatch.borrow(playerCharacter, msg);
+			DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
+			return true;
+		case 100043: // teleport me to Hrimdal
+			city = City.getCity(30);
+			if (city != null)
+				handleTeleport(playerCharacter, city.getLoc(), 1, 20, true);
+			msg.updateMessage(4, 0);
+			dispatch = Dispatch.borrow(playerCharacter, msg);
+			DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
+			return true;
+		case 100044: // teleport me to Hothor's Doom
+			city = City.getCity(29);
+			if (city != null)
+				handleTeleport(playerCharacter, city.getLoc(), 1, 20, true);
+			dispatch = Dispatch.borrow(playerCharacter, msg);
+			DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
+			return true;
+		case 100045: // teleport me to Scraefahl
+			city = City.getCity(32);
+			if (city != null)
+				handleTeleport(playerCharacter, city.getLoc(), 1, 20, false);
+			msg.updateMessage(4, 0);
+			dispatch = Dispatch.borrow(playerCharacter, msg);
+			DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
+			return true;
+		case 100046: // teleport me to Valkirch
+			city = City.getCity(31);
+			if (city != null)
+				handleTeleport(playerCharacter, city.getLoc(), 1, 20, true);
+			msg.updateMessage(4, 0);
+			dispatch = Dispatch.borrow(playerCharacter, msg);
+			DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
+			return true;
+
+
+
+		default:
+		}
+		return false;
+	}
+
+	private static boolean finishMessage(VendorDialogMsg msg, ClientConnection origin) throws MsgSendException {
+		msg.updateMessage(4, 0);
+		Dispatch dispatch = Dispatch.borrow(origin.getPlayerCharacter(), msg);
+		DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
+		return true;
+	}
+
+	private static void handleTeleport(PlayerCharacter pc, Vector3fImmutable loc, int minLevel, int maxLevel, boolean useSquare) {
+		if (pc == null)
+			return;
+
+		int level = pc.getLevel();
+		if (level >= minLevel && level <= maxLevel) {
+			if (useSquare)
+				loc = getSquare(loc);
+			pc.teleport(loc);
+			pc.setSafeMode();
+			// PowersManager.applyPower(pc, pc, new Vector3f(0f,
+			// 0f, 0f), -1661758934, 40, false);
+		}
+		else {
+			if (level < minLevel)
+				ErrorPopupMsg.sendErrorPopup(pc, 74);
+			else
+				ErrorPopupMsg.sendErrorPopup(pc, 139);
+
+		}
+	}
+
+	private static void handleRepledge(PlayerCharacter pc, City city, int minLevel, int maxLevel, boolean useSquare) {
+		if (pc == null || city == null)
+			return;
+
+		Vector3fImmutable loc = city.getLoc();
+		int level = pc.getLevel();
+		if (level >= minLevel && level <= maxLevel) {
+			// set guild
+			Guild guild = city.getGuild();
+			if (guild != null) {
+				// teleport player
+				if (useSquare)
+					loc = getSquare(loc);
+				pc.teleport(loc);
+				pc.setSafeMode();
+				// PowersManager.applyPower(pc, pc, new
+				// Vector3f(0f, 0f, 0f), -1661758934, 40, false);
+
+				// join guild
+				GuildManager.joinGuild(pc, guild, GuildHistoryType.JOIN);
+				
+				pc.resetGuildStatuses();
+				
+				if (guild.isNPCGuild())
+					pc.setFullMember(true);
+				
+				if (useSquare)
+					loc = loc.add(30, 0, 0);
+				pc.setBindLoc(loc);
+			}
+			else {
+				// guild not found, just teleport
+				if (useSquare)
+					loc = getSquare(loc);
+				pc.teleport(loc);
+				pc.setSafeMode();
+				// PowersManager.applyPower(pc, pc, new
+				// Vector3f(0f, 0f, 0f), -1661758934, 50, false);
+			}
+		}
+		else {
+
+			if (level < minLevel)
+				ErrorPopupMsg.sendErrorPopup(pc, 74);
+			else
+				ErrorPopupMsg.sendErrorPopup(pc, 139);
+		}
+	}
+
+	// randomly place around a tol using a square (+- 30 units in x and z
+	// direction)
+	public static Vector3fImmutable getSquare(Vector3fImmutable cityLoc) {
+		Vector3fImmutable loc = cityLoc;
+		// get direction
+		int roll = ThreadLocalRandom.current().nextInt(4);
+		if (roll == 0) { // north
+			loc = loc.add((ThreadLocalRandom.current().nextInt(60) - 30), 0, -30);
+		}
+		else if (roll == 1) { // south
+			loc = loc.add((ThreadLocalRandom.current().nextInt(60) - 30), 0, 30);
+		}
+		else if (roll == 2) { // east
+			loc = loc.add(30, 0, (ThreadLocalRandom.current().nextInt(60) - 30));
+		}
+		else { // west
+			loc = loc.add(-30, 0, (ThreadLocalRandom.current().nextInt(60) - 30));
+		}
+
+		// Make sure no one gets stuck in the tree.
+
+		if (loc.distanceSquared2D(cityLoc) < 250) {
+			loc = cityLoc;
+			loc = loc.add(30, 0, 0);
+		}
+
+		return loc;
+	}
+
+	// Handle promotion
+	private static void promote(PlayerCharacter pc, NPC npc) {
+
+		if (npc == null || pc == null)
+			return;
+
+		// test level 10
+		if (pc.getLevel() < 10) {
+			// TODO send client promotion error
+			while (pc.getLevel() > 65)
+				pc.setLevel((short) 65);
+			return;
+		}
+
+		// verify player not already promoted
+		if (pc.getPromotionClass() != null) {
+			// TODO send client promotion error
+			return;
+		}
+
+		// Get promotion class for npc
+		Contract contract = npc.getContract();
+		if (contract == null)
+			return;
+		int promoID = contract.getPromotionClass();
+		if (promoID == 0)
+			return;
+		PromotionClass promo = DbManager.PromotionQueries.GET_PROMOTION_CLASS(promoID);
+		if (promo == null) {
+			// TODO log error here
+			return;
+		}
+
+		// verify race valid for profession
+		Race race = pc.getRace();
+		if (race == null || !promo.isAllowedRune(race.getToken())) {
+			// TODO send client promotion error
+			return;
+		}
+
+		// verify baseclass valid for profession
+		BaseClass bc = pc.getBaseClass();
+		if (bc == null || !promo.isAllowedRune(bc.getToken())) {
+			// TODO send client promotion error
+			return;
+		}
+
+		// verify gender
+		if (promoID == 2511 && pc.isMale()) // Fury
+			return;
+		if (promoID == 2512 && pc.isMale()) // Huntress
+			return;
+		if (promoID == 2517 && !pc.isMale()) // Warlock
+			return;
+
+		// Everything valid. Let's promote
+		pc.setPromotionClass(promo.getObjectUUID());
+
+		//pc.setLevel((short) 65);
+
+
+		promo = pc.getPromotionClass();
+		if (promo == null) {
+			// TODO log error here
+			return;
+		}
+
+		// recalculate all bonuses/formulas/skills/powers
+		pc.recalculate();
+
+		// send the rune application to the clients
+		ApplyRuneMsg arm = new ApplyRuneMsg(pc.getObjectType().ordinal(), pc.getObjectUUID(), promo.getObjectUUID(), promo.getObjectType().ordinal(), promo
+				.getObjectUUID(), true);
+		DispatchMessage.dispatchMsgToInterestArea(pc, arm, DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, true, false);
+
+
+	}
+
+	/**
+	 * Load and send vault to player. This is public only so a DevCmd can hook
+	 * into it.
+	 *
+	 * @param playerCharacter     - Player Character requesting vault
+	 * @param target - NPC vaultkeeper
+	 * @param cc     - Client Connection
+	 */
+	public static void getVault(PlayerCharacter playerCharacter, NPC target, ClientConnection cc) {
+		if (playerCharacter == null || cc == null || target == null)
+			return;
+
+		Account ac = playerCharacter.getAccount();
+		if (ac == null)
+			return;
+
+		CharacterItemManager itemManager = playerCharacter.getCharItemManager();
+		if (itemManager == null)
+			return;
+
+		// TODO uncomment this block after we determine when we
+		// setBankOpen(false)
+		/*
+		 * // cannot have bank and vault open at the same time if
+		 * (itemManager.isBankOpen()) return;
+		 */
+
+		if (itemManager.getTradingWith() != null) {
+			return;
+			// TODO close trade window here - simple once this is moved to WS
+		}
+
+		itemManager.setVaultOpen(true);
+
+		// TODO for public test - remove this afterwards
+		//		DevCmd.fillVault(pc, itemManager);
+
+		// TODO When do we setVaultOpen(false)? I don't think the client sends a
+		// "CloseVault" message.
+
+		OpenVaultMsg openVaultMsg = new OpenVaultMsg(playerCharacter, target);
+		Dispatch dispatch = Dispatch.borrow(playerCharacter, openVaultMsg);
+		DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
+
+		ShowVaultInventoryMsg showVaultInventoryMsg = new ShowVaultInventoryMsg(playerCharacter, ac, target); // 37??
+		dispatch = Dispatch.borrow(playerCharacter, showVaultInventoryMsg);
+		DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
+
+		// All recordings have "open - show - open."
+		// Seems to work fine with just "show - open" as well.
+
+	}
+
+	/**
+	 * Load and send Bank to player. This is public only so a DevCmd can hook
+	 * into it.
+	 *
+	 * @param playerCharacter     - Player Character requesting vault
+	 * @param target - NPC vaultkeeper
+	 * @param cc     - Client Connection
+	 */
+	public static void getBank(PlayerCharacter playerCharacter, NPC target, ClientConnection cc) {
+
+		if (playerCharacter == null)
+			return;
+
+		if (cc == null)
+			return;
+
+		CharacterItemManager itemManager = playerCharacter.getCharItemManager();
+
+		if (itemManager == null)
+			return;
+
+		// TODO uncomment this block after we determine when we
+		// setVaultOpen(false)
+		/*
+		 * // cannot have bank and vault open at the same time if
+		 * (itemManager.isVaultOpen()) return;
+		 */
+
+		if (itemManager.getTradingWith() != null) {
+			return;
+			// TODO close trade window here - simple once this is moved to WS
+		}
+
+		itemManager.setBankOpen(true);
+		// TODO When do we setBankOpen(false)? I don't think the client sends a
+		// "CloseBank" message.
+
+		AckBankWindowOpenedMsg ackBankWindowOpenedMsg = new AckBankWindowOpenedMsg(playerCharacter, 0L, 0L);
+
+		Dispatch dispatch = Dispatch.borrow(playerCharacter, ackBankWindowOpenedMsg);
+		DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
+
+		ReqBankInventoryMsg reqBankInventoryMsg = new ReqBankInventoryMsg(playerCharacter, 0L);
+		dispatch = Dispatch.borrow(playerCharacter, reqBankInventoryMsg);
+		DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
+
+		ShowBankInventoryMsg showBankInventoryMsg = new ShowBankInventoryMsg(playerCharacter, 0L);
+		dispatch = Dispatch.borrow(playerCharacter, showBankInventoryMsg);
+		DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
+
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied ByteBufferWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+
+		writer.putInt(this.messageType);
+		for (int i = 0; i < 3; i++)
+			writer.putInt(0);
+		if (messageType == 1)
+			writer.putString(this.language);
+		else
+			writer.putString("");
+		writer.putInt(this.vendorObjectType);
+		writer.putInt(this.vendorObjectID);
+
+		for (int i = 0; i < 3; i++)
+			writer.putInt(0);
+		writer.put((byte) 0);
+		writer.put((byte) 0);
+		if (this.messageType == 1) {
+			writer.putInt(this.unknown01);
+			writer.putInt(this.unknown02);
+		}
+		else if (this.messageType == 4) {
+			writer.putInt(0);
+			writer.putInt(0x364AF0D0); // 0x325695C0
+			if (this.vd != null)
+				writer.putInt(vd.getObjectUUID());
+			else
+				writer.putInt(this.unknown03);
+			writer.putInt(1);
+			writer.put((byte) 0);
+			writer.putInt(this.unknown03);
+			writer.put((byte) 0);
+			writer.putInt(0);
+			writer.putInt(0);
+			writer.putInt(2); // 2
+			if (menuType == 2)
+				writer.putInt(3);
+			else if (menuType == 3)
+				writer.putInt(4);
+			else
+				writer.putInt(5);
+			//			writer.putInt(3); // 3=buy/sell/trade, 4=untrain, 5 closes
+			writer.putInt(10); // 10
+			writer.putInt(0);
+			writer.putInt(0);
+			writer.putInt(0);
+			return;
+
+		}
+		writer.putInt(15);
+		writer.putInt(5);
+		if (this.vd != null)
+			writer.putInt(vd.getObjectUUID());
+		else
+			writer.putInt(this.unknown03);
+
+		//filename datablock
+		writer.putInt(1);
+		writer.put((byte) 1);
+		writer.putInt(this.unknown03);
+		writer.put((byte) 1);
+		writer.putString(vd.getDialogType());
+		writer.putInt(0);
+		writer.putInt(1);
+		writer.putInt(4);
+		writer.putInt(0);
+		writer.put((byte) 1);
+
+		//vendor dialog datablock
+		writer.putString(vd.getDialogType());
+		writer.putString(vd.getIntro());
+		writer.put((byte) 0);
+
+		//menu options datablock
+		writer.putInt(1);
+		writer.putString(" [ " + vd.getIntro() + " ] ");
+		ArrayList<MenuOption> options = vd.getOptions();
+		writer.putInt((options.size() + 1));
+		for (MenuOption option : options) {
+			if (option.getMessage().equals(" [ Merchant options ] ")) {
+				writer.putInt(16);
+			}
+			else {
+				writer.putInt(14);
+			}
+			writer.put((byte) 0);
+			writer.putInt(0);
+			writer.put((byte) 0);
+			writer.putInt(1);
+			writer.putString(option.getMessage());
+			writer.putInt(option.getOptionID());
+			for (int i = 0; i < 3; i++)
+				writer.putInt(0);
+		}
+		writer.putInt(10);
+		writer.put((byte) 0);
+		writer.putInt(0);
+		writer.put((byte) 0);
+		writer.putInt(1);
+		writer.putString("Done");
+		for (int i = 0; i < 4; i++)
+			writer.putInt(0);
+		//		writer.putInt(1);
+		//		writer.putInt(2);
+		for (int i = 0; i < 4; i++)
+			writer.putInt(0);
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied
+	 * ByteBufferReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		this.messageType = reader.getInt();
+		for (int i = 0; i < 3; i++)
+			reader.getInt();
+		this.language = reader.getString();
+		this.vendorObjectType = reader.getInt();
+		this.vendorObjectID = reader.getInt();
+		for (int i = 0; i < 3; i++)
+			reader.getInt();
+		reader.get();
+		reader.get();
+		this.unknown01 = reader.getInt();
+		this.unknown02 = reader.getInt();
+		this.unknown03 = reader.getInt();
+		reader.getInt();
+		// if (this.messageType == 1) {
+		reader.get();
+		reader.getInt();
+		reader.get();
+		reader.getInt();
+		reader.getShort();
+		// return;
+		// }
+		// reader.get();
+		// this.unknown04 = reader.getInt();
+		// reader.get();
+		// TODO more message to go here
+	}
+
+	public int getMessageType() {
+		return this.messageType;
+	}
+
+	public void setMessageType(int value) {
+		this.messageType = value;
+	}
+
+	public int getVendorObjectType() {
+		return this.vendorObjectType;
+	}
+
+	public int getVendorObjectID() {
+		return this.vendorObjectID;
+	}
+
+	public int getUnknown01() {
+		return this.unknown01;
+	}
+
+	public int getUnknown02() {
+		return this.unknown02;
+	}
+
+	public int getUnknown03() {
+		return this.unknown03;
+	}
+
+	public void setUnknown03(int value) {
+		this.unknown03 = value;
+	}
+
+	public void setLanguage(String value) {
+		this.language = value;
+	}
+
+	public void updateMessage(int messageType, int menuType) {
+		this.messageType = messageType;
+		this.menuType = menuType;
+	}
+
+	public void updateMessage(int messageType, String dialogType, String intro, int menuType) {
+		this.messageType = messageType;
+		this.dialogType = dialogType;
+		this.intro = intro;
+		this.introCode = " [ " + this.intro + " ] ";
+		this.menuType = menuType;
+	}
+
+	public void updateMessage(int messageType, VendorDialog vd) {
+		this.messageType = messageType;
+		this.vd = vd;
+	}
+}
diff --git a/src/engine/net/client/msg/ViewResourcesMessage.java b/src/engine/net/client/msg/ViewResourcesMessage.java
new file mode 100644
index 00000000..074d6077
--- /dev/null
+++ b/src/engine/net/client/msg/ViewResourcesMessage.java
@@ -0,0 +1,198 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+import engine.Enum;
+import engine.gameManager.DbManager;
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.objects.*;
+
+
+public class ViewResourcesMessage extends ClientNetMsg {
+
+	//resource hashes
+	//0001240F
+	//002339C7 (locked)
+	//00263669
+	//00270DC3
+	//002D6DEF
+	//047636B3 (locked)
+	//047B0CC1
+	//04AB3761
+	//1AF5DB3A
+	//47033237
+	//4F8EFB0F
+	//5B57C3E4
+	//86A0AC24
+	//9705591E
+	//98378CB4
+	//98D78D15
+	//A0703E8C (locked)
+	//A0DA3807
+	//A1723A93
+	//A26E59CF
+	//D665C60F
+	//E3D05AE3
+	//ED13904D
+
+	private Guild guild;
+	private Building warehouseBuilding;
+	private Warehouse warehouseObject;
+	private PlayerCharacter player;
+	private City city;
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+
+	public ViewResourcesMessage(PlayerCharacter player) {
+		super(Protocol.VIEWRESOURCES);
+		this.guild = null;
+		this.player = player;
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the ByteBuffer into a message. If a BufferUnderflow occurs (based on reading past the limit) then this constructor Throws that Exception to the caller.
+	 */
+	public ViewResourcesMessage(AbstractConnection origin, ByteBufferReader reader)
+			 {
+		super(Protocol.VIEWRESOURCES, origin, reader);
+	}
+
+	public boolean configure() {
+
+		if (this.warehouseBuilding.getParentZone() == null)
+			return false;
+
+		this.city = (City) DbManager.getObject(Enum.GameObjectType.City, this.warehouseBuilding.getParentZone().getPlayerCityUUID());
+
+		if (this.city == null)
+			return false;
+
+		this.warehouseObject = this.city.getWarehouse();
+
+        return this.warehouseObject != null;
+    }
+
+	/**
+	 * Serializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+
+		writer.putInt(warehouseObject.getResources().size());
+
+		for (ItemBase ib : (warehouseObject.getResources().keySet())){
+
+			writer.putInt(ib.getHashID());
+			writer.putInt((warehouseObject.getResources().get(ib)));
+
+
+			if (warehouseObject.isResourceLocked(ib) == true)
+				writer.put((byte)1);
+			else
+				writer.put((byte)0);
+		}
+
+		writer.putInt(warehouseObject.getResources().size());
+
+		for (ItemBase ib : warehouseObject.getResources().keySet()){
+			writer.putInt(ib.getHashID());
+			writer.putInt(0); //available?
+			writer.putInt(Warehouse.getMaxResources().get(ib.getUUID())); //max?
+		}
+		GuildTag._serializeForDisplay(guild.getGuildTag(),writer);
+
+		// Serialize what tags?  Errant?
+
+		writer.putInt(16);
+		writer.putInt(16);
+		writer.putInt(16);
+		writer.putInt(0);
+		writer.putInt(0);
+
+		if (GuildStatusController.isTaxCollector(player.getGuildStatus())){
+			writer.putInt(1);
+			writer.putString("Deposit");
+			writer.putInt(-1760114543);
+			writer.putInt(1);
+			writer.put((byte)0);
+
+		}else
+
+			if (this.player.getGuild().equals(warehouseBuilding.getGuild()) &&  (GuildStatusController.isInnerCouncil(this.player.getGuildStatus()))){
+				writer.putInt(4);
+				writer.putString("Lock");
+				writer.putInt(2393548);
+				writer.putInt(1); //locked? on/off
+				writer.put((byte)0);
+				writer.putString("Deposit");
+				writer.putInt(-1760114543);
+
+				writer.putInt(1);
+				writer.put((byte)0);
+				writer.putString("Manage Mines");
+				writer.putInt(-820683698);
+				writer.putInt(1);
+				writer.put((byte)0);
+				writer.putString("Withdraw");
+				writer.putInt(-530228289);
+				writer.putInt(1);
+				writer.put((byte)0);
+			}else{
+				writer.putInt(2);
+				writer.putString("Lock");
+				writer.putInt(2393548);
+				writer.putInt(0); //locked? on/off
+				writer.put((byte)0);
+				writer.putString("Deposit");
+				writer.putInt(-1760114543);
+				writer.putInt(1);
+				writer.put((byte)0);
+			}
+
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		reader.getInt();
+		reader.getInt();
+		reader.getInt();
+		reader.getInt();
+		reader.getInt();
+		reader.getInt();
+		reader.getInt();
+		reader.getInt();
+		reader.getInt();
+		reader.getInt();
+		reader.getInt();
+		reader.getInt();
+		reader.getInt();
+		//		this.locX = reader.getFloat();
+		//		this.locY = reader.getFloat();
+		//		this.locZ = reader.getFloat();
+		//		this.name = reader.getString();
+		//		this.unknown01 = reader.getInt();
+	}
+
+	public void setGuild(Guild guild) {
+		this.guild = guild;
+	}
+
+	public void setWarehouseBuilding(Building warehouseBuilding) {
+		this.warehouseBuilding = warehouseBuilding;
+	}
+}
diff --git a/src/engine/net/client/msg/VisualUpdateMessage.java b/src/engine/net/client/msg/VisualUpdateMessage.java
new file mode 100644
index 00000000..348cc338
--- /dev/null
+++ b/src/engine/net/client/msg/VisualUpdateMessage.java
@@ -0,0 +1,91 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+
+import engine.Enum.GameObjectType;
+import engine.exception.SerializationException;
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.objects.AbstractGameObject;
+import engine.objects.Building;
+
+public class VisualUpdateMessage extends ClientNetMsg {
+	private int effectType;
+	private AbstractGameObject ago;
+	private Building building;
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public VisualUpdateMessage(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.VISUALUPDATE, origin, reader);
+	}
+	
+	public VisualUpdateMessage() {
+		super(Protocol.VISUALUPDATE);
+	}
+
+	public VisualUpdateMessage(AbstractGameObject ago, int visualID) {
+		super(Protocol.VISUALUPDATE);
+
+		if (ago == null)
+			return;
+
+		this.effectType = visualID;
+		this.ago = ago;
+		
+	}
+	/**
+	 * Deserializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		
+	}
+
+    public void configure() {
+
+        if (this.ago.getObjectType() == GameObjectType.Building)
+            this.building = (Building)this.ago;
+        else
+            this.building = null;
+
+    }
+	/**
+	 * Serializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) throws SerializationException {
+
+        if (this.building == null) {
+            writer.putInt(4);
+            writer.putInt(0);
+            writer.putInt(this.effectType);
+            writer.putInt(ago.getObjectType().ordinal());
+            writer.putInt(ago.getObjectUUID());
+            writer.putInt(0);
+            return;
+        }
+
+        writer.putShort((short)100);
+        writer.putShort((short)120);
+        writer.putInt(1);
+        writer.putInt(this.building.getObjectType().ordinal());
+        writer.putInt(this.building.getObjectUUID());
+        writer.putInt(this.effectType);
+
+	}
+}
diff --git a/src/engine/net/client/msg/WhoRequestMsg.java b/src/engine/net/client/msg/WhoRequestMsg.java
new file mode 100644
index 00000000..c04dbd2b
--- /dev/null
+++ b/src/engine/net/client/msg/WhoRequestMsg.java
@@ -0,0 +1,106 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+
+public class WhoRequestMsg extends ClientNetMsg {
+
+	private int set;
+	private int filterType;
+	private String filter;
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public WhoRequestMsg() {
+		super(Protocol.WHOREQUEST);
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public WhoRequestMsg(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.WHOREQUEST, origin, reader);
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+		writer.putInt(this.set);
+		writer.putInt(this.filterType);
+		writer.putString(this.filter);
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		this.set = reader.getInt();
+		this.filterType = reader.getInt();
+		this.filter = reader.getString();
+	}
+
+	/**
+	 * @return the set
+	 */
+	public int getSet() {
+		return set;
+	}
+
+	/**
+	 * @return the filterType
+	 */
+	public int getFilterType() {
+		return filterType;
+	}
+
+	/**
+	 * @return the filter
+	 */
+	public String getFilter() {
+		return filter;
+	}
+
+	/**
+	 * @param set
+	 *            the set to set
+	 */
+	public void setSet(int set) {
+		this.set = set;
+	}
+
+	/**
+	 * @param filterType
+	 *            the filterType to set
+	 */
+	public void setFilterType(int filterType) {
+		this.filterType = filterType;
+	}
+
+	/**
+	 * @param filter
+	 *            the filter to set
+	 */
+	public void setFilter(String filter) {
+		this.filter = filter;
+	}
+
+}
diff --git a/src/engine/net/client/msg/WhoResponseMsg.java b/src/engine/net/client/msg/WhoResponseMsg.java
new file mode 100644
index 00000000..85184b77
--- /dev/null
+++ b/src/engine/net/client/msg/WhoResponseMsg.java
@@ -0,0 +1,313 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+import engine.Enum;
+import engine.gameManager.SessionManager;
+import engine.net.*;
+import engine.net.client.ClientConnection;
+import engine.net.client.Protocol;
+import engine.objects.Guild;
+import engine.objects.GuildStatusController;
+import engine.objects.PlayerCharacter;
+import engine.objects.PromotionClass;
+import engine.server.MBServerStatics;
+import org.pmw.tinylog.Logger;
+
+import java.util.ArrayList;
+
+public class WhoResponseMsg extends ClientNetMsg {
+
+	private int unknown01;
+	private static int worldPop;
+	private  ArrayList<PlayerCharacter> members = new ArrayList<>();
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public WhoResponseMsg() {
+		super(Protocol.WHORESPONSE);
+		this.unknown01 = 1;
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public WhoResponseMsg(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.WHORESPONSE, origin, reader);
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+    @Override
+    protected void _serialize(ByteBufferWriter writer) {
+        writer.putInt(unknown01);
+        writer.putInt(worldPop);
+            int size = this.members.size();
+        writer.putInt(size);
+        //PlayerCharacter pc : this.members
+        for (PlayerCharacter pc: this.members) {
+            writer.putInt(pc.getObjectType().ordinal());
+            writer.putInt(pc.getObjectUUID());
+            writer.putString(pc.getFirstName());
+            writer.putString(pc.getLastName());
+            writer.putInt(pc.getRaceToken());
+            writer.putInt(pc.getClassToken());
+            writer.putInt(pc.getLevel());
+            writer.putInt(0); // unknown 0
+            writer.putInt(pc.isMale() ? 1 : 2); //gender?
+            writer.putInt(0); // unknown 0
+            Guild guild = pc.getGuild();
+            if (guild != null) {
+                writer.put((byte) 1); // Send Guild Info
+                writer.put((byte) 1); // SkipPartTwo
+                writer.putString(guild.getName());
+                writer.putInt(guild.getCharter()); // Charter Type
+                writer.putInt(GuildStatusController.getTitle(pc.getGuildStatus()));
+                writer.putString("what"); // City?, Skip if SkipPartTwo = 0x00
+            } else {
+                writer.put((byte) 0); // Don't Send guild info
+                writer.put((byte) 0); // Don't send last string
+            }
+        }
+    }
+
+	/**
+	 * Deserializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		this.unknown01 = reader.getInt();
+		WhoResponseMsg.worldPop = reader.getInt();
+		// TODO implement Deserialization
+	}
+
+	/**
+	 * @return the number of PlayerCharacters
+	 */
+	public int getSize() {
+		return this.members.size();
+	}
+
+	/**
+	 * @return the unknown01
+	 */
+	public int getUnknown01() {
+		return unknown01;
+	}
+
+	/**
+	 * @param unknown01
+	 *            the unknown01 to set
+	 */
+	public void setUnknown01(int unknown01) {
+		this.unknown01 = unknown01;
+	}
+
+	/**
+	 * @return the worldPop
+	 */
+	public static int getWorldPop() {
+		return WhoResponseMsg.worldPop;
+	}
+
+	/**
+	 * @param worldPop
+	 *            the worldPop to set
+	 */
+	public static void setWorldPop(int worldPop) {
+		WhoResponseMsg.worldPop = worldPop;
+	}
+
+	public boolean addMember(PlayerCharacter pc) {
+		if (this.members.size() > 100)
+			return false;
+		this.members.add(pc);
+		return true;
+	}
+
+	public static void HandleResponse(int set, int filterType, String filter, ClientConnection origin) {
+
+		WhoResponseMsg msg = new WhoResponseMsg();
+		WhoResponseMsg.setWorldPop(SessionManager.getAllActivePlayerCharacters().size());
+
+		PlayerCharacter playerCharacter = SessionManager.getPlayerCharacter(origin);
+
+		if (playerCharacter != null) {
+			//check threshold
+			long currentTime = System.currentTimeMillis();
+			long timestamp = playerCharacter.getTimeStamp("whoWindow");
+			long dif = currentTime - timestamp;
+			if (dif < MBServerStatics.WHO_WINDOW_THRESHOLD)
+				return;
+			playerCharacter.setTimeStamp("whoWindow", currentTime);
+		}
+
+		if (playerCharacter == null) {
+		} else if (filterType == 0) { // No Filter, send everyone
+			for (PlayerCharacter player : SessionManager.getAllActivePlayerCharacters())
+				if (player != null)
+					if (player.isActive())
+						if (!isAdmin(player))
+							if (!HandleSet(set, player, playerCharacter, msg))
+								break;
+		}
+
+		else if (filterType == 1) { // Race Filter
+			for (PlayerCharacter player : SessionManager.getAllActivePlayerCharacters())
+				if (player != null)
+					if (!isAdmin(player))
+						if (player.isActive()) {
+							String[] race = player.getRace().getName().split(",");
+							if (filter.compareTo(race[0]) == 0)
+								if (!HandleSet(set, player, playerCharacter, msg))
+									break;
+						}
+		}
+
+		else if (filterType == 2) { // Class Filter
+			for (PlayerCharacter player : SessionManager.getAllActivePlayerCharacters())
+				if (player != null)
+					if (!isAdmin(player))
+						if (player.isActive()) {
+							if (filter.compareTo(player.getBaseClass().getName()) ==0 || (player.getPromotionClass() != null && filter.compareTo(player.getPromotionClass().getName()) == 0))
+								if (!HandleSet(set, player, playerCharacter, msg))
+									break;
+							
+							
+							// TODO Promotion Class needs added to
+							// PlayerCharacter
+							// else if
+							// (filter.compareTo(pc.getPromotionClass().getName())
+							// == 0)
+							// if (!HandleSet(set, pc, ori, msg))
+							// break;
+						}
+		}
+
+		else if (filterType == 3) { // Level Filter
+			String range[] = filter.split(" ");
+			int low;
+			int high;
+
+			try {
+				low = Integer.parseInt(range[0]);
+			} catch (NumberFormatException e) {
+				low = 1;
+				Logger.error("WhoResponseMsg: Low value in filter is not a proper integer. Defaulting to 1. Error = "
+						+ e.getMessage());
+			}
+
+			try {
+				high = Integer.parseInt(range[1]);
+			} catch (NumberFormatException e) {
+				high = 1;
+				Logger.error(
+						"WhoResponseMsg: High value in filter is not a proper integer. Defaulting to 75. Error = " + e.getMessage());
+			}
+
+			for (PlayerCharacter player : SessionManager.getAllActivePlayerCharacters())
+				if (player != null)
+					if (!isAdmin(player))
+						if (player.isActive())
+							if (player.getLevel() >= low && player.getLevel() <= high)
+								if (!HandleSet(set, player, playerCharacter, msg))
+									break;
+		}
+
+		else if (filterType == 4) { // Name Filter
+			filter = filter.toLowerCase();
+			for (PlayerCharacter player : SessionManager.getAllActivePlayerCharacters())
+				if (player != null)
+					if (!isAdmin(player))
+						if (player.isActive())
+							if (player.getName().toLowerCase().indexOf(filter) > -1)
+								if (!HandleSet(set, player, playerCharacter, msg))
+									break;
+		}
+
+		else if (filterType == 6) { // Status Filter
+			int type = Integer.parseInt(filter);
+			for (PlayerCharacter player : SessionManager.getAllActivePlayerCharacters())
+				if (player != null)
+					if (!isAdmin(player))
+						if (player.isActive()) {
+							if (type == 1) {
+								if (player.isLFGroup())
+									if (!HandleSet(set, player, playerCharacter, msg))
+										break;
+							} else if (type == 2) {
+								if (player.isLFGuild())
+									if (!HandleSet(set, player, playerCharacter, msg))
+										break;
+							} else if (type == 3) {
+								if (player.isRecruiting())
+									if (!HandleSet(set, player, playerCharacter, msg))
+										break;
+							} else
+								break;
+						}
+		}
+
+        Dispatch dispatch = Dispatch.borrow(origin.getPlayerCharacter(), msg);
+        DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+
+	}
+
+	private static boolean HandleSet(int set, PlayerCharacter a, PlayerCharacter b, WhoResponseMsg msg) {
+		// This function handles the sets. 0 = search all, 1 = search nation,
+		// 2 = search guild. Returns true until 100 (max) players found
+		
+		if (set == 0) { // All
+            return msg.addMember(a);
+		} else if (set == 1) { // Nation
+			if (compareNation(a, b))
+                return msg.addMember(a);
+		} else if (set == 2) { // Guild
+			if (compareGuild(a, b))
+                return msg.addMember(a);
+		}
+		return true;
+	}
+
+	private static boolean isAdmin(PlayerCharacter pc) {
+		PromotionClass promo = pc.getPromotionClass();
+		if (promo == null)
+			return false;
+        return promo.getObjectUUID() <= 2503 || promo.getObjectUUID() >= 2527;
+    }
+
+	private static boolean compareGuild(PlayerCharacter a, PlayerCharacter b) {
+		if (a == null || b == null)
+			return false;
+        return Guild.sameGuild(a.getGuild(), b.getGuild());
+    }
+
+	private static boolean compareNation(PlayerCharacter a, PlayerCharacter b) {
+		if (a == null || b == null)
+			return false;
+		Guild aG = a.getGuild();
+		Guild bG = b.getGuild();
+		if (aG == null || bG == null)
+			return false;
+        return (aG.getNation() == bG.getNation()) && aG.getNation() != null;
+    }
+
+
+	@Override
+	protected int getPowerOfTwoBufferSize() {
+		return 14;
+	}
+
+}
diff --git a/src/engine/net/client/msg/WorldDataMsg.java b/src/engine/net/client/msg/WorldDataMsg.java
new file mode 100644
index 00000000..119163c5
--- /dev/null
+++ b/src/engine/net/client/msg/WorldDataMsg.java
@@ -0,0 +1,134 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+
+import engine.exception.SerializationException;
+import engine.gameManager.ConfigManager;
+import engine.gameManager.ZoneManager;
+import engine.net.AbstractConnection;
+import engine.net.AbstractNetMsg;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.objects.Zone;
+import engine.server.MBServerStatics;
+import org.pmw.tinylog.Logger;
+
+public class WorldDataMsg extends ClientNetMsg {
+
+	public static final long wdComp = 0xFF00FF0000000001L;
+	private static byte ver;
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public WorldDataMsg() {
+		super(Protocol.NEWWORLD);
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public WorldDataMsg(AbstractConnection origin, ByteBufferReader reader)
+			 {
+		super(Protocol.NEWWORLD, origin, reader);
+	}
+
+	/**
+	 * @see AbstractNetMsg#getPowerOfTwoBufferSize()
+	 */
+	@Override
+	protected int getPowerOfTwoBufferSize() {
+		// Larger size for historically larger opcodes
+		return (18); // 2^17 == 131,072
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer)
+			throws SerializationException {
+
+
+		// TODO replace this return with SerializationException
+
+		Zone root = ZoneManager.getSeaFloor();
+		if (root == null){
+			Logger.error("Failed to find Sea Floor!");
+			return;
+		}
+
+		writer.putString(ConfigManager.MB_WORLD_NAME.getValue());
+		writer.putInt(512);
+		writer.putInt(384);
+
+		writer.putInt(MBServerStatics.worldMapID);
+		writer.putInt(0x00000000);
+
+		writer.putInt(getTotalMapSize(root) + 1);
+		Zone.serializeForClientMsg(root,writer);
+
+		Zone hotzone = ZoneManager.getHotZone();
+
+		if (hotzone == null)
+			writer.putLong(0L);
+		else {
+			writer.putInt(hotzone.getObjectType().ordinal());
+			writer.putInt(hotzone.getObjectUUID());
+		}
+
+
+		
+
+		writer.putFloat(0);
+		writer.putFloat(1);
+		writer.putFloat(0);
+		writer.putFloat(0.69999999f);
+		writer.putFloat(.5f);
+		writer.putFloat(1);
+		writer.putFloat(.5f);
+		writer.putFloat(0.69999999f);
+		writer.putFloat(1);
+		writer.putFloat(0);
+		writer.putFloat(0);
+		writer.putFloat(0.69999999f);
+		writer.putFloat(1);
+		writer.putFloat(.5f);
+		writer.putFloat(.5f);
+		writer.putFloat(0.69999999f);
+		writer.putFloat(1);
+		
+
+
+	}
+
+
+	@Override
+	protected void _deserialize(ByteBufferReader reader) {
+
+	}
+
+	private static int getTotalMapSize(Zone root) {
+		if (root.getNodes().isEmpty())
+			return 0;
+
+		int size = root.getNodes().size();
+		for (Zone child : root.getNodes())
+			size += getTotalMapSize(child);
+		return size;
+	}
+
+
+}
diff --git a/src/engine/net/client/msg/WorldObjectMsg.java b/src/engine/net/client/msg/WorldObjectMsg.java
new file mode 100644
index 00000000..403563e5
--- /dev/null
+++ b/src/engine/net/client/msg/WorldObjectMsg.java
@@ -0,0 +1,282 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+import engine.Enum;
+import engine.Enum.RunegateType;
+import engine.gameManager.DbManager;
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.Network;
+import engine.net.client.Protocol;
+import engine.objects.AbstractGameObject;
+import engine.objects.City;
+import engine.objects.Mine;
+import engine.objects.Runegate;
+import engine.session.Session;
+import org.pmw.tinylog.Logger;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class WorldObjectMsg extends ClientNetMsg {
+
+	private Session s;
+	private boolean forEnterWorld;
+	private static ByteBuffer cachedEnterWorld;
+	private static long cachedExpireTime;
+
+	public static final long wdComp = 0xFF00FF0000000003L;
+	private static byte ver = 1;
+
+	private boolean updateCities = false;
+	private boolean updateRunegates = false;
+	private boolean updateMines = false;
+
+	/**
+	 * This is the general purpose constructor.
+	 *
+	 * @param s
+	 *            Session
+	 * @param forEnterWorld
+	 *            boolean flag
+	 */
+	public WorldObjectMsg(Session s, boolean forEnterWorld) {
+		super(Protocol.CITYDATA);
+		this.s = s;
+		this.forEnterWorld = forEnterWorld;
+	}
+
+	public WorldObjectMsg(boolean updateCities, boolean updateRunegates, boolean updateMines) {
+		super(Protocol.CITYDATA);
+		this.s = null;
+		this.forEnterWorld = false;
+		this.updateCities = updateCities;
+		this.updateRunegates = updateRunegates;
+		this.updateMines = updateMines;
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public WorldObjectMsg(AbstractConnection origin, ByteBufferReader reader)
+			 {
+		super(Protocol.CITYDATA, origin, reader);
+		this.forEnterWorld = false;
+	}
+
+	@Override
+	protected int getPowerOfTwoBufferSize() {
+		return (18); // 2^14 == 16384
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+		if (this.forEnterWorld)
+			serializeForEnterWorld(writer);
+		else
+			serializeForMapUpdate(writer);
+	}
+
+	/**
+	 * Specific use serializer
+	 *
+	 * @param writer
+	 */
+	private void serializeForMapUpdate(ByteBufferWriter writer) {
+
+		//Handle City updates
+
+		if (this.updateCities) {
+			writer.put((byte) 0);
+			ArrayList<City> cityList = new ArrayList<>();
+			ConcurrentHashMap<Integer, AbstractGameObject> map = DbManager.getMap(Enum.GameObjectType.City);
+			if (map != null) {
+				for (AbstractGameObject ago : map.values())
+					if (ago.getObjectType().equals(Enum.GameObjectType.City))
+						cityList.add((City)ago);
+				
+				writer.putInt(cityList.size());
+				for (City city: cityList){
+					City.serializeForClientMsg(city, writer);
+				}
+				
+			} else {
+				Logger.error("missing city map");
+				writer.putInt(0);
+			}
+		} else
+			writer.put((byte) 1);
+
+
+		//Handle Runegate updates
+		if (this.updateRunegates) {
+
+			writer.put((byte) 0);
+			writer.putInt(RunegateType.values().length);
+
+			for(RunegateType gateType : engine.Enum.RunegateType.values()) {
+
+				Runegate.getRunegates()[gateType.ordinal()]._serializeForEnterWorld(writer);
+			}
+		} else
+			writer.put((byte) 1);
+
+
+		//Handle Mine updates
+		try{
+			if (this.updateMines) {
+				ArrayList<Mine> mineList = new ArrayList<>();
+				for (Mine toAdd: Mine.mineMap.keySet()){
+					mineList.add(toAdd);
+				}
+				
+				writer.putInt(mineList.size());
+				for (Mine mine: mineList)
+					Mine.serializeForClientMsg(mine, writer);
+			} else
+				writer.putInt(0);
+		}catch(Exception e){
+			Logger.error(e);
+		}
+		
+
+
+		writer.put((byte) 0); // PAD
+	}
+
+	/**
+	 * Specific use serializer
+	 *
+	 * @param writer
+	 */
+	private void serializeForEnterWorld(ByteBufferWriter writer) {
+		if (s == null || s.getPlayerCharacter() == null)
+			return;
+
+		long startT = System.currentTimeMillis();
+
+		if (cachedEnterWorld == null) {
+			// Never before been cached, so init stuff
+			cachedEnterWorld = Network.byteBufferPool.getBuffer(19);
+			cachedExpireTime = 0L;
+		}
+
+		//Check to see if its time to renew cache.
+		if (cachedExpireTime < System.currentTimeMillis()) {
+			synchronized (cachedEnterWorld) {
+				WorldObjectMsg.attemptSerializeForEnterWorld(cachedEnterWorld);
+			}
+			cachedExpireTime = startT + 60000;
+		}
+
+		writer.putBB(cachedEnterWorld);
+
+	}
+
+	private static void attemptSerializeForEnterWorld(ByteBuffer bb) {
+		bb.clear();
+		ByteBufferWriter temp = new ByteBufferWriter(bb);
+		temp.put((byte) 0); // PAD
+
+
+		ArrayList<City> cityList = new ArrayList<>();
+		ConcurrentHashMap<Integer, AbstractGameObject> map = DbManager.getMap(Enum.GameObjectType.City);
+		for (AbstractGameObject ago : map.values())
+
+			if (ago.getObjectType().equals(Enum.GameObjectType.City))
+				cityList.add((City)ago);
+
+		temp.putInt(cityList.size());
+		
+		for (City city: cityList)
+			City.serializeForClientMsg(city, temp);
+		temp.put((byte) 0); // PAD
+
+		// Serialize runegates
+
+		temp.putInt(RunegateType.values().length);
+
+		for(RunegateType gateType : engine.Enum.RunegateType.values()) {
+
+			Runegate.getRunegates()[gateType.ordinal()]._serializeForEnterWorld(temp);
+		}
+
+		ArrayList<Mine> mineList = new ArrayList<>();
+		for (Mine toAdd : Mine.mineMap.keySet()){
+			mineList.add(toAdd);
+		}
+		
+		temp.putInt(mineList.size());
+		for (Mine mine: mineList)
+			Mine.serializeForClientMsg(mine, temp);
+		temp.put((byte) 0); // PAD
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)
+			 {
+		// Client only sends 11 bytes.
+		
+		byte type = reader.get();
+		
+		if (type == 1){
+			reader.get();
+			reader.get();
+			reader.getInt();
+			
+		}else{
+			reader.get();
+			reader.getInt();
+			reader.get();
+			reader.getInt();
+		}
+		   
+	}
+
+	/**
+	 * @return the s
+	 */
+	public Session getS() {
+		return s;
+	}
+
+	/**
+	 * @return the forEnterWorld
+	 */
+	public boolean isForEnterWorld() {
+		return forEnterWorld;
+	}
+
+	public void updateCities(boolean value) {
+		this.updateCities = value;
+	}
+
+	public void updateRunegates(boolean value) {
+		this.updateRunegates = value;
+	}
+
+	public void updateMines(boolean value) {
+		this.updateMines = value;
+	}
+
+
+}
diff --git a/src/engine/net/client/msg/WorldRealmMsg.java b/src/engine/net/client/msg/WorldRealmMsg.java
new file mode 100644
index 00000000..f2f05b0a
--- /dev/null
+++ b/src/engine/net/client/msg/WorldRealmMsg.java
@@ -0,0 +1,100 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+
+
+
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg;
+
+import engine.Enum.RealmType;
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.objects.Realm;
+
+
+public class WorldRealmMsg extends ClientNetMsg {
+
+	
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public WorldRealmMsg() {
+		super(Protocol.REALMDATA);
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public WorldRealmMsg(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.REALMDATA, origin, reader);
+	}
+
+	@Override
+	protected int getPowerOfTwoBufferSize() {
+		return (14); // 2^14 == 16384
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+
+		int realmCount;
+		int realmID;
+		Realm serverRealm;
+
+
+		realmCount = RealmType.values().length - 1;
+		// Realm count without seafloor
+
+		writer.putInt(realmCount);
+
+		for (RealmType realmType : RealmType.values()) {
+
+			realmID = realmType.getRealmID();
+			// Don't serialize seafloor
+
+			if (realmID == 0)
+				continue;
+
+			serverRealm = Realm.getRealm(realmID);
+			serverRealm.serializeForClientMsg(writer);
+
+		}
+
+		writer.putInt(0x0);
+			writer.putInt(3000000);
+
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		// TODO Implement Deserialization
+	}
+}
diff --git a/src/engine/net/client/msg/chat/AbstractChatMsg.java b/src/engine/net/client/msg/chat/AbstractChatMsg.java
new file mode 100644
index 00000000..714f71b1
--- /dev/null
+++ b/src/engine/net/client/msg/chat/AbstractChatMsg.java
@@ -0,0 +1,190 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg.chat;
+
+
+import engine.exception.SerializationException;
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.net.client.msg.ClientNetMsg;
+import engine.objects.AbstractWorldObject;
+
+public abstract class AbstractChatMsg extends ClientNetMsg {
+
+	protected int sourceType;
+	protected int sourceID;
+	protected String sourceName;
+	protected AbstractWorldObject source;
+
+	protected int unknown01;
+	protected String message;
+	protected int unknown02;
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	protected AbstractChatMsg(Protocol protocolMsg, AbstractWorldObject source, String message) {
+		super(protocolMsg);
+
+		this.unknown01 = 0;
+		this.message = message;
+		this.source = source;
+		this.unknown02 = 0;
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	protected AbstractChatMsg(Protocol protocolMsg, AbstractConnection origin, ByteBufferReader reader) {
+		super(protocolMsg, origin, reader);
+
+		//THIS may cause initialization error, but messing up guild chat
+		//this.unknown01 = 0;
+		//this.unknown02 = 0;
+	}
+
+	/**
+	 * Copy constructor
+	 */
+	protected AbstractChatMsg(AbstractChatMsg msg) {
+		super(msg.getProtocolMsg());
+        this.sourceType = msg.sourceType;
+        this.sourceID = msg.sourceID;
+        this.sourceName = msg.sourceName;
+        this.source = msg.source;
+        this.unknown01 = msg.unknown01;
+		this.message = msg.getMessage();
+		this.unknown02 = msg.getUnknown02();
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied ByteBufferReader.
+	 */
+	@Override
+	protected abstract void _deserialize(ByteBufferReader reader) ;
+
+	/**
+	 * Serializes the subclass specific items to the supplied ByteBufferWriter.
+	 */
+	@Override
+	protected abstract void _serialize(ByteBufferWriter writer) throws SerializationException;
+
+	/**
+	 * @return the unknown01
+	 */
+	public int getUnknown01() {
+		return unknown01;
+	}
+
+	/**
+	 * @return the message
+	 */
+	public String getMessage() {
+		return message;
+	}
+
+	/**
+	 * @return the unknown02
+	 */
+	public int getUnknown02() {
+		return unknown02;
+	}
+
+	/**
+	 * @param unknown01
+	 *            the unknown01 to set
+	 */
+	public void setUnknown01(int unknown01) {
+		this.unknown01 = unknown01;
+	}
+
+	/**
+	 * @param message
+	 *            the message to set
+	 */
+	public void setMessage(String message) {
+		this.message = message;
+	}
+
+	/**
+	 * @param unknown02
+	 *            the unknown02 to set
+	 */
+	public void setUnknown02(int unknown02) {
+		this.unknown02 = unknown02;
+	}
+
+	/**
+	 * @return the sourceObjName
+	 */
+	public AbstractWorldObject getSource() {
+		return source;
+	}
+
+	/**
+	 * @param sourceObjName
+	 *            the sourceObjName to set
+	 */
+	public void setSource(AbstractWorldObject source) {
+		this.source = source;
+	}
+
+	/**
+	 * @return the sourceType
+	 */
+	public int getSourceType() {
+		return sourceType;
+	}
+
+	/**
+	 * @return the sourceID
+	 */
+	public int getSourceID() {
+		return sourceID;
+	}
+
+	
+	/**
+	 * @return the sourceName
+	 */
+	public String getSourceName() {
+		return sourceName;
+	}
+
+	/**
+	 * @param sourceType
+	 *            the sourceType to set
+	 */
+	public void setSourceType(int sourceType) {
+		this.sourceType = sourceType;
+	}
+
+	/**
+	 * @param sourceID
+	 *            the sourceID to set
+	 */
+	public void setSourceID(int sourceID) {
+		this.sourceID = sourceID;
+	}
+
+	/**
+	 * @param sourceName
+	 *            the sourceName to set
+	 */
+	public void setSourceName(String sourceName) {
+		this.sourceName = sourceName;
+	}
+
+}
diff --git a/src/engine/net/client/msg/chat/ChatCSRMsg.java b/src/engine/net/client/msg/chat/ChatCSRMsg.java
new file mode 100644
index 00000000..f70450a7
--- /dev/null
+++ b/src/engine/net/client/msg/chat/ChatCSRMsg.java
@@ -0,0 +1,78 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg.chat;
+
+
+import engine.gameManager.SessionManager;
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.ClientConnection;
+import engine.net.client.Protocol;
+import engine.objects.AbstractWorldObject;
+import engine.server.MBServerStatics;
+import engine.session.Session;
+
+public class ChatCSRMsg extends AbstractChatMsg {
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public ChatCSRMsg(AbstractWorldObject source, String message) {
+		super(Protocol.CHATCSR, source, message);
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public ChatCSRMsg(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.CHATCSR, origin, reader);
+		Session s = SessionManager.getSession((ClientConnection) origin);
+		if (s == null)
+			return;
+		this.source = s.getPlayerCharacter();
+	}
+
+	/**
+	 * Copy constructor
+	 */
+	public ChatCSRMsg(ChatCSRMsg msg) {
+		super(msg);
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied ByteBufferReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		reader.getInt();
+		reader.getLong();
+		this.message = reader.getString();
+		reader.getInt(); // pad
+		this.unknown02 = reader.getInt();
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied ByteBufferWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+		writer.putInt(this.source.getObjectType().ordinal());
+		writer.putInt(this.source.getObjectUUID());
+		writer.putInt(this.unknown01);
+		writer.putString(this.message);
+		writer.putString(this.source.getName());
+		writer.putInt(MBServerStatics.worldMapID);
+	}
+
+}
diff --git a/src/engine/net/client/msg/chat/ChatCityMsg.java b/src/engine/net/client/msg/chat/ChatCityMsg.java
new file mode 100644
index 00000000..67b64309
--- /dev/null
+++ b/src/engine/net/client/msg/chat/ChatCityMsg.java
@@ -0,0 +1,88 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg.chat;
+
+import engine.Enum.GameObjectType;
+import engine.gameManager.SessionManager;
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.objects.AbstractCharacter;
+import engine.objects.AbstractGameObject;
+import engine.objects.AbstractWorldObject;
+import engine.server.MBServerStatics;
+
+public class ChatCityMsg extends AbstractChatMsg {
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public ChatCityMsg(AbstractWorldObject source, String message) {
+		super(Protocol.CHATCITY, source, message);
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public ChatCityMsg(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.CHATCITY, origin, reader);
+	}
+
+	/**
+	 * Copy constructor
+	 */
+	public ChatCityMsg(ChatCityMsg msg) {
+		super(msg);
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied ByteBufferReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		long sourceID = reader.getLong();
+		int objectUUID = AbstractGameObject.extractUUID(GameObjectType.PlayerCharacter, sourceID);
+		this.source = SessionManager.getPlayerCharacterByID(objectUUID);
+		this.unknown01 = reader.getInt();
+		this.message = reader.getString();
+		this.sourceName = reader.getString();
+		this.unknown02 = reader.getInt();
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied ByteBufferWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+		// TODO Implement Serialize
+		if (this.source != null){
+			writer.putInt(source.getObjectType().ordinal());
+			writer.putInt(source.getObjectUUID());
+		}
+			
+		else
+			writer.putLong(0L);
+		writer.putInt(0);
+		writer.putString(this.message);
+		if (this.source == null) {
+			// TODO log error here
+			writer.putString("");
+			writer.putInt(0);
+		} else {
+			writer.putString(((AbstractCharacter) this.source).getFirstName());
+			writer.putInt(MBServerStatics.worldMapID);
+		}
+	}
+
+}
diff --git a/src/engine/net/client/msg/chat/ChatGlobalMsg.java b/src/engine/net/client/msg/chat/ChatGlobalMsg.java
new file mode 100644
index 00000000..c3561417
--- /dev/null
+++ b/src/engine/net/client/msg/chat/ChatGlobalMsg.java
@@ -0,0 +1,89 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg.chat;
+
+
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.objects.AbstractCharacter;
+import engine.objects.AbstractWorldObject;
+import engine.server.MBServerStatics;
+
+public class ChatGlobalMsg extends AbstractChatMsg {
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	
+	//chat global is using leader channel protocolMsg now.
+	public ChatGlobalMsg(AbstractWorldObject source, String message) {
+		super(Protocol.LEADERCHANNELMESSAGE, source, message);
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public ChatGlobalMsg(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.LEADERCHANNELMESSAGE, origin, reader);
+	}
+
+	/**
+	 * Copy constructor
+	 */
+	public ChatGlobalMsg(ChatGlobalMsg msg) {
+		super(msg);
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied ByteBufferReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		this.sourceType = reader.getInt();
+		this.sourceID = reader.getInt();
+		this.unknown01 = reader.getInt();
+
+		this.message = reader.getString();
+		this.sourceName = reader.getString();
+
+		this.unknown02 = reader.getInt();
+		reader.getInt();
+		reader.getInt();
+		reader.getInt();
+		
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied ByteBufferWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+		writer.putInt(this.sourceType);
+		writer.putInt(this.sourceID);
+		writer.putInt(this.unknown01);
+		writer.putString(this.message);
+		if (this.source == null) {
+			// TODO log error here
+			writer.putString("");
+			writer.putInt(0);
+		} else {
+			writer.putString(((AbstractCharacter) this.source).getFirstName());
+			writer.putInt(MBServerStatics.worldMapID);
+		}
+		writer.putInt(0);
+		writer.putInt(0);
+		writer.putInt(0);
+	}
+}
diff --git a/src/engine/net/client/msg/chat/ChatGroupMsg.java b/src/engine/net/client/msg/chat/ChatGroupMsg.java
new file mode 100644
index 00000000..72fd375f
--- /dev/null
+++ b/src/engine/net/client/msg/chat/ChatGroupMsg.java
@@ -0,0 +1,75 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg.chat;
+
+
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.objects.AbstractWorldObject;
+
+public class ChatGroupMsg extends AbstractChatMsg {
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public ChatGroupMsg(AbstractWorldObject source, String message) {
+		super(Protocol.CHATGROUP, source, message);
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public ChatGroupMsg(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.CHATGROUP, origin, reader);
+	}
+
+	/**
+	 * Copy constructor
+	 */
+	public ChatGroupMsg(ChatGroupMsg msg) {
+		super(msg);
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied ByteBufferReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		this.sourceType = reader.getInt();
+		this.sourceID = reader.getInt();
+		this.unknown01 = reader.getInt();
+
+		this.message = reader.getString();
+		this.sourceName = reader.getString();
+
+		this.unknown02 = reader.getInt();
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied ByteBufferWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+		writer.putInt(this.sourceType);
+		writer.putInt(this.sourceID);
+		writer.putInt(this.unknown01);
+
+		writer.putString(this.message);
+		writer.putString(this.sourceName);
+
+		writer.putInt(this.unknown02);
+	}
+
+}
diff --git a/src/engine/net/client/msg/chat/ChatGuildMsg.java b/src/engine/net/client/msg/chat/ChatGuildMsg.java
new file mode 100644
index 00000000..94b77de6
--- /dev/null
+++ b/src/engine/net/client/msg/chat/ChatGuildMsg.java
@@ -0,0 +1,153 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg.chat;
+
+
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.objects.AbstractWorldObject;
+
+public class ChatGuildMsg extends AbstractChatMsg {
+
+	protected int unknown03;
+	protected int unknown04;
+	protected int unknown05;
+	protected int unknown06;
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public ChatGuildMsg(AbstractWorldObject source, String message) {
+		super(Protocol.CHATGUILD, source, message);
+		this.unknown03 = 0;
+		this.unknown04 = 0;
+		this.unknown05 = 0;
+		this.unknown06 = 0;
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public ChatGuildMsg(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.CHATGUILD, origin, reader);
+	}
+
+	/**
+	 * Copy constructor
+	 */
+	public ChatGuildMsg(ChatGuildMsg msg) {
+		super(msg);
+        this.unknown03 = msg.unknown03;
+        this.unknown04 = msg.unknown04;
+        this.unknown05 = msg.unknown05;
+        this.unknown06 = msg.unknown06;
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied ByteBufferReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		this.sourceType = reader.getInt();
+		this.sourceID = reader.getInt();
+		this.unknown01 = reader.getInt();
+		this.message = reader.getString();
+		this.unknown02 = reader.getInt();
+		this.sourceName = reader.getString();
+		this.unknown03 = reader.getInt();
+		this.unknown04 = reader.getInt();
+		this.unknown05 = reader.getInt();
+		this.unknown06 = reader.getInt();
+	}
+
+
+	/**
+	 * Serializes the subclass specific items to the supplied ByteBufferWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+		writer.putInt(this.sourceType);
+		writer.putInt(this.sourceID);
+		writer.putInt(this.unknown01);
+		writer.putString(this.message);
+		writer.putInt(this.unknown02);
+		writer.putString(this.sourceName);
+		writer.putInt(this.unknown03);
+		writer.putInt(this.unknown04);
+		writer.putInt(this.unknown05);
+		writer.putInt(this.unknown06);
+	}
+
+	/**
+	 * @return the unknown03
+	 */
+	public int getUnknown03() {
+		return unknown03;
+	}
+
+	/**
+	 * @param unknown03
+	 *            the unknown03 to set
+	 */
+	public void setUnknown03(int unknown03) {
+		this.unknown03 = unknown03;
+	}
+
+	/**
+	 * @return the unknown04
+	 */
+	public int getUnknown04() {
+		return unknown04;
+	}
+
+	/**
+	 * @param unknown04
+	 *            the unknown04 to set
+	 */
+	public void setUnknown04(int unknown04) {
+		this.unknown04 = unknown04;
+	}
+
+	/**
+	 * @return the unknown05
+	 */
+	public int getUnknown05() {
+		return unknown05;
+	}
+
+	/**
+	 * @param unknown05
+	 *            the unknown05 to set
+	 */
+	public void setUnknown05(int unknown05) {
+		this.unknown05 = unknown05;
+	}
+
+	/**
+	 * @return the unknown06
+	 */
+	public int getUnknown06() {
+		return unknown06;
+	}
+
+	/**
+	 * @param unknown06
+	 *            the unknown06 to set
+	 */
+	public void setUnknown06(int unknown06) {
+		this.unknown06 = unknown06;
+	}
+
+}
diff --git a/src/engine/net/client/msg/chat/ChatICMsg.java b/src/engine/net/client/msg/chat/ChatICMsg.java
new file mode 100644
index 00000000..cc50d26a
--- /dev/null
+++ b/src/engine/net/client/msg/chat/ChatICMsg.java
@@ -0,0 +1,132 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg.chat;
+
+
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.objects.AbstractWorldObject;
+
+public class ChatICMsg extends AbstractChatMsg {
+
+	protected int unknown03;
+	protected int unknown04;
+	protected int unknown05;
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public ChatICMsg(AbstractWorldObject source, String message) {
+		super(Protocol.CHATIC, source, message);
+		this.unknown03 = 0;
+		this.unknown04 = 0;
+		this.unknown05 = 0;
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public ChatICMsg(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.CHATIC, origin, reader);
+	}
+
+	/**
+	 * Copy constructor
+	 */
+	public ChatICMsg(ChatICMsg msg) {
+		super(msg);
+        this.unknown03 = msg.unknown03;
+        this.unknown04 = msg.unknown04;
+        this.unknown05 = msg.unknown05;
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied ByteBufferReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		this.sourceType = reader.getInt();
+		this.sourceID = reader.getInt();
+		this.unknown01 = reader.getInt();
+		this.message = reader.getString();
+		this.sourceName = reader.getString();
+		this.unknown02 = reader.getInt();
+		this.unknown03 = reader.getInt();
+		this.unknown04 = reader.getInt();
+		this.unknown05 = reader.getInt();
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied ByteBufferWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+		writer.putInt(this.sourceType);
+		writer.putInt(this.sourceID);
+		writer.putInt(this.unknown01);
+		writer.putString(this.message);
+		writer.putString(this.sourceName);
+		writer.putInt(this.unknown02);
+		writer.putInt(this.unknown03);
+		writer.putInt(this.unknown04);
+		writer.putInt(this.unknown05);
+	}
+
+	/**
+	 * @return the unknown03
+	 */
+	public int getUnknown03() {
+		return unknown03;
+	}
+
+	/**
+	 * @param unknown03
+	 *            the unknown03 to set
+	 */
+	public void setUnknown03(int unknown03) {
+		this.unknown03 = unknown03;
+	}
+
+	/**
+	 * @return the unknown04
+	 */
+	public int getUnknown04() {
+		return unknown04;
+	}
+
+	/**
+	 * @param unknown04
+	 *            the unknown04 to set
+	 */
+	public void setUnknown04(int unknown04) {
+		this.unknown04 = unknown04;
+	}
+
+	/**
+	 * @return the unknown05
+	 */
+	public int getUnknown05() {
+		return unknown05;
+	}
+
+	/**
+	 * @param unknown05
+	 *            the unknown05 to set
+	 */
+	public void setUnknown05(int unknown05) {
+		this.unknown05 = unknown05;
+	}
+
+}
\ No newline at end of file
diff --git a/src/engine/net/client/msg/chat/ChatInfoMsg.java b/src/engine/net/client/msg/chat/ChatInfoMsg.java
new file mode 100644
index 00000000..d39b4c85
--- /dev/null
+++ b/src/engine/net/client/msg/chat/ChatInfoMsg.java
@@ -0,0 +1,60 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg.chat;
+
+
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.objects.AbstractWorldObject;
+
+public class ChatInfoMsg extends AbstractChatMsg {
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public ChatInfoMsg(AbstractWorldObject source, String message) {
+		super(Protocol.CHATINFO, source, message);
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public ChatInfoMsg(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.CHATINFO, origin, reader);
+	}
+
+	/**
+	 * Copy constructor
+	 */
+	public ChatInfoMsg(ChatInfoMsg msg) {
+		super(msg);
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied ByteBufferReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied ByteBufferWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+		// TODO Implement Serialization
+	}
+
+}
diff --git a/src/engine/net/client/msg/chat/ChatLeaderMsg.java b/src/engine/net/client/msg/chat/ChatLeaderMsg.java
new file mode 100644
index 00000000..82f57281
--- /dev/null
+++ b/src/engine/net/client/msg/chat/ChatLeaderMsg.java
@@ -0,0 +1,151 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg.chat;
+
+
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.objects.AbstractWorldObject;
+
+public class ChatLeaderMsg extends AbstractChatMsg {
+
+	protected int unknown03;
+	protected int unknown04;
+	protected int unknown05;
+	protected int unknown06;
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public ChatLeaderMsg(AbstractWorldObject source, String message) {
+		super(Protocol.LEADERCHANNELMESSAGE, source, message);
+		this.unknown03 = 0;
+		this.unknown04 = 0;
+		this.unknown05 = 0;
+		this.unknown06 = 0;
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public ChatLeaderMsg(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.LEADERCHANNELMESSAGE, origin, reader);
+	}
+
+	/**
+	 * Copy constructor
+	 */
+	public ChatLeaderMsg(ChatLeaderMsg msg) {
+		super(msg);
+        this.unknown03 = msg.unknown03;
+        this.unknown04 = msg.unknown04;
+        this.unknown05 = msg.unknown05;
+        this.unknown06 = msg.unknown06;
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied ByteBufferReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		this.sourceType = reader.getInt();
+		this.sourceID = reader.getInt();
+		this.unknown01 = reader.getInt();
+		this.message = reader.getString();
+		this.sourceName = reader.getString();
+		this.unknown03 = reader.getInt();
+		this.unknown04 = reader.getInt();
+		this.unknown05 = reader.getInt();
+		this.unknown06 = reader.getInt();
+	}
+
+
+	/**
+	 * Serializes the subclass specific items to the supplied ByteBufferWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+		writer.putInt(this.sourceType);
+		writer.putInt(this.sourceID);
+		writer.putInt(this.unknown01);
+		writer.putString(this.message);
+		writer.putString(this.sourceName);
+		writer.putInt(this.unknown03);
+		writer.putInt(this.unknown04);
+		writer.putInt(this.unknown05);
+		writer.putInt(this.unknown06);
+	}
+
+	/**
+	 * @return the unknown03
+	 */
+	public int getUnknown03() {
+		return unknown03;
+	}
+
+	/**
+	 * @param unknown03
+	 *            the unknown03 to set
+	 */
+	public void setUnknown03(int unknown03) {
+		this.unknown03 = unknown03;
+	}
+
+	/**
+	 * @return the unknown04
+	 */
+	public int getUnknown04() {
+		return unknown04;
+	}
+
+	/**
+	 * @param unknown04
+	 *            the unknown04 to set
+	 */
+	public void setUnknown04(int unknown04) {
+		this.unknown04 = unknown04;
+	}
+
+	/**
+	 * @return the unknown05
+	 */
+	public int getUnknown05() {
+		return unknown05;
+	}
+
+	/**
+	 * @param unknown05
+	 *            the unknown05 to set
+	 */
+	public void setUnknown05(int unknown05) {
+		this.unknown05 = unknown05;
+	}
+
+	/**
+	 * @return the unknown06
+	 */
+	public int getUnknown06() {
+		return unknown06;
+	}
+
+	/**
+	 * @param unknown06
+	 *            the unknown06 to set
+	 */
+	public void setUnknown06(int unknown06) {
+		this.unknown06 = unknown06;
+	}
+
+}
diff --git a/src/engine/net/client/msg/chat/ChatPvPMsg.java b/src/engine/net/client/msg/chat/ChatPvPMsg.java
new file mode 100644
index 00000000..dd93f139
--- /dev/null
+++ b/src/engine/net/client/msg/chat/ChatPvPMsg.java
@@ -0,0 +1,87 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg.chat;
+
+
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.objects.AbstractWorldObject;
+
+public class ChatPvPMsg extends AbstractChatMsg {
+
+	protected int unknown03;
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public ChatPvPMsg(AbstractWorldObject source, String message) {
+		super(Protocol.CHATPVP, source, message);
+		this.unknown03 = 0;
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public ChatPvPMsg(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.CHATPVP, origin, reader);
+	}
+
+	/**
+	 * Copy constructor
+	 */
+	public ChatPvPMsg(ChatPvPMsg msg) {
+		super(msg);
+        this.unknown03 = msg.unknown03;
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied ByteBufferReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		this.unknown01 = reader.getInt();
+		this.unknown02 = reader.getInt();
+		this.unknown03 = reader.getInt();
+		this.message = reader.getString();
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied ByteBufferWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+		writer.putInt(0); // Probably Type
+		writer.putInt(0); // Probably objectUUID
+		writer.putInt(0);
+
+		writer.putString(this.message);
+	}
+
+	/**
+	 * @return the unknown03
+	 */
+	public int getUnknown03() {
+		return unknown03;
+	}
+
+	/**
+	 * @param unknown03
+	 *            the unknown03 to set
+	 */
+	public void setUnknown03(int unknown03) {
+		this.unknown03 = unknown03;
+	}
+
+}
diff --git a/src/engine/net/client/msg/chat/ChatSayMsg.java b/src/engine/net/client/msg/chat/ChatSayMsg.java
new file mode 100644
index 00000000..c8137cb8
--- /dev/null
+++ b/src/engine/net/client/msg/chat/ChatSayMsg.java
@@ -0,0 +1,91 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg.chat;
+
+import engine.Enum.GameObjectType;
+import engine.gameManager.SessionManager;
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.objects.AbstractCharacter;
+import engine.objects.AbstractGameObject;
+import engine.objects.AbstractWorldObject;
+import engine.server.MBServerStatics;
+
+public class ChatSayMsg extends AbstractChatMsg {
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public ChatSayMsg(AbstractWorldObject source, String message) {
+		super(Protocol.CHATSAY, source, message);
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public ChatSayMsg(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.CHATSAY, origin, reader);
+	}
+
+	/**
+	 * Copy constructor
+	 */
+	public ChatSayMsg(ChatSayMsg msg) {
+		super(msg);
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied ByteBufferReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+
+		long sourceID = reader.getLong();
+		this.unknown01 = reader.getInt();
+
+		int objectUUID = AbstractGameObject.extractUUID(GameObjectType.PlayerCharacter, sourceID);
+		this.source = SessionManager.getPlayerCharacterByID(objectUUID);
+
+		// this.unknown01 = reader.getInt(); //not needed?
+		this.message = reader.getString();
+
+		this.sourceName = reader.getString();
+		this.unknown02 = reader.getInt();
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied ByteBufferWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+
+		if (this.source != null){
+			writer.putInt(this.source.getObjectType().ordinal());
+			writer.putInt(source.getObjectUUID());
+		}
+		else
+			writer.putLong(0L);
+		writer.putInt(0);
+		writer.putString(this.message);
+		if (this.source == null) {
+			// TODO log error here
+			writer.putString("");
+			writer.putInt(0);
+		} else {
+			writer.putString(((AbstractCharacter) this.source).getFirstName());
+			writer.putInt(MBServerStatics.worldMapID);
+		}
+	}
+}
diff --git a/src/engine/net/client/msg/chat/ChatShoutMsg.java b/src/engine/net/client/msg/chat/ChatShoutMsg.java
new file mode 100644
index 00000000..91442752
--- /dev/null
+++ b/src/engine/net/client/msg/chat/ChatShoutMsg.java
@@ -0,0 +1,80 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg.chat;
+
+
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.objects.AbstractCharacter;
+import engine.objects.AbstractWorldObject;
+import engine.server.MBServerStatics;
+
+public class ChatShoutMsg extends AbstractChatMsg {
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public ChatShoutMsg(AbstractWorldObject source, String message) {
+		super(Protocol.CHATSHOUT, source, message);
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public ChatShoutMsg(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.CHATSHOUT, origin, reader);
+	}
+
+	/**
+	 * Copy constructor
+	 */
+	public ChatShoutMsg(ChatShoutMsg msg) {
+		super(msg);
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied ByteBufferReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		this.sourceType = reader.getInt();
+		this.sourceID = reader.getInt();
+		this.unknown01 = reader.getInt();
+
+		this.message = reader.getString();
+		this.sourceName = reader.getString();
+
+		this.unknown02 = reader.getInt();
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied ByteBufferWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+		writer.putInt(this.sourceType);
+		writer.putInt(this.sourceID);
+		writer.putInt(this.unknown01);
+		writer.putString(this.message);
+		if (this.source == null) {
+			// TODO log error here
+			writer.putString("");
+			writer.putInt(0);
+		} else {
+			writer.putString(((AbstractCharacter) this.source).getFirstName());
+			writer.putInt(MBServerStatics.worldMapID);
+		}
+	}
+}
diff --git a/src/engine/net/client/msg/chat/ChatSystemChannelMsg.java b/src/engine/net/client/msg/chat/ChatSystemChannelMsg.java
new file mode 100644
index 00000000..33c96e7d
--- /dev/null
+++ b/src/engine/net/client/msg/chat/ChatSystemChannelMsg.java
@@ -0,0 +1,162 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg.chat;
+
+
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.objects.AbstractWorldObject;
+
+public class ChatSystemChannelMsg extends AbstractChatMsg {
+
+	// TODO enum this?
+
+	// messageType
+	// 1 = Error
+	// 2 = Info
+	// 3 = Message of the Day
+	protected int messageType;
+
+	protected int unknown03;
+	protected int unknown04;
+
+	protected int channel;
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public ChatSystemChannelMsg(AbstractWorldObject source, String message, int messageType) {
+		super(Protocol.SYSTEMCHANNEL, source, message);
+		this.channel = 0;
+		this.messageType = messageType;
+		this.unknown03 = 0;
+		this.unknown04 = 0;
+	}
+
+	/**
+	 * Copy constructor
+	 */
+	public ChatSystemChannelMsg(ChatSystemChannelMsg msg) {
+		super(msg);
+        this.messageType = msg.messageType;
+        this.unknown03 = msg.unknown03;
+        this.unknown04 = msg.unknown04;
+        this.channel = msg.channel;
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public ChatSystemChannelMsg(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.SYSTEMCHANNEL, origin, reader);
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied ByteBufferReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		this.channel = reader.getInt();
+		this.messageType = reader.getInt();
+		this.sourceType = reader.getInt();
+		this.sourceID = reader.getInt();
+		this.message = reader.getString();
+		this.unknown03 = reader.getInt();
+		this.unknown04 = reader.getInt(); // seems to alternate beween 0x0 and
+		// 0x40C30000
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied ByteBufferWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+		writer.putInt(this.channel);
+		writer.putInt(this.messageType);
+		if (this.source != null)
+			;// writer.putLong(this.source.getCompositeID());
+		else
+			// writer.putLong(0L);
+			writer.putString(this.message);
+		writer.putInt(this.unknown03);
+		writer.putInt(this.unknown04);
+
+		/*
+		 * for (String key : this.vars.keySet()) { writer.putString(key);
+		 * writer.putString(this.vars.get(key)); }
+		 */
+	}
+
+	/**
+	 * @return the channel
+	 */
+	public int getChannel() {
+		return channel;
+	}
+
+	/**
+	 * @param channel
+	 *            the channel to set
+	 */
+	public void setChannel(int channel) {
+		this.channel = channel;
+	}
+
+	/**
+	 * @return the messageType
+	 */
+	public int getMessageType() {
+		return messageType;
+	}
+
+	/**
+	 * @param messageType
+	 *            the messageType to set
+	 */
+	public void setMessageType(int messageType) {
+		this.messageType = messageType;
+	}
+
+	/**
+	 * @return the unknown03
+	 */
+	public int getUnknown03() {
+		return unknown03;
+	}
+
+	/**
+	 * @param unknown03
+	 *            the unknown03 to set
+	 */
+	public void setUnknown03(int unknown03) {
+		this.unknown03 = unknown03;
+	}
+
+	/**
+	 * @return the unknown04
+	 */
+	public int getUnknown04() {
+		return unknown04;
+	}
+
+	/**
+	 * @param unknown04
+	 *            the unknown04 to set
+	 */
+	public void setUnknown04(int unknown04) {
+		this.unknown04 = unknown04;
+	}
+
+}
diff --git a/src/engine/net/client/msg/chat/ChatSystemMsg.java b/src/engine/net/client/msg/chat/ChatSystemMsg.java
new file mode 100644
index 00000000..e6e61968
--- /dev/null
+++ b/src/engine/net/client/msg/chat/ChatSystemMsg.java
@@ -0,0 +1,203 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg.chat;
+
+
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.objects.AbstractWorldObject;
+import engine.server.MBServerStatics;
+
+import java.util.concurrent.ConcurrentHashMap;
+
+
+public class ChatSystemMsg extends AbstractChatMsg {
+
+	// TODO enum this?
+
+	// channel
+	// 1 = System
+	// 2 = General Announcement (Flashing at top of screen!)
+	// 3 = Commander
+	// 5 = Nation
+	// 6 = Leader
+	// 7 = Shout
+	// 8 = Siege
+	// 9 = Territory
+	// 10 = Info
+	// 12 = Guild
+	// 13 = Inner Council
+	// 14 = Group
+	// 15 = City
+	// 16 = say
+	// 17 = Emote
+	// 19 = tell
+	protected int channel;
+
+	// messageType
+	// 1 = Error
+	// 2 = Info
+	// 3 = Message of the Day
+	protected int messageType;
+
+	protected int unknown03;
+	protected int numVariables;
+	// TODO this doesn't need to be a global value,
+	// its inherit to the HashMap
+	protected ConcurrentHashMap<String, String> vars;
+
+	// TODO make a list of variables
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public ChatSystemMsg(AbstractWorldObject source, String message) {
+		super(Protocol.SYSTEMBROADCASTCHANNEL, source, message);
+		this.messageType = 2;
+		this.unknown03 = 0;
+		this.numVariables = 0;
+		vars = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW);
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public ChatSystemMsg(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.SYSTEMBROADCASTCHANNEL, origin, reader);
+	}
+
+	/**
+	 * Copy constructor
+	 */
+	public ChatSystemMsg(ChatSystemMsg msg) {
+		super(msg);
+        this.channel = msg.channel;
+        this.messageType = msg.messageType;
+        this.unknown03 = msg.unknown03;
+        this.numVariables = msg.numVariables;
+        this.vars = msg.vars;
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied ByteBufferReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		this.vars = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW);
+
+		this.channel = reader.getInt();
+		this.messageType = reader.getInt();
+		this.sourceType = reader.getInt();
+		this.sourceID = reader.getInt();
+
+		this.message = reader.getString();
+		this.unknown03 = reader.getInt();
+		this.numVariables = reader.getInt();
+
+		for (int i = 0; i < this.numVariables; ++i) {
+			String key = reader.getString();
+			String value = reader.getString();
+			reader.get();
+			this.vars.put(key, value);
+		}
+
+	}
+
+
+	/**
+	 * Serializes the subclass specific items to the supplied ByteBufferWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+		writer.putInt(this.channel);
+		writer.putInt(this.messageType);
+		if (this.source != null){
+			writer.putInt(source.getObjectType().ordinal());
+			writer.putInt(source.getObjectUUID());
+		}
+		else
+			writer.putLong(0L);
+		writer.putString(this.message);
+		writer.putInt(this.unknown03);
+		writer.putInt(this.numVariables);
+
+		for (String key : this.vars.keySet()) {
+			writer.putString(key);
+			writer.putString(this.vars.get(key));
+		}
+	}
+
+	/**
+	 * @return the channel
+	 */
+	public int getChannel() {
+		return channel;
+	}
+
+	/**
+	 * @param channel
+	 *            the channel to set
+	 */
+	public void setChannel(int channel) {
+		this.channel = channel;
+	}
+
+	/**
+	 * @return the messageType
+	 */
+	public int getMessageType() {
+		return messageType;
+	}
+
+	/**
+	 * @param messageType
+	 *            the messageType to set
+	 */
+	public void setMessageType(int messageType) {
+		this.messageType = messageType;
+	}
+
+	/**
+	 * @return the unknown03
+	 */
+	public int getUnknown03() {
+		return unknown03;
+	}
+
+	/**
+	 * @param unknown03
+	 *            the unknown03 to set
+	 */
+	public void setUnknown03(int unknown03) {
+		this.unknown03 = unknown03;
+	}
+
+	/**
+	 * @return the numVariables
+	 */
+	public int getNumVariables() {
+		return numVariables;
+	}
+
+
+
+	/**
+	 * @return the vars
+	 */
+	public ConcurrentHashMap<String, String> getVars() {
+		return vars;
+	}
+
+
+}
diff --git a/src/engine/net/client/msg/chat/ChatTellMsg.java b/src/engine/net/client/msg/chat/ChatTellMsg.java
new file mode 100644
index 00000000..43d98098
--- /dev/null
+++ b/src/engine/net/client/msg/chat/ChatTellMsg.java
@@ -0,0 +1,171 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg.chat;
+
+
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.objects.AbstractCharacter;
+import engine.objects.AbstractWorldObject;
+import engine.server.MBServerStatics;
+
+public class ChatTellMsg extends AbstractChatMsg {
+
+	protected AbstractWorldObject target;
+	protected int targetType;
+	protected int targetID;
+	protected String targetName;
+	protected int unknown03;
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public ChatTellMsg(AbstractWorldObject source, AbstractWorldObject target, String message) {
+		super(Protocol.CHATTELL, source, message);
+		this.target = target;
+
+		// TODO could this be simplified? Check serializer;
+		if (this.target != null) {
+			this.targetType = target.getObjectType().ordinal();
+			this.targetID = target.getObjectUUID();
+			this.targetName = target.getName();
+		}
+		this.unknown03 = 0;
+	}
+
+	/**
+	 * Copy constructor
+	 */
+	public ChatTellMsg(ChatTellMsg msg) {
+		super(msg);
+		this.target = msg.target;
+		this.targetType = msg.targetType;
+		this.targetID = msg.targetID;
+		this.targetName = msg.targetName;
+		this.unknown03 = msg.unknown03;
+	}
+
+	public int getTargetType() {
+		return targetType;
+	}
+
+	public int getTargetID() {
+		return targetID;
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public ChatTellMsg(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.CHATTELL, origin, reader);
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied ByteBufferReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		this.sourceType = reader.getInt();
+		this.sourceID = reader.getInt();
+		this.unknown01 = reader.getInt();
+		this.message = reader.getString();
+		this.sourceName = reader.getString(); // sourceName
+		this.targetType = reader.getInt();
+		this.targetID = reader.getInt();
+		this.targetName = reader.getString();
+		this.unknown02 = reader.getInt();
+		this.unknown03 = reader.getInt();
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied ByteBufferWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+		writer.putInt(this.source.getObjectType().ordinal());
+		writer.putInt(source.getObjectUUID());
+		writer.putInt(this.unknown01);
+		writer.putString(this.message);
+		if (AbstractWorldObject.IsAbstractCharacter(source)) {
+			writer.putString(((AbstractCharacter) this.source).getFirstName());
+		} else {
+			writer.putString(this.source.getName());
+		}
+		if (this.target != null) {
+			writer.putInt(this.target.getObjectType().ordinal());
+			writer.putInt(this.target.getObjectUUID());
+			if (AbstractWorldObject.IsAbstractCharacter(target)) {
+				writer.putString(((AbstractCharacter) this.target).getFirstName());
+			} else {
+				writer.putString(this.target.getName());
+			}
+		} else {
+			writer.putInt(this.targetType);
+			writer.putInt(this.targetID);
+			writer.putString(this.targetName);
+		}
+		writer.putInt(MBServerStatics.worldMapID);
+		writer.putInt(MBServerStatics.worldMapID);
+	}
+
+	/**
+	 * @return the target
+	 */
+	public AbstractWorldObject getTarget() {
+		return target;
+	}
+
+	/**
+	 * @param target
+	 *            the target to set
+	 */
+	public void setTarget(AbstractWorldObject target) {
+		this.target = target;
+	}
+
+
+
+
+	/**
+	 * @return the targetName
+	 */
+	public String getTargetName() {
+		return targetName;
+	}
+
+	/**
+	 * @param targetName
+	 *            the targetName to set
+	 */
+	public void setTargetName(String targetName) {
+		this.targetName = targetName;
+	}
+
+	/**
+	 * @return the unknown03
+	 */
+	public int getUnknown03() {
+		return unknown03;
+	}
+
+	/**
+	 * @param unknown03
+	 *            the unknown03 to set
+	 */
+	public void setUnknown03(int unknown03) {
+		this.unknown03 = unknown03;
+	}
+
+}
diff --git a/src/engine/net/client/msg/chat/GuildEnterWorldMsg.java b/src/engine/net/client/msg/chat/GuildEnterWorldMsg.java
new file mode 100644
index 00000000..ef3e3ed7
--- /dev/null
+++ b/src/engine/net/client/msg/chat/GuildEnterWorldMsg.java
@@ -0,0 +1,246 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg.chat;
+
+import engine.Enum.GameObjectType;
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.objects.PlayerCharacter;
+
+public class GuildEnterWorldMsg extends AbstractChatMsg {
+
+	protected String name;
+	protected int guildTitle;
+	protected int charterType;
+	protected int unknown04;
+	protected int guildUUID;
+	protected int unknown05;
+	protected int unknown06;
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public GuildEnterWorldMsg(PlayerCharacter source) {
+		super(Protocol.GUILDMEMBERONLINE, source, "[!PLAYERRANK!] !PLAYERNAME! is online");
+
+		this.name = "";
+		this.guildTitle = 0;
+		this.charterType = 9;
+		this.unknown02 = 12;
+		this.unknown04 = 2;
+		this.guildUUID = 0;
+		this.unknown05 = 0x2E46D00C;
+		this.unknown06 = 0;
+	}
+
+	/**
+	 * Copy constructor
+	 */
+	public GuildEnterWorldMsg(GuildEnterWorldMsg msg) {
+		super(msg);
+        this.name = msg.name;
+        this.guildTitle = msg.guildTitle;
+        this.charterType = msg.charterType;
+        this.unknown04 = msg.unknown04;
+        this.guildUUID = msg.guildUUID;
+        this.unknown05 = msg.unknown05;
+        this.unknown06 = msg.unknown06;
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public GuildEnterWorldMsg(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.GUILDMEMBERONLINE, origin, reader);
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied ByteBufferWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+		writer.putString(this.name);
+
+		writer.putInt(guildTitle);
+		writer.put((byte) 1); // forgot this
+		writer.putInt(this.charterType);
+		writer.putInt(this.unknown02);
+		writer.putInt(this.unknown04);
+                writer.putInt(GameObjectType.Guild.ordinal());
+		writer.putInt(this.guildUUID);
+
+		writer.putString(this.message);
+		writer.putInt(this.unknown05);
+		writer.putInt(this.unknown06);
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied ByteBufferReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		this.name = reader.getString();
+		this.guildTitle = reader.getInt();
+		reader.get(); // forgot this
+		this.unknown02 = reader.getInt();
+		this.charterType = reader.getInt();
+		this.unknown04 = reader.getInt();
+                reader.getInt(); // Object Type Padding
+		this.guildUUID = reader.getInt();
+		this.message = reader.getString();
+		this.unknown05 = reader.getInt();
+		this.unknown06 = reader.getInt();
+	}
+
+	/**
+	 * @return the name
+	 */
+	public String getName() {
+		return name;
+	}
+
+	/**
+	 * @param name
+	 *            the name to set
+	 */
+	public void setName(String name) {
+		this.name = name;
+	}
+
+	/**
+	 * @return the guildTitle
+	 */
+	public int getGuildTitle() {
+		return guildTitle;
+	}
+
+	/**
+	 * @param guildTitle
+	 *            the guildTitle to set
+	 */
+	public void setGuildTitle(int guildTitle) {
+		this.guildTitle = guildTitle;
+	}
+
+	/**
+	 * @return the unknown02
+	 */
+	@Override
+	public int getUnknown02() {
+		return unknown02;
+	}
+
+	/**
+	 * @param unknown02
+	 *            the unknown02 to set
+	 */
+	@Override
+	public void setUnknown02(int unknown02) {
+		this.unknown02 = unknown02;
+	}
+
+	/**
+	 * @return the unknown03
+	 */
+	public int getCharter() {
+		return charterType;
+	}
+
+	/**
+	 * @param unknown03
+	 *            the unknown03 to set
+	 */
+	public void setCharter(int charter) {
+		this.charterType = charter;
+	}
+
+	/**
+	 * @return the unknown04
+	 */
+	public int getUnknown04() {
+		return unknown04;
+	}
+
+	/**
+	 * @param unknown04
+	 *            the unknown04 to set
+	 */
+	public void setUnknown04(int unknown04) {
+		this.unknown04 = unknown04;
+	}
+
+	/**
+	 * @return the guildUUID
+	 */
+	public int getGuildUUID() {
+		return guildUUID;
+	}
+
+	/**
+	 * @param guildUUID
+	 *            the guildUUID to set
+	 */
+	public void setGuildUUID(int guildUUID) {
+		this.guildUUID = guildUUID;
+	}
+
+	/**
+	 * @return the message
+	 */
+	@Override
+	public String getMessage() {
+		return message;
+	}
+
+	/**
+	 * @param message
+	 *            the message to set
+	 */
+	@Override
+	public void setMessage(String message) {
+		this.message = message;
+	}
+
+	/**
+	 * @return the unknown05
+	 */
+	public int getUnknown05() {
+		return unknown05;
+	}
+
+	/**
+	 * @param unknown05
+	 *            the unknown05 to set
+	 */
+	public void setUnknown05(int unknown05) {
+		this.unknown05 = unknown05;
+	}
+
+	/**
+	 * @return the unknown06
+	 */
+	public int getUnknown06() {
+		return unknown06;
+	}
+
+	/**
+	 * @param unknown06
+	 *            the unknown06 to set
+	 */
+	public void setUnknown06(int unknown06) {
+		this.unknown06 = unknown06;
+	}
+
+}
diff --git a/src/engine/net/client/msg/commands/ClientAdminCommandMsg.java b/src/engine/net/client/msg/commands/ClientAdminCommandMsg.java
new file mode 100644
index 00000000..5335f660
--- /dev/null
+++ b/src/engine/net/client/msg/commands/ClientAdminCommandMsg.java
@@ -0,0 +1,101 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg.commands;
+
+import engine.Enum.GameObjectType;
+import engine.gameManager.DbManager;
+import engine.math.Vector3f;
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.net.client.msg.ClientNetMsg;
+import engine.objects.AbstractGameObject;
+
+public class ClientAdminCommandMsg extends ClientNetMsg {
+
+	private int msgType;
+	private String msgCommand;
+	private long senderCompID;
+        private int targetType;
+	private int targetUUID;
+	private Vector3f location;
+	private AbstractGameObject target;
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public ClientAdminCommandMsg() {
+		super(Protocol.CLIENTADMINCOMMAND);
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public ClientAdminCommandMsg(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.CLIENTADMINCOMMAND, origin, reader);
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied ByteBufferWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+		// TODO implement Serialize()
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied ByteBufferReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		this.msgType = reader.getInt();
+		this.msgCommand = reader.getString();
+                this.targetType = reader.getInt();
+		this.targetUUID = reader.getInt();
+		this.senderCompID = reader.getLong(); // always null??
+		this.location = new Vector3f(reader.getFloat(), reader.getFloat(), reader.getFloat());
+
+		if (targetUUID != 0) 
+                    target = DbManager.getObject(GameObjectType.values()[this.targetType], this.targetUUID);
+	}
+
+	/**
+	 * @return the msgCommand
+	 */
+	public String getMsgCommand() {
+		return msgCommand;
+	}
+
+	/**
+	 * @return the targetUUID
+	 */
+	public int getTargetUUID() {
+		return targetUUID;
+	}
+
+	/**
+	 * @return the location
+	 */
+	public Vector3f getLocation() {
+		return location;
+	}
+
+	/**
+	 * @return the target
+	 */
+	public AbstractGameObject getTarget() {
+		return target;
+	}
+
+}
diff --git a/src/engine/net/client/msg/group/AppointGroupLeaderMsg.java b/src/engine/net/client/msg/group/AppointGroupLeaderMsg.java
new file mode 100644
index 00000000..636c34d4
--- /dev/null
+++ b/src/engine/net/client/msg/group/AppointGroupLeaderMsg.java
@@ -0,0 +1,129 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg.group;
+
+
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.net.client.msg.ClientNetMsg;
+
+public class AppointGroupLeaderMsg extends ClientNetMsg {
+
+	private int targetType;
+	private int targetID;
+	private int response;
+	private String message;
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public AppointGroupLeaderMsg() {
+		super(Protocol.GROUPLEADERAPPOINT);
+		this.message = "You are already the group leader";
+
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public AppointGroupLeaderMsg(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.GROUPLEADERAPPOINT, origin, reader);
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied ByteBufferWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+		writer.putInt(this.targetType);
+		writer.putInt(this.targetID);
+		writer.putInt(this.response);
+		if (this.response == 1)
+			writer.putString(this.message);
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied ByteBufferReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		this.targetType = reader.getInt();
+		this.targetID = reader.getInt();
+		this.response = reader.getInt();
+		if (this.response == 1)
+			this.message = reader.getString();
+	}
+
+	/**
+	 * @return the targetType
+	 */
+	public int getTargetType() {
+		return targetType;
+	}
+
+	/**
+	 * @param targetType
+	 *            the targetType to set
+	 */
+	public void setTargetType(int targetType) {
+		this.targetType = targetType;
+	}
+
+	/**
+	 * @return the targetID
+	 */
+	public int getTargetID() {
+		return targetID;
+	}
+
+	/**
+	 * @param targetID
+	 *            the targetID to set
+	 */
+	public void setTargetID(int targetID) {
+		this.targetID = targetID;
+	}
+
+	/**
+	 * @return the response
+	 */
+	public int getResponse() {
+		return response;
+	}
+
+	/**
+	 * @param response
+	 *            the response to set
+	 */
+	public void setResponse(int response) {
+		this.response = response;
+	}
+
+	/**
+	 * @return the message
+	 */
+	public String getMessage() {
+		return message;
+	}
+
+	/**
+	 * @param message
+	 *            the message to set
+	 */
+	public void setMessage(String message) {
+		this.message = message;
+	}
+
+}
diff --git a/src/engine/net/client/msg/group/DisbandGroupMsg.java b/src/engine/net/client/msg/group/DisbandGroupMsg.java
new file mode 100644
index 00000000..d542420b
--- /dev/null
+++ b/src/engine/net/client/msg/group/DisbandGroupMsg.java
@@ -0,0 +1,69 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg.group;
+
+
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.net.client.msg.ClientNetMsg;
+
+public class DisbandGroupMsg extends ClientNetMsg {
+
+	private int unknown01;
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public DisbandGroupMsg() {
+		super(Protocol.GROUPDISBAND);
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the ByteBuffer into a message. If a BufferUnderflow occurs (based on reading past the limit) then this constructor Throws that Exception to the caller.
+	 */
+	public DisbandGroupMsg(AbstractConnection origin, ByteBufferReader reader)
+			 {
+		super(Protocol.GROUPDISBAND, origin, reader);
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied ByteBufferWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+		writer.putInt(this.unknown01);
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied ByteBufferReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		this.unknown01 = reader.getInt();
+	}
+
+	/**
+	 * @return the unknown01
+	 */
+	public int getUnknown01() {
+		return unknown01;
+	}
+
+	/**
+	 * @param unknown01
+	 *            the unknown01 to set
+	 */
+	public void setUnknown01(int unknown01) {
+		this.unknown01 = unknown01;
+	}
+
+}
diff --git a/src/engine/net/client/msg/group/FormationFollowMsg.java b/src/engine/net/client/msg/group/FormationFollowMsg.java
new file mode 100644
index 00000000..c57401ca
--- /dev/null
+++ b/src/engine/net/client/msg/group/FormationFollowMsg.java
@@ -0,0 +1,112 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg.group;
+
+
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.net.client.msg.ClientNetMsg;
+
+public class FormationFollowMsg extends ClientNetMsg {
+
+	private boolean follow;
+	private int formation;
+	private int unknown01;
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public FormationFollowMsg() {
+		super(Protocol.GROUPFOLLOW);
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the ByteBuffer into a message. If a BufferUnderflow occurs (based on reading past the limit) then this constructor Throws that Exception to the caller.
+	 */
+	public FormationFollowMsg(AbstractConnection origin, ByteBufferReader reader)
+			 {
+		super(Protocol.GROUPFOLLOW, origin, reader);
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied ByteBufferWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+		if (this.follow) {
+			writer.putInt(1);
+			writer.putInt(0);
+		} else {
+			writer.putInt(0);
+			writer.putInt(1);
+		}
+		writer.putInt(this.formation);
+		writer.putInt(this.unknown01);
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied ByteBufferReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+        this.follow = reader.getInt() == 1;
+		reader.getInt();
+		this.formation = reader.getInt();
+		this.unknown01 = reader.getInt();
+	}
+
+	/**
+	 * @return the follow
+	 */
+	public boolean isFollow() {
+		return follow;
+	}
+
+	/**
+	 * @param follow
+	 *            the follow to set
+	 */
+	public void setFollow(boolean follow) {
+		this.follow = follow;
+	}
+
+	/**
+	 * @return the formation
+	 */
+	public int getFormation() {
+		return formation;
+	}
+
+	/**
+	 * @param formation
+	 *            the formation to set
+	 */
+	public void setFormation(int formation) {
+		this.formation = formation;
+	}
+
+	/**
+	 * @return the unknown01
+	 */
+	public int getUnknown01() {
+		return unknown01;
+	}
+
+	/**
+	 * @param unknown01
+	 *            the unknown01 to set
+	 */
+	public void setUnknown01(int unknown01) {
+		this.unknown01 = unknown01;
+	}
+
+}
diff --git a/src/engine/net/client/msg/group/GroupInviteMsg.java b/src/engine/net/client/msg/group/GroupInviteMsg.java
new file mode 100644
index 00000000..c7e98ed5
--- /dev/null
+++ b/src/engine/net/client/msg/group/GroupInviteMsg.java
@@ -0,0 +1,220 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg.group;
+
+
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.net.client.msg.ClientNetMsg;
+
+public class GroupInviteMsg extends ClientNetMsg {
+
+	private int sourceType;
+	private int sourceID;
+	private int targetType;
+	private int targetID;
+	private int groupType;
+	private int groupID;
+	private int unknown01;
+	private int invited;
+	private String name;
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public GroupInviteMsg() {
+		super(Protocol.INVITEGROUP);
+		this.name = "";
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public GroupInviteMsg(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.INVITEGROUP, origin, reader);
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied ByteBufferWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+		writer.putInt(this.sourceType);
+		writer.putInt(this.sourceID);
+		writer.putInt(this.targetType);
+		writer.putInt(this.targetID);
+		writer.putInt(this.groupType);
+		writer.putInt(this.groupID);
+		writer.putInt(this.unknown01);
+		writer.putInt(this.invited);
+		if (this.invited == 1)
+			writer.putString(this.name);
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied ByteBufferReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+
+		this.sourceType = reader.getInt();
+		this.sourceID = reader.getInt();
+		this.targetType = reader.getInt();
+		this.targetID = reader.getInt();
+		this.groupType = reader.getInt();
+		this.groupID = reader.getInt();
+		this.unknown01 = reader.getInt();
+		this.invited = reader.getInt();
+		if (this.invited == 1)
+			this.name = reader.getString();
+
+	}
+
+	/**
+	 * @return the sourceType
+	 */
+	public int getSourceType() {
+		return sourceType;
+	}
+
+	/**
+	 * @param sourceType
+	 *            the sourceType to set
+	 */
+	public void setSourceType(int sourceType) {
+		this.sourceType = sourceType;
+	}
+
+	/**
+	 * @return the sourceID
+	 */
+	public int getSourceID() {
+		return sourceID;
+	}
+
+	/**
+	 * @param sourceID
+	 *            the sourceID to set
+	 */
+	public void setSourceID(int sourceID) {
+		this.sourceID = sourceID;
+	}
+
+	/**
+	 * @return the targetType
+	 */
+	public int getTargetType() {
+		return targetType;
+	}
+
+	/**
+	 * @param targetType
+	 *            the targetType to set
+	 */
+	public void setTargetType(int targetType) {
+		this.targetType = targetType;
+	}
+
+	/**
+	 * @return the targetID
+	 */
+	public int getTargetID() {
+		return targetID;
+	}
+
+	/**
+	 * @param targetID
+	 *            the targetID to set
+	 */
+	public void setTargetID(int targetID) {
+		this.targetID = targetID;
+	}
+
+	/**
+	 * @return the groupType
+	 */
+	public int getGroupType() {
+		return groupType;
+	}
+
+	/**
+	 * @param groupType
+	 *            the groupType to set
+	 */
+	public void setGroupType(int groupType) {
+		this.groupType = groupType;
+	}
+
+	/**
+	 * @return the groupID
+	 */
+	public int getGroupID() {
+		return groupID;
+	}
+
+	/**
+	 * @param groupID
+	 *            the groupID to set
+	 */
+	public void setGroupID(int groupID) {
+		this.groupID = groupID;
+	}
+
+	/**
+	 * @return the unknown01
+	 */
+	public int getUnknown01() {
+		return unknown01;
+	}
+
+	/**
+	 * @param unknown01
+	 *            the unknown01 to set
+	 */
+	public void setUnknown01(int unknown01) {
+		this.unknown01 = unknown01;
+	}
+
+	/**
+	 * @return the invited
+	 */
+	public int getInvited() {
+		return invited;
+	}
+
+	/**
+	 * @param invited
+	 *            the invited to set
+	 */
+	public void setInvited(int invited) {
+		this.invited = invited;
+	}
+
+	/**
+	 * @return the name
+	 */
+	public String getName() {
+		return name;
+	}
+
+	/**
+	 * @param name
+	 *            the name to set
+	 */
+	public void setName(String name) {
+		this.name = name;
+	}
+
+}
diff --git a/src/engine/net/client/msg/group/GroupInviteResponseMsg.java b/src/engine/net/client/msg/group/GroupInviteResponseMsg.java
new file mode 100644
index 00000000..c988f43e
--- /dev/null
+++ b/src/engine/net/client/msg/group/GroupInviteResponseMsg.java
@@ -0,0 +1,123 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg.group;
+
+
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.net.client.msg.ClientNetMsg;
+
+public class GroupInviteResponseMsg extends ClientNetMsg {
+
+	private int groupType;
+	private int groupID;
+	private int unknown01;
+	private int unknown02;
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public GroupInviteResponseMsg() {
+		super(Protocol.JOINGROUP);
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the ByteBuffer into a message. If a BufferUnderflow occurs (based on reading past the limit) then this constructor Throws that Exception to the caller.
+	 */
+	public GroupInviteResponseMsg(AbstractConnection origin, ByteBufferReader reader)
+			 {
+		super(Protocol.JOINGROUP, origin, reader);
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied ByteBufferWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+		writer.putInt(this.groupType);
+		writer.putInt(this.groupID);
+		writer.putInt(this.unknown01);
+		writer.putInt(this.unknown02);
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied ByteBufferReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		this.groupType = reader.getInt();
+		this.groupID = reader.getInt();
+		this.unknown01 = reader.getInt();
+		this.unknown02 = reader.getInt();
+	}
+
+	/**
+	 * @return the groupType
+	 */
+	public int getGroupType() {
+		return groupType;
+	}
+
+	/**
+	 * @param groupType
+	 *            the groupType to set
+	 */
+	public void setGroupType(int groupType) {
+		this.groupType = groupType;
+	}
+
+	/**
+	 * @return the groupID
+	 */
+	public int getGroupID() {
+		return groupID;
+	}
+
+	/**
+	 * @param groupID
+	 *            the groupID to set
+	 */
+	public void setGroupID(int groupID) {
+		this.groupID = groupID;
+	}
+
+	/**
+	 * @return the unknown01
+	 */
+	public int getUnknown01() {
+		return unknown01;
+	}
+
+	/**
+	 * @param unknown01
+	 *            the unknown01 to set
+	 */
+	public void setUnknown01(int unknown01) {
+		this.unknown01 = unknown01;
+	}
+
+	/**
+	 * @return the unknown02
+	 */
+	public int getUnknown02() {
+		return unknown02;
+	}
+
+	/**
+	 * @param unknown02
+	 *            the unknown02 to set
+	 */
+	public void setUnknown02(int unknown02) {
+		this.unknown02 = unknown02;
+	}
+
+}
diff --git a/src/engine/net/client/msg/group/GroupUpdateMsg.java b/src/engine/net/client/msg/group/GroupUpdateMsg.java
new file mode 100644
index 00000000..e4b554bc
--- /dev/null
+++ b/src/engine/net/client/msg/group/GroupUpdateMsg.java
@@ -0,0 +1,352 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg.group;
+
+import engine.Enum.GameObjectType;
+import engine.gameManager.GroupManager;
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.net.client.msg.ClientNetMsg;
+import engine.objects.Group;
+import engine.objects.PlayerCharacter;
+
+import java.util.Collections;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+//See GroupUpdateMsgBreakdown.txt is SBData directory.
+public class GroupUpdateMsg extends ClientNetMsg {
+
+    private int messageType;
+    private int unknown02;
+    private int playerUUID;
+    private Set<PlayerCharacter> players;
+    private Group group;
+
+    /**
+     * This is the general purpose constructor.
+     */
+    public GroupUpdateMsg() {
+        super(Protocol.UPDATEGROUP);
+        this.messageType = 1;
+        this.unknown02 = 1;
+        this.playerUUID = 0;
+        this.players = Collections.newSetFromMap(new ConcurrentHashMap<>());
+    }
+
+    public GroupUpdateMsg(int messageType, int unknown02, Set<PlayerCharacter> players, Group group) {
+        super(Protocol.UPDATEGROUP);
+        this.messageType = messageType;
+        this.unknown02 = unknown02;
+        this.playerUUID = 0;
+        this.players = players;
+        this.group = group;
+    }
+
+    /**
+     * This constructor is used by NetMsgFactory. It attempts to deserialize the
+     * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+     * past the limit) then this constructor Throws that Exception to the
+     * caller.
+     */
+    public GroupUpdateMsg(AbstractConnection origin, ByteBufferReader reader)  {
+        super(Protocol.UPDATEGROUP, origin, reader);
+    }
+
+    /**
+     * Serializes the subclass specific items to the supplied ByteBufferWriter.
+     */
+    @Override
+    protected void _serialize(ByteBufferWriter writer) {
+        writer.putInt(GameObjectType.Group.ordinal());
+        writer.putInt(this.group.getObjectUUID());
+        writer.putInt(this.messageType);
+
+	// 5 breaks everything including movement etc
+        // 4 sends a party dissolved message
+        // 3 closes the group window and leaves the group
+        // 2 seems to update the location but not the stats correctly upon coming back into range
+        // 1 seems to add you to the group but if called by a job tops up your stats on the client and desyncs it
+        
+        switch (messageType) {
+            case 4:
+                GroupUpdateMsg._serializeFour(writer);
+                break;
+            case 5:
+                this._serializeFive(writer);
+                break;
+            case 6:
+                this._serializeSix(writer);
+                break;
+            case 7:
+                this._serializeSeven(writer);
+                break;
+            case 8:
+                this._serializeEight(writer);
+                break;
+            default:
+                writer.putInt(this.unknown02);
+                // Send player data
+                writer.putInt(this.players.size());
+                int i = 0;
+                for (PlayerCharacter pc : this.players) {
+                    this.serializePlayer(writer, pc, this.messageType, i++);
+                }
+                writer.putInt(0); // end data
+                break;
+        }
+    }
+
+    // *** Refactor: This method is an abortion.  Needs to be re-written from scratch.
+    
+    private void serializePlayer(ByteBufferWriter writer, PlayerCharacter player, int messageType, int count) {
+        
+        if (messageType == 1) {
+            writer.putString((player != null) ? player.getFirstName() : "nullError");
+            writer.putString((player != null) ? player.getLastName() : "");
+        } else if (messageType == 2) {
+            if (count == 0) {
+                writer.putString((player != null) ? player.getFirstName() : "nullError");
+                writer.putString((player != null) ? player.getLastName() : "");
+            } else {
+                writer.putInt(0);
+            }
+        } else if (messageType == 3) {
+            writer.putString(" ");
+            writer.putString(" ");
+        } else {
+            writer.putInt(0);
+            writer.putInt(0);
+        }
+
+        if (messageType == 3) {
+            for (int i = 0; i < 6; i++) {
+                writer.putInt(0);
+            }
+        } else {
+            // mana health stam %
+            writer.putInt((int) (player.getHealth() / player.getHealthMax() * 100)); // should be health but does nothing
+            writer.putInt((int) (player.getStamina() / player.getStaminaMax() * 100)); // stam %
+            writer.putInt((int) (player.getMana() / player.getManaMax() * 100)); // mana %
+            writer.putInt((player != null) ? Float.floatToIntBits(player.getLoc().getX()) : 0);
+            writer.putInt((player != null) ? Float.floatToIntBits(player.getLoc().getY()) : 0);
+            writer.putInt((player != null) ? Float.floatToIntBits(player.getLoc().getZ()) : 0);
+        }
+        
+        if (player == null)
+            writer.putLong(0);
+        else {
+            writer.putInt(GameObjectType.PlayerCharacter.ordinal());
+            writer.putInt(player.getObjectUUID());
+        }
+
+        if (messageType == 3) {
+            writer.putInt(0);
+            writer.putInt(-1);
+            writer.putInt(0);
+            return;
+        } else if (messageType == 5) {
+            writer.putInt(0);
+            writer.putInt(0);
+            return;
+        }
+        if (group != null && player != null) {
+            writer.putInt((this.group.getGroupLead() == player) ? 2 : 1);
+        } else {
+            writer.putInt(1);
+        }
+        if (messageType == 2) {
+            writer.putInt(-1);
+            writer.putInt(0);
+            return;
+        }
+        writer.putInt(1);
+        writer.putInt(1);
+        writer.put((byte) 1);
+
+	// if sending message type 1 this seems to make the group window flicker the button
+        // i think getfollow and split gold might be the wrong way around
+        if (group != null) {
+            writer.put(this.group.getSplitGold() ? (byte) 1 : (byte) 0);
+        } else {
+            writer.put((byte) 0);
+        }
+
+        // always gets reset on a message type 1
+        if (player != null) {
+            writer.put(player.getFollow() ? (byte) 1 : (byte) 0);
+        } else {
+            writer.put((byte) 0);
+        }
+    }
+
+    private static void _serializeFour(ByteBufferWriter writer) {
+
+        // 4 sends a party dissolved window
+        for (int i = 0; i < 3; i++) {
+            writer.putInt(0);
+        }
+    }
+
+    //sync player's stats and position
+    private void _serializeFive(ByteBufferWriter writer) {
+        writer.putInt(1);
+        writer.putInt(players.size() - 1);
+        for (PlayerCharacter player : players) {
+            if (player.getObjectUUID() == this.playerUUID) {
+                continue; //skip self
+            }
+            writer.putInt((int) (player.getHealth() / player.getHealthMax() * 100));
+            writer.putInt((int) (player.getStamina() / player.getStaminaMax() * 100));
+            writer.putInt((int) (player.getMana() / player.getManaMax() * 100));
+            writer.putFloat(player.getLoc().x);
+            writer.putFloat(player.getLoc().y);
+            writer.putFloat(player.getLoc().z);
+            writer.putInt(GameObjectType.PlayerCharacter.ordinal());
+            writer.putInt(player.getObjectUUID());
+           
+        }
+        writer.putInt(0);
+        writer.putInt(0);
+    }
+
+    private void _serializeSix(ByteBufferWriter writer) {
+        writer.putInt(0);
+        if (this.group != null) {
+            writer.put(this.group.getSplitGold() ? (byte) 1 : (byte) 0);
+        } else {
+            writer.put((byte) 0);
+        }
+        writer.putInt(0);
+        writer.putInt(0);
+    }
+
+    private void _serializeSeven(ByteBufferWriter writer) {
+        PlayerCharacter player = this.players.iterator().next();
+        writer.putInt(0);
+        if (player != null) {
+            writer.put(player.getFollow() ? (byte) 1 : (byte) 0);
+        } else {
+            writer.put((byte) 0);
+        }
+        writer.putInt(0);
+        writer.putInt(0);
+    }
+
+    private void _serializeEight(ByteBufferWriter writer) {
+        PlayerCharacter player = this.players.iterator().next();;
+        writer.putInt(0);
+        if (player != null) {
+            writer.put(player.getFollow() ? (byte) 1 : (byte) 0);
+            writer.putInt(GameObjectType.PlayerCharacter.ordinal());
+            writer.putInt(player.getObjectUUID());
+        } else {
+            writer.put((byte) 0);
+            writer.putLong(0L);
+        }
+        writer.putInt(0);
+        writer.putInt(0);
+    }
+
+    /**
+     * Deserializes the subclass specific items from the supplied
+     * ByteBufferReader.
+     */
+    @Override
+    protected void _deserialize(ByteBufferReader reader)  {
+        this.players = Collections.newSetFromMap(new ConcurrentHashMap<>());
+
+        reader.getInt();
+        this.group = GroupManager.getGroup(reader.getInt());
+        // TODO figure this mess out
+    }
+
+    public void addPlayer(PlayerCharacter value) {
+        this.players.add(value);
+    }
+
+    public void setPlayer(PlayerCharacter value) {
+        this.players.clear();
+        this.players.add(value);
+    }
+
+    /**
+     * @return the messageType
+     */
+    public int getMessageType() {
+        return messageType;
+    }
+
+    /**
+     * @param messageType the messageType to set
+     */
+    public void setMessageType(int messageType) {
+        this.messageType = messageType;
+    }
+
+    /**
+     * @return the unknown02
+     */
+    public int getUnknown02() {
+        return unknown02;
+    }
+
+    /**
+     * @param unknown02 the unknown02 to set
+     */
+    public void setUnknown02(int unknown02) {
+        this.unknown02 = unknown02;
+    }
+
+    /**
+     * @return the playerUUID
+     */
+    public long getPlayerID() {
+        return playerUUID;
+    }
+
+    /**
+     * @param playerUUID the playerUUID to set
+     */
+    public void setPlayerUUID(int playerUUID) {
+        this.playerUUID = playerUUID;
+    }
+
+    /**
+     * @return the players
+     */
+    public Set<PlayerCharacter> getPlayers() {
+        return players;
+    }
+
+    /**
+     * @param players the players to set
+     */
+    public void setPlayers(Set<PlayerCharacter> players) {
+        this.players = players;
+    }
+
+    /**
+     * @return the group
+     */
+    public Group getGroup() {
+        return group;
+    }
+
+    /**
+     * @param group the group to set
+     */
+    public void setGroup(Group group) {
+        this.group = group;
+    }
+
+}
diff --git a/src/engine/net/client/msg/group/LeaveGroupMsg.java b/src/engine/net/client/msg/group/LeaveGroupMsg.java
new file mode 100644
index 00000000..8c3f1678
--- /dev/null
+++ b/src/engine/net/client/msg/group/LeaveGroupMsg.java
@@ -0,0 +1,122 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg.group;
+
+
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.net.client.msg.ClientNetMsg;
+
+public class LeaveGroupMsg extends ClientNetMsg {
+
+	private int unknown01;
+	private int unknown02;
+	private int unknown03;
+	private int unknown04;
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public LeaveGroupMsg() {
+		super(Protocol.LEAVEGROUP);
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the ByteBuffer into a message. If a BufferUnderflow occurs (based on reading past the limit) then this constructor Throws that Exception to the caller.
+	 */
+	public LeaveGroupMsg(AbstractConnection origin, ByteBufferReader reader)
+			 {
+		super(Protocol.LEAVEGROUP, origin, reader);
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied ByteBufferWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+		writer.putInt(this.unknown01);
+		writer.putInt(this.unknown02);
+		writer.putInt(this.unknown03);
+		writer.putInt(this.unknown04);
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied ByteBufferReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		this.unknown01 = reader.getInt();
+		this.unknown02 = reader.getInt();
+		this.unknown03 = reader.getInt();
+		this.unknown04 = reader.getInt();
+	}
+
+	/**
+	 * @return the unknown01
+	 */
+	public int getUnknown01() {
+		return unknown01;
+	}
+
+	/**
+	 * @param unknown01
+	 *            the unknown01 to set
+	 */
+	public void setUnknown01(int unknown01) {
+		this.unknown01 = unknown01;
+	}
+
+	/**
+	 * @return the unknown02
+	 */
+	public int getUnknown02() {
+		return unknown02;
+	}
+
+	/**
+	 * @param unknown02
+	 *            the unknown02 to set
+	 */
+	public void setUnknown02(int unknown02) {
+		this.unknown02 = unknown02;
+	}
+
+	/**
+	 * @return the unknown03
+	 */
+	public int getUnknown03() {
+		return unknown03;
+	}
+
+	/**
+	 * @param unknown03
+	 *            the unknown03 to set
+	 */
+	public void setUnknown03(int unknown03) {
+		this.unknown03 = unknown03;
+	}
+
+	/**
+	 * @return the unknown04
+	 */
+	public int getUnknown04() {
+		return unknown04;
+	}
+
+	/**
+	 * @param unknown04
+	 *            the unknown04 to set
+	 */
+	public void setUnknown04(int unknown04) {
+		this.unknown04 = unknown04;
+	}
+}
diff --git a/src/engine/net/client/msg/group/RemoveFromGroupMsg.java b/src/engine/net/client/msg/group/RemoveFromGroupMsg.java
new file mode 100644
index 00000000..4ee5fb56
--- /dev/null
+++ b/src/engine/net/client/msg/group/RemoveFromGroupMsg.java
@@ -0,0 +1,129 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg.group;
+
+
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.net.client.msg.ClientNetMsg;
+
+public class RemoveFromGroupMsg extends ClientNetMsg {
+
+	private int targetType;
+	private int targetID;
+	private int response;
+	private String message;
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public RemoveFromGroupMsg() {
+		super(Protocol.GROUPREMOVE);
+
+		this.message = "Quit if you want to remove yourself";
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public RemoveFromGroupMsg(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.GROUPREMOVE, origin, reader);
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied ByteBufferWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+		writer.putInt(this.targetType);
+		writer.putInt(this.targetID);
+		writer.putInt(this.response);
+		if (this.response == 1)
+			writer.putString(this.message);
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied ByteBufferReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		this.targetType = reader.getInt();
+		this.targetID = reader.getInt();
+		this.response = reader.getInt();
+		if (this.response == 1)
+			this.message = reader.getString();
+	}
+
+	/**
+	 * @return the targetType
+	 */
+	public int getTargetType() {
+		return targetType;
+	}
+
+	/**
+	 * @param targetType
+	 *            the targetType to set
+	 */
+	public void setTargetType(int targetType) {
+		this.targetType = targetType;
+	}
+
+	/**
+	 * @return the targetID
+	 */
+	public int getTargetID() {
+		return targetID;
+	}
+
+	/**
+	 * @param targetID
+	 *            the targetID to set
+	 */
+	public void setTargetID(int targetID) {
+		this.targetID = targetID;
+	}
+
+	/**
+	 * @return the response
+	 */
+	public int getResponse() {
+		return response;
+	}
+
+	/**
+	 * @param response
+	 *            the response to set
+	 */
+	public void setResponse(int response) {
+		this.response = response;
+	}
+
+	/**
+	 * @return the message
+	 */
+	public String getMessage() {
+		return message;
+	}
+
+	/**
+	 * @param message
+	 *            the message to set
+	 */
+	public void setMessage(String message) {
+		this.message = message;
+	}
+
+}
diff --git a/src/engine/net/client/msg/group/ToggleGroupSplitMsg.java b/src/engine/net/client/msg/group/ToggleGroupSplitMsg.java
new file mode 100644
index 00000000..c58aaa77
--- /dev/null
+++ b/src/engine/net/client/msg/group/ToggleGroupSplitMsg.java
@@ -0,0 +1,72 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg.group;
+
+
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.net.client.msg.ClientNetMsg;
+
+public class ToggleGroupSplitMsg extends ClientNetMsg {
+
+	private int unknown01;
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public ToggleGroupSplitMsg() {
+		super(Protocol.GROUPTREASURE);
+		this.unknown01 = 1;
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public ToggleGroupSplitMsg(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.GROUPTREASURE, origin, reader);
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied ByteBufferWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+		writer.putInt(this.unknown01);
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied ByteBufferReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		this.unknown01 = reader.getInt();
+	}
+
+	/**
+	 * @return the unknown01
+	 */
+	public int getUnknown01() {
+		return unknown01;
+	}
+
+	/**
+	 * @param unknown01
+	 *            the unknown01 to set
+	 */
+	public void setUnknown01(int unknown01) {
+		this.unknown01 = unknown01;
+	}
+
+}
diff --git a/src/engine/net/client/msg/guild/AcceptInviteToGuildMsg.java b/src/engine/net/client/msg/guild/AcceptInviteToGuildMsg.java
new file mode 100644
index 00000000..f9899e73
--- /dev/null
+++ b/src/engine/net/client/msg/guild/AcceptInviteToGuildMsg.java
@@ -0,0 +1,110 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg.guild;
+
+import engine.Enum.GameObjectType;
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.net.client.msg.ClientNetMsg;
+
+public class AcceptInviteToGuildMsg extends ClientNetMsg {
+
+    private int guildUUID;
+    private int unknown01;
+    private int unknown02;
+
+    /**
+     * This is the general purpose constructor.
+     */
+    public AcceptInviteToGuildMsg() {
+        super(Protocol.JOINGUILD);
+    }
+
+    public AcceptInviteToGuildMsg(int guildUUID, int unknown01, int unknown02) {
+        super(Protocol.JOINGUILD);
+        this.guildUUID = guildUUID;
+        this.unknown01 = unknown01;
+        this.unknown02 = unknown02;
+    }
+
+    /**
+     * This constructor is used by NetMsgFactory. It attempts to deserialize the
+     * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+     * past the limit) then this constructor Throws that Exception to the
+     * caller.
+     */
+    public AcceptInviteToGuildMsg(AbstractConnection origin, ByteBufferReader reader)
+             {
+        super(Protocol.JOINGUILD, origin, reader);
+    }
+
+    /**
+     * Serializes the subclass specific items to the supplied ByteBufferWriter.
+     */
+    @Override
+    protected void _serialize(ByteBufferWriter writer) {
+        writer.putInt(GameObjectType.Guild.ordinal());
+        writer.putInt(this.guildUUID);
+        writer.putInt(this.unknown01);
+        writer.putInt(this.unknown02);
+    }
+
+    /**
+     * Deserializes the subclass specific items from the supplied
+     * ByteBufferReader.
+     */
+    @Override
+    protected void _deserialize(ByteBufferReader reader)  {
+        reader.getInt(); // Object Type padding
+        this.guildUUID = reader.getInt();
+        this.unknown01 = reader.getInt();
+        this.unknown02 = reader.getInt();
+    }
+
+    /**
+     * @return the guildUUID
+     */
+    public int getGuildUUID() {
+        return guildUUID;
+    }
+
+    /**
+     * @return the unknown01
+     */
+    public int getUnknown01() {
+        return unknown01;
+    }
+
+    /**
+     * @param unknown01
+     * the unknown01 to set
+     */
+    public void setUnknown01(int unknown01) {
+        this.unknown01 = unknown01;
+    }
+
+    /**
+     * @return the unknown02
+     */
+    public int getUnknown02() {
+        return unknown02;
+    }
+
+    /**
+     * @param unknown02
+     * the unknown02 to set
+     */
+    public void setUnknown02(int unknown02) {
+        this.unknown02 = unknown02;
+    }
+
+}
diff --git a/src/engine/net/client/msg/guild/AcceptSubInviteMsg.java b/src/engine/net/client/msg/guild/AcceptSubInviteMsg.java
new file mode 100644
index 00000000..8e35ca6d
--- /dev/null
+++ b/src/engine/net/client/msg/guild/AcceptSubInviteMsg.java
@@ -0,0 +1,154 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+
+
+
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+
+
+
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+
+
+
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg.guild;
+
+import engine.Enum.GameObjectType;
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.net.client.msg.ClientNetMsg;
+
+public class AcceptSubInviteMsg extends ClientNetMsg {
+
+	private int guildUUID;
+	private int unknown01;
+	private int unknown02;
+	private String response;
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public AcceptSubInviteMsg() {
+		super(Protocol.JOINFORPROVINCE);
+	}
+
+	public AcceptSubInviteMsg(int guildUUID, int unknown01, int unknown02, String response) {
+		super(Protocol.JOINFORPROVINCE);
+		this.guildUUID = guildUUID;
+		this.unknown01 = unknown01;
+		this.unknown02 = unknown02;
+		this.response = response;
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the ByteBuffer into a message. If a BufferUnderflow occurs (based on reading past the limit) then this constructor Throws that Exception to the caller.
+	 */
+	public AcceptSubInviteMsg(AbstractConnection origin, ByteBufferReader reader)
+			 {
+		super(Protocol.JOINFORPROVINCE, origin, reader);
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied ByteBufferWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+                writer.putInt(GameObjectType.Guild.ordinal());
+		writer.putInt(this.guildUUID);
+		writer.putInt(this.unknown01);
+		writer.putInt(this.unknown02);
+		writer.putString(this.response);
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied ByteBufferReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		reader.getInt(); // Object Type Padding
+                this.guildUUID = reader.getInt();
+		this.unknown01 = reader.getInt();
+		this.unknown02 = reader.getInt();
+		reader.getString();
+	}
+
+	/**
+	 * @return the guildUUID
+	 */
+	public int guildUUID() {
+		return guildUUID;
+	}
+
+	/**
+	 * @return the unknown01
+	 */
+	public int getUnknown01() {
+		return unknown01;
+	}
+
+	/**
+	 * @param unknown01
+	 *            the unknown01 to set
+	 */
+	public void setUnknown01(int unknown01) {
+		this.unknown01 = unknown01;
+	}
+
+	/**
+	 * @return the unknown02
+	 */
+	public int getUnknown02() {
+		return unknown02;
+	}
+
+	/**
+	 * @param unknown02
+	 *            the unknown02 to set
+	 */
+	public void setUnknown02(int unknown02) {
+		this.unknown02 = unknown02;
+	}
+
+
+	public String getResponse() {
+		return this.response;
+	}
+
+	public void setResponse(String value) {
+		this.response = value;
+	}
+}
diff --git a/src/engine/net/client/msg/guild/BanishUnbanishMsg.java b/src/engine/net/client/msg/guild/BanishUnbanishMsg.java
new file mode 100644
index 00000000..af8722d3
--- /dev/null
+++ b/src/engine/net/client/msg/guild/BanishUnbanishMsg.java
@@ -0,0 +1,72 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg.guild;
+
+
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.net.client.msg.ClientNetMsg;
+
+public class BanishUnbanishMsg extends ClientNetMsg {
+
+	private int target;
+	private int msgType;
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public BanishUnbanishMsg() {
+		super(Protocol.BANISHMEMBER);
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the ByteBuffer into a message. If a BufferUnderflow occurs (based on reading past the limit) then this constructor Throws that Exception to the caller.
+	 */
+	public BanishUnbanishMsg(AbstractConnection origin, ByteBufferReader reader)
+			 {
+		super(Protocol.BANISHMEMBER, origin, reader);
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied ByteBufferWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied ByteBufferReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		reader.getInt();
+		target = reader.getInt();
+
+		//Unknows?
+		reader.getInt();
+        this.msgType = reader.getInt();
+        reader.getInt();
+	}
+
+	public int getTarget() {
+		return target;
+	}
+
+	public int getMsgType() {
+		return msgType;
+	}
+
+	public void setMsgType(int msgType) {
+		this.msgType = msgType;
+	}
+}
diff --git a/src/engine/net/client/msg/guild/BreakFealtyMsg.java b/src/engine/net/client/msg/guild/BreakFealtyMsg.java
new file mode 100644
index 00000000..ae4bac3e
--- /dev/null
+++ b/src/engine/net/client/msg/guild/BreakFealtyMsg.java
@@ -0,0 +1,61 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+
+package engine.net.client.msg.guild;
+
+
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.net.client.msg.ClientNetMsg;
+
+public class BreakFealtyMsg extends ClientNetMsg {
+
+	private int guildUUID;
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public BreakFealtyMsg() {
+		super(Protocol.BREAKFEALTY);
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the ByteBuffer into a message. If a BufferUnderflow occurs (based on reading past the limit) then this constructor Throws that Exception to the caller.
+	 */
+	public BreakFealtyMsg(AbstractConnection origin, ByteBufferReader reader)
+			 {
+		super(Protocol.BREAKFEALTY, origin, reader);
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied ByteBufferWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied ByteBufferReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+                        reader.getInt(); // Object Type Padding
+			this.guildUUID = reader.getInt();
+			reader.getInt();
+	}
+
+	public int getGuildUUID() {
+		return guildUUID;
+	}
+
+}
diff --git a/src/engine/net/client/msg/guild/ChangeRankMsg.java b/src/engine/net/client/msg/guild/ChangeRankMsg.java
new file mode 100644
index 00000000..05e14a4c
--- /dev/null
+++ b/src/engine/net/client/msg/guild/ChangeRankMsg.java
@@ -0,0 +1,163 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+
+
+
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+
+
+
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+
+
+
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+
+
+package engine.net.client.msg.guild;
+
+import engine.Enum.GameObjectType;
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.net.client.msg.ClientNetMsg;
+
+public class ChangeRankMsg extends ClientNetMsg {
+
+    private int playerUUID;
+    private int previousRank, newRank;
+    private byte ic, rec, tax;
+    private String errorMsg;
+
+    /**
+     * This is the general purpose constructor.
+     */
+    public ChangeRankMsg() {
+        super(Protocol.GUILDRANKCHANGE);
+    }
+
+    /**
+     * This constructor is used by NetMsgFactory. It attempts to deserialize the
+     * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+     * past the limit) then this constructor Throws that Exception to the
+     * caller.
+     */
+    public ChangeRankMsg(AbstractConnection origin, ByteBufferReader reader)
+             {
+        super(Protocol.GUILDRANKCHANGE, origin, reader);
+    }
+
+    public int getPlayerUUID() {
+        return playerUUID;
+    }
+
+    public int getPreviousRank() {
+        return previousRank;
+    }
+
+    public void setPreviousRank(int previousRank) {
+        this.previousRank = previousRank;
+    }
+
+    public int getNewRank() {
+        return newRank;
+    }
+
+    public void setNewRank(int newRank) {
+        this.newRank = newRank;
+    }
+
+    public byte getIc() {
+        return ic;
+    }
+
+    public void setIc(byte ic) {
+        this.ic = ic;
+    }
+
+    public byte getRec() {
+        return rec;
+    }
+
+    public void setRec(byte rec) {
+        this.rec = rec;
+    }
+
+    public byte getTax() {
+        return tax;
+    }
+
+    public void setTax(byte tax) {
+        this.tax = tax;
+    }
+
+    /**
+     * Serializes the subclass specific items to the supplied ByteBufferWriter.
+     */
+    @Override
+    protected void _serialize(ByteBufferWriter writer) {
+        writer.putInt(GameObjectType.PlayerCharacter.ordinal());
+        writer.putInt(this.playerUUID);
+        writer.putInt(this.newRank);
+        writer.putInt(this.previousRank);
+
+        writer.put(ic);
+        writer.put(tax);
+        writer.put(rec);
+
+        writer.putInt(0);
+
+        writer.putString(this.errorMsg);
+    }
+
+    /**
+     * Deserializes the subclass specific items from the supplied
+     * ByteBufferReader.
+     */
+    @Override
+    protected void _deserialize(ByteBufferReader reader)  {
+        reader.getInt(); // Object Type Padding
+        this.playerUUID = reader.getInt();
+        this.newRank = reader.getInt();
+        this.previousRank = reader.getInt();
+        this.ic = reader.get();
+        this.tax = reader.get();
+        this.rec = reader.get();
+
+        reader.getInt(); //Pad
+
+        this.errorMsg = reader.getString();
+    }
+}
diff --git a/src/engine/net/client/msg/guild/DisbandGuildMsg.java b/src/engine/net/client/msg/guild/DisbandGuildMsg.java
new file mode 100644
index 00000000..0ed97a3b
--- /dev/null
+++ b/src/engine/net/client/msg/guild/DisbandGuildMsg.java
@@ -0,0 +1,58 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg.guild;
+
+
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.net.client.msg.ClientNetMsg;
+
+public class DisbandGuildMsg extends ClientNetMsg {
+
+	private int response;
+	private String message;
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public DisbandGuildMsg() {
+		super(Protocol.DISBANDGUILD);
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the ByteBuffer into a message. If a BufferUnderflow occurs (based on reading past the limit) then this constructor Throws that Exception to the caller.
+	 */
+	public DisbandGuildMsg(AbstractConnection origin, ByteBufferReader reader)
+			 {
+		super(Protocol.DISBANDGUILD, origin, reader);
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied ByteBufferWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+		writer.putInt(this.response);
+		writer.putString(this.message);
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied ByteBufferReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		this.response = reader.getInt();
+		this.message = reader.getString();
+		//this.sourceID = reader.getInt();
+	}
+
+}
diff --git a/src/engine/net/client/msg/guild/DismissGuildMsg.java b/src/engine/net/client/msg/guild/DismissGuildMsg.java
new file mode 100644
index 00000000..1e0559ea
--- /dev/null
+++ b/src/engine/net/client/msg/guild/DismissGuildMsg.java
@@ -0,0 +1,78 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg.guild;
+
+
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.net.client.msg.ClientNetMsg;
+
+public class DismissGuildMsg extends ClientNetMsg {
+
+	private int unknown01;
+	private int guildType;
+	private int guildID;
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public DismissGuildMsg() {
+		super(Protocol.DISMISSGUILD);
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the ByteBuffer into a message. If a BufferUnderflow occurs (based on reading past the limit) then this constructor Throws that Exception to the caller.
+	 */
+	public DismissGuildMsg(AbstractConnection origin, ByteBufferReader reader)
+			 {
+		super(Protocol.DISMISSGUILD, origin, reader);
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied ByteBufferWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied ByteBufferReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		this.unknown01 = reader.getInt();
+		this.guildType = reader.getInt();
+		this.guildID = reader.getInt();
+		reader.getInt();
+		
+	}
+
+	public int getUnknown01() {
+		return this.unknown01;
+	}
+
+	
+
+	public void setUnknown01(int value) {
+		this.unknown01 = value;
+	}
+
+	public int getGuildID() {
+		return guildID;
+	}
+
+	public void setGuildID(int guildID) {
+		this.guildID = guildID;
+	}
+
+}
diff --git a/src/engine/net/client/msg/guild/GuildControlMsg.java b/src/engine/net/client/msg/guild/GuildControlMsg.java
new file mode 100644
index 00000000..39e286da
--- /dev/null
+++ b/src/engine/net/client/msg/guild/GuildControlMsg.java
@@ -0,0 +1,241 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg.guild;
+
+
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.net.client.msg.ClientNetMsg;
+
+public class GuildControlMsg extends ClientNetMsg {
+
+	private int unknown01;
+	private int unknown02;
+	private int unknown03;
+	private int unknown04;
+	private String message;
+	private byte unknown05;
+	private int unknown06;
+	private int unknown07;
+	private byte unknown08;
+
+	private byte isGM;
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public GuildControlMsg() {
+		super(Protocol.REQUESTMEMBERLIST);
+		this.unknown01 = 2;
+		this.unknown02 = 0;
+		this.unknown03 = 0;
+		this.unknown04 = 0;
+		this.message = "No error message";
+		this.unknown05 = 0;
+		this.unknown06 = 0;
+		this.unknown07 = 257;
+		this.unknown08 = (byte) 1;
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public GuildControlMsg(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.REQUESTMEMBERLIST, origin, reader);
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied ByteBufferWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+		writer.putInt(this.unknown01);
+		writer.putInt(this.unknown02);
+		writer.putInt(this.unknown03);
+		writer.putInt(this.unknown04);
+		writer.putString(this.message);
+
+		writer.put((byte) 1);	//Always 1
+
+		writer.put(isGM);	//Can be Tax Collector
+		writer.put(isGM);	//Can be Recruiter
+		writer.put(isGM);	//Can be IC
+		writer.put(isGM);
+
+		writer.put(isGM);	//Can be GM
+		writer.put((byte) 1);
+		writer.put((byte) 1);
+		writer.put((byte) 1);
+		writer.put((byte) 1);
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied ByteBufferReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		this.unknown01 = reader.getInt();
+		this.unknown02 = reader.getInt();
+		this.unknown03 = reader.getInt();
+		this.unknown04 = reader.getInt();
+		this.message = reader.getString();
+		this.unknown05 = reader.get();
+		if (this.unknown05 == (byte) 1) {
+			this.unknown06 = reader.getInt();
+			this.unknown07 = reader.getInt();
+			this.unknown08 = reader.get();
+		}
+	}
+
+	/**
+	 * @return the unknown01
+	 */
+	public int getUnknown01() {
+		return unknown01;
+	}
+
+	/**
+	 * @param unknown01
+	 *            the unknown01 to set
+	 */
+	public void setUnknown01(int unknown01) {
+		this.unknown01 = unknown01;
+	}
+
+	/**
+	 * @return the unknown02
+	 */
+	public int getUnknown02() {
+		return unknown02;
+	}
+
+	/**
+	 * @param unknown02
+	 *            the unknown02 to set
+	 */
+	public void setUnknown02(int unknown02) {
+		this.unknown02 = unknown02;
+	}
+
+	/**
+	 * @return the unknown03
+	 */
+	public int getUnknown03() {
+		return unknown03;
+	}
+
+	/**
+	 * @param unknown03
+	 *            the unknown03 to set
+	 */
+	public void setUnknown03(int unknown03) {
+		this.unknown03 = unknown03;
+	}
+
+	/**
+	 * @return the unknown04
+	 */
+	public int getUnknown04() {
+		return unknown04;
+	}
+
+	/**
+	 * @param unknown04
+	 *            the unknown04 to set
+	 */
+	public void setUnknown04(int unknown04) {
+		this.unknown04 = unknown04;
+	}
+
+	/**
+	 * @return the message
+	 */
+	public String getMessage() {
+		return message;
+	}
+
+	/**
+	 * @param message
+	 *            the message to set
+	 */
+	public void setMessage(String message) {
+		this.message = message;
+	}
+
+	/**
+	 * @return the unknown05
+	 */
+	public byte getUnknown05() {
+		return unknown05;
+	}
+
+	/**
+	 * @param unknown05
+	 *            the unknown05 to set
+	 */
+	public void setUnknown05(byte unknown05) {
+		this.unknown05 = unknown05;
+	}
+
+	/**
+	 * @return the unknown06
+	 */
+	public int getUnknown06() {
+		return unknown06;
+	}
+
+	/**
+	 * @param unknown06
+	 *            the unknown06 to set
+	 */
+	public void setUnknown06(int unknown06) {
+		this.unknown06 = unknown06;
+	}
+
+	/**
+	 * @return the unknown07
+	 */
+	public int getUnknown07() {
+		return unknown07;
+	}
+
+	/**
+	 * @param unknown07
+	 *            the unknown07 to set
+	 */
+	public void setUnknown07(int unknown07) {
+		this.unknown07 = unknown07;
+	}
+
+	/**
+	 * @return the unknown08
+	 */
+	public byte getUnknown08() {
+		return unknown08;
+	}
+
+	public void setGM(byte b) {
+		this.isGM = b;
+	}
+
+	/**
+	 * @param unknown08
+	 *            the unknown08 to set
+	 */
+	public void setUnknown08(byte unknown08) {
+		this.unknown08 = unknown08;
+	}
+
+}
diff --git a/src/engine/net/client/msg/guild/GuildCreationCloseMsg.java b/src/engine/net/client/msg/guild/GuildCreationCloseMsg.java
new file mode 100644
index 00000000..82b4e552
--- /dev/null
+++ b/src/engine/net/client/msg/guild/GuildCreationCloseMsg.java
@@ -0,0 +1,67 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg.guild;
+
+
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.net.client.msg.ClientNetMsg;
+import org.pmw.tinylog.Logger;
+
+public class GuildCreationCloseMsg extends ClientNetMsg {
+
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public GuildCreationCloseMsg() {
+		super(Protocol.CANCELGUILDCREATION);
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public GuildCreationCloseMsg(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.CANCELGUILDCREATION, origin, reader);
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+		writer.putInt(0);
+		writer.putInt(0);
+		writer.putInt(0);
+		writer.putInt(0);
+		
+
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		for(int j = 0; j < 4; j++) {
+			int i = reader.getInt();
+				Logger.info( j+ "->" + i);
+		}
+	}
+
+}
+
+
+
diff --git a/src/engine/net/client/msg/guild/GuildCreationFinalizeMsg.java b/src/engine/net/client/msg/guild/GuildCreationFinalizeMsg.java
new file mode 100644
index 00000000..a28c4c58
--- /dev/null
+++ b/src/engine/net/client/msg/guild/GuildCreationFinalizeMsg.java
@@ -0,0 +1,160 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg.guild;
+
+import engine.Enum.GameObjectType;
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.net.client.msg.ClientNetMsg;
+import engine.objects.GuildTag;
+import engine.objects.Item;
+import org.pmw.tinylog.Logger;
+
+public class GuildCreationFinalizeMsg extends ClientNetMsg {
+
+    private GuildTag guildTag;
+
+    private String guildName;
+    private String guildMotto;
+
+    private int unknown01; //Appears to be a pad
+    private int unknown02; //Appears to be a pad
+    private int unknown03; //Appears to be a pad
+
+    private int icVote;
+    private int membershipVote;
+
+    private int unknown04; //Always -1
+    private int unknown05; //Appears to be a pad
+    private int unknown06; //Appears to be a pad
+    private int subType; //Appears to be a pad
+    private int unknown08; //3 = close window, 4 = Failure to create Guild.
+    private boolean close = false;
+    private int charterUUID;
+
+    /**
+     * This is the general purpose constructor.
+     */
+    public GuildCreationFinalizeMsg() {
+        super(Protocol.CREATEPETITION);
+    }
+
+    public GuildCreationFinalizeMsg(boolean close) {
+        super(Protocol.CREATEPETITION);
+        this.close = close;
+    }
+
+    /**
+     * This constructor is used by NetMsgFactory. It attempts to deserialize the
+     * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+     * past the limit) then this constructor Throws that Exception to the
+     * caller.
+     */
+    public GuildCreationFinalizeMsg(AbstractConnection origin, ByteBufferReader reader)  {
+        super(Protocol.CREATEPETITION, origin, reader);
+    }
+
+    /**
+     * Serializes the subclass specific items to the supplied NetMsgWriter.
+     */
+    @Override
+    protected void _serialize(ByteBufferWriter writer) {
+        
+        GuildTag._serializeForDisplay(guildTag,writer);
+
+        writer.putString(this.guildName);
+        writer.putString(this.guildMotto);
+
+        writer.putInt(this.unknown01);
+        writer.putInt(this.unknown02);
+        writer.putInt(this.unknown03);
+
+        writer.putInt(this.membershipVote);
+        writer.putInt(this.icVote);
+
+        writer.putInt(this.unknown04);
+        writer.putInt(this.unknown05);
+
+        writer.putInt(this.unknown06);
+        writer.putInt(3);
+        writer.putInt(this.unknown08);
+        writer.putInt(GameObjectType.Item.ordinal());
+        writer.putInt(this.charterUUID);
+    }
+
+    /**
+     * Deserializes the subclass specific items from the supplied NetMsgReader.
+     */
+    @Override
+    protected void _deserialize(ByteBufferReader reader)  {
+        guildTag = new GuildTag(reader, true);
+
+        this.guildName = reader.getString();
+        this.guildMotto = reader.getString();
+
+        this.unknown01 = reader.getInt();
+        this.unknown02 = reader.getInt();
+        this.unknown03 = reader.getInt();
+
+        this.membershipVote = reader.getInt();
+        this.icVote = reader.getInt();
+
+        this.unknown04 = reader.getInt();
+        this.unknown05 = reader.getInt();
+
+        this.unknown06 = reader.getInt();
+        this.subType = reader.getInt();
+        this.unknown08 = reader.getInt();
+        reader.getInt(); // Object Type padding
+        this.charterUUID = reader.getInt();
+    }
+
+    public String getName() {
+        return this.guildName;
+    }
+
+    public String getMotto() {
+        return this.guildMotto;
+    }
+
+    public Item getCharter() {
+
+        Item charterObject;
+
+        charterObject = Item.getFromCache(this.charterUUID);
+
+        if (charterObject == null)
+            Logger.error( "Invalid charter object UUID: " + this.charterUUID);
+
+        return charterObject;
+    }
+
+    public GuildTag getGuildTag() {
+        return this.guildTag;
+    }
+
+    public int getMemberVoteFlag() {
+        
+        if (this.membershipVote != 0)
+            return 1;
+        
+        return 0;
+    }
+
+    public int getICVoteFlag() {
+        
+        if (this.icVote != 0)
+            return 1;
+        
+        return 0;
+    }
+}
diff --git a/src/engine/net/client/msg/guild/GuildCreationOptionsMsg.java b/src/engine/net/client/msg/guild/GuildCreationOptionsMsg.java
new file mode 100644
index 00000000..83574d94
--- /dev/null
+++ b/src/engine/net/client/msg/guild/GuildCreationOptionsMsg.java
@@ -0,0 +1,151 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg.guild;
+
+
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.net.client.msg.ClientNetMsg;
+import engine.objects.GuildTag;
+
+public class GuildCreationOptionsMsg extends ClientNetMsg {
+
+	private int screenType;
+	private ScreenType messageScreen;
+	private boolean close = false;
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public GuildCreationOptionsMsg() {
+		super(Protocol.CHECKUNIQUEGUILD);
+	}
+	public GuildCreationOptionsMsg(boolean close) {
+		super(Protocol.CHECKUNIQUEGUILD);
+		this.close = close;
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public GuildCreationOptionsMsg(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.CHECKUNIQUEGUILD, origin, reader);
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+		writer.putInt(this.screenType);
+		if (this.close){
+			writer.putInt(2);
+			writer.putInt(0);
+//			writer.putInt(0);
+			return;
+		}
+		if(this.messageScreen != null) {
+			this.messageScreen._serialize(writer);
+		} else {
+			writer.putInt(0);
+			writer.putInt(5);
+		}
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		this.screenType = reader.getInt();
+		if(this.screenType == 1) {
+			this.messageScreen = new Screen1();
+		} else if(this.screenType == 2){
+			this.messageScreen = new Screen2();
+		} else {
+			System.out.println("Attempting to Deserilaize: Message Type" + screenType);
+			int counter = 0;
+			do {
+			int i = reader.getInt();
+			System.out.println(counter++ + "->" + i);
+			} while (true);
+		}
+		this.messageScreen._deserialize(reader);
+	}
+
+	/**
+	 * @return the rulership
+	 */
+	public int getScreenType() {
+		return this.screenType;
+	}
+
+	/**
+	 * @param rulership
+	 *            the rulership to set
+	 */
+	public void setScreenType(int type) {
+		this.screenType = type;
+	}
+}
+
+abstract class ScreenType {
+	public abstract void _serialize(ByteBufferWriter writer);
+	public abstract void _deserialize(ByteBufferReader reader) ;
+}
+
+class Screen1 extends ScreenType{
+	private int unknown01;
+	private int unknown02;
+	private String name;
+	private String motto;
+
+	@Override
+	public void _serialize(ByteBufferWriter writer) {
+		writer.putInt(this.unknown01);
+		writer.putInt(this.unknown02);
+		writer.putString(this.name);
+	}
+	@Override
+	public void _deserialize(ByteBufferReader reader)
+			 {
+		this.unknown01 = reader.getInt();
+		this.unknown02 = reader.getInt();
+		this.name = reader.getString();
+		this.motto = reader.getString();
+	}
+}
+
+class Screen2 extends ScreenType{
+	private int unknown01;
+	private int unknown02;
+	private GuildTag gt;
+
+	@Override
+	public void _serialize(ByteBufferWriter writer) {
+		writer.putInt(this.unknown01);
+		writer.putInt(this.unknown02);
+		GuildTag._serializeForGuildCreation(this.gt,writer);
+	}
+	@Override
+	public void _deserialize(ByteBufferReader reader)
+			 {
+		this.unknown01 = reader.getInt();
+		this.unknown02 = reader.getInt();
+		this.gt = new GuildTag(reader, true);
+	}
+}
+
+
diff --git a/src/engine/net/client/msg/guild/GuildInfoMsg.java b/src/engine/net/client/msg/guild/GuildInfoMsg.java
new file mode 100644
index 00000000..f81f4f06
--- /dev/null
+++ b/src/engine/net/client/msg/guild/GuildInfoMsg.java
@@ -0,0 +1,453 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+
+
+
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg.guild;
+
+
+import engine.Enum.GameObjectType;
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.net.client.msg.ClientNetMsg;
+import engine.objects.*;
+import org.joda.time.DateTime;
+
+
+public class GuildInfoMsg extends ClientNetMsg {
+
+	private int msgType;
+	private int objectUUID;
+        private int objectType;
+	private Guild guild;
+	private AbstractGameObject ago;
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public GuildInfoMsg() {
+		super(Protocol.UPDATEGUILD);
+		this.msgType = 4;
+        this.objectType = 0;
+        this.objectUUID = 0;
+
+	}
+
+	public GuildInfoMsg(AbstractGameObject ago, Guild guild, int unknown01) {
+		super(Protocol.UPDATEGUILD);
+		this.msgType = unknown01;
+        this.objectType = ago.getObjectType().ordinal();
+        this.objectUUID = ago.getObjectUUID();
+		this.ago = ago;
+
+		this.guild = guild;
+	}
+
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public GuildInfoMsg(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.UPDATEGUILD, origin, reader);
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied ByteBufferWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+		writer.putInt(this.msgType);
+
+		if(this.msgType == 2) {
+			new GuildInfoMessageType2(this.ago, guild)._serialize(writer);
+		} else if(this.msgType == 5) {
+			new GuildInfoMessageType5(this.ago, guild)._serialize(writer);
+		}else if(this.msgType == 4){
+			new GuildInfoMessageType4(this.ago, guild)._serialize(writer);
+		} else {
+			writer.putLong(0);
+			writer.putInt(0);
+			writer.putInt(0);
+			writer.putInt(0);
+			writer.putInt(0);
+			writer.putInt(0);
+			writer.putInt(0);
+			writer.putInt(0);
+			writer.putInt(0);
+			writer.put((byte) 0);
+		}
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied ByteBufferReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		this.msgType = reader.getInt();
+        this.objectType = reader.getInt();
+        this.objectUUID = reader.getInt();
+        
+        if (this.msgType == 1)
+        	reader.getInt();
+		reader.getInt(); // PAdding
+		reader.getInt();
+		reader.getInt();
+		reader.getInt();
+		reader.getInt();
+		reader.getInt();
+		reader.getInt();
+		reader.get();
+		
+		//default guild tag deserializations.
+		if (this.msgType == 5){
+			reader.getInt();
+			reader.getInt();
+			reader.getInt();
+			reader.getInt();
+			reader.getInt();
+			
+			reader.getInt();
+			reader.getInt();
+			reader.getInt();
+			reader.getInt();
+			reader.getInt();
+			
+			reader.getInt();
+		}
+		
+		
+	}
+
+	/**
+	 * @return the unknown01
+	 */
+	public int getMsgType() {
+		return msgType;
+	}
+
+	public void setMsgType(int msgType) {
+		this.msgType = msgType;
+	}
+
+	/**
+	 * @return the objectUUID
+	 */
+	public int getObjectID() {
+		return objectUUID;
+	}
+
+	public int getObjectType() {
+		return objectType;
+	}
+
+	public void setObjectType(int objectType) {
+		this.objectType = objectType;
+	}
+}
+
+abstract class GuildInfoMessageType {
+	protected int objectType;
+	protected int objectID;
+	protected Guild g;
+	protected AbstractGameObject ago;
+
+	public GuildInfoMessageType(AbstractGameObject ago, Guild g) {
+		this.objectType = ago.getObjectType().ordinal();
+		this.objectID = ago.getObjectUUID();
+		this.ago = ago;
+		this.g = g;
+	}
+
+	abstract void _serialize(ByteBufferWriter writer);
+}
+
+class GuildInfoMessageType2 extends GuildInfoMessageType {
+
+	public GuildInfoMessageType2(AbstractGameObject ago, Guild g) {
+		super(ago, g);
+	}
+
+	@Override
+	void _serialize(ByteBufferWriter writer) {
+		writer.putInt(this.objectType);
+		writer.putInt(this.objectID);
+		writer.putInt(0);
+		writer.putInt(0);
+		writer.putInt(0);
+		writer.putInt(0);
+		writer.putInt(1);
+
+		Guild nation = null;
+		if (this.g != null) {
+                        writer.putInt(GameObjectType.Guild.ordinal());
+			writer.putInt(g.getObjectUUID());
+			writer.putString(g.getName());
+
+			if(this.objectType == GameObjectType.PlayerCharacter.ordinal()) {
+				PlayerCharacter pc = PlayerCharacter.getFromCache(this.objectID);
+
+				if(pc != null) {
+					writer.putInt(GuildStatusController.getRank(pc.getGuildStatus()));
+				}
+			} else {
+				writer.putInt(5);
+			}
+			GuildTag._serializeForDisplay(g.getGuildTag(),writer);
+			nation = this.g.getNation();
+		} else {
+			writer.putLong(0L);
+			writer.putString("");
+			GuildTag._serializeForDisplay(GuildTag.ERRANT,writer);
+		}
+
+		writer.putInt(1);
+		if (nation != null) {
+                        writer.putInt(GameObjectType.Guild.ordinal());
+			writer.putInt(nation.getObjectUUID());
+
+			City city = g.getOwnedCity();
+			if (city != null) {
+				writer.putString(city.getCityName());
+				writer.putInt(city.getObjectType().ordinal());
+				writer.putInt(city.getObjectUUID());
+			} else {
+				writer.putString(""); //city name
+				writer.putLong(0L); //should be city ID
+			}
+
+			GuildTag._serializeForDisplay(nation.getGuildTag(),writer);
+
+		} else {
+			writer.putLong(0L);
+			writer.putString("");
+			writer.putLong(0L);
+			GuildTag._serializeForDisplay(GuildTag.ERRANT,writer);
+		}
+		writer.putInt(0);
+	 	
+		writer.putInt(0);
+		writer.put((byte)0);
+	}
+}
+
+class GuildInfoMessageType4 extends GuildInfoMessageType {
+
+	public GuildInfoMessageType4(AbstractGameObject ago, Guild g) {
+		super(ago, g);
+	}
+
+	@Override
+	void _serialize(ByteBufferWriter writer) {
+		String cityName = "";
+		writer.putInt(this.objectType);
+		writer.putInt(this.objectID);
+		writer.putInt(0);
+		writer.putInt(0);
+		writer.putInt(0);
+		writer.putInt(0);
+		PlayerCharacter pc = PlayerCharacter.getFromCache(this.objectID);
+		if (this.g == null || pc == null){
+			writer.putInt(0);
+			writer.putInt(0);
+			writer.putInt(0);
+			writer.putInt(0);
+			writer.put((byte)0);
+			return;
+		}
+
+		writer.putInt(1);
+		Guild nation = this.g.getNation();
+		writer.putInt(0);
+		writer.putInt(0);
+		writer.putString(g.getName());
+		writer.putInt(0);
+		GuildTag._serializeForDisplay(g.getGuildTag(),writer);
+		writer.putInt(1);
+		writer.putInt(0);
+		writer.putInt(0);
+		City city = g.getOwnedCity();
+		if (city != null)
+			cityName = city.getCityName();
+		writer.putString(nation.getName());
+		writer.putInt(0);
+		writer.putInt(0);
+
+		GuildTag._serializeForDisplay(nation.getGuildTag(),writer);
+		writer.putInt(1);
+
+		writer.putString(g.getName());
+		writer.putString(g.getMotto());
+		writer.putString(nation.getName());
+		writer.putInt(GuildStatusController.getRank(pc.getGuildStatus()));
+		writer.putInt(GuildStatusController.getTitle(pc.getGuildStatus()));
+		writer.putInt(g.getCharter());
+		writer.putString(cityName); //Shows City Name FUCK
+		AbstractCharacter guildLeader;
+		String guildLeaderName = "";
+		if (g.isNPCGuild()){
+			guildLeader = NPC.getFromCache(g.getGuildLeaderUUID());
+			if (guildLeader != null)
+				guildLeaderName = guildLeader.getName();
+		}
+			
+		else{
+			 guildLeader = PlayerCharacter.getFromCache(g.getGuildLeaderUUID());
+			 if (guildLeader != null)
+				 guildLeaderName = ((PlayerCharacter)guildLeader).getCombinedName();
+		}
+		
+			writer.putString(guildLeaderName);
+		writer.putString(pc.getName());
+	
+		writer.putDateTime(DateTime.now());
+		writer.put((byte)1);
+		writer.put((byte)1);
+		writer.put((byte)1);
+		writer.put((byte)0);
+		writer.putInt(0);
+
+
+
+
+
+
+		//	writer.put((byte)0);
+
+
+		//		writer.putString(cityName);
+		//		writer.putInt(10);
+		//		writer.putInt(6);
+		//		writer.putInt(10);
+		//		writer.putString(cityName);
+		//		writer.putString(pc.getFirstName());
+		//		writer.putString("Nation Leader");
+		//		writer.putDateTime(DateTime.now());
+		//		writer.put((byte)1);
+		//		writer.put((byte)1);
+		//		writer.putInt(1);
+		//		writer.putInt(this.objectType);
+		//		writer.putInt(this.objectID);
+		//		writer.putInt(0);
+		//		writer.putInt(0);
+
+
+	}
+}
+
+class GuildInfoMessageType5 extends GuildInfoMessageType {
+
+	public GuildInfoMessageType5(AbstractGameObject ago, Guild g) {
+		super(ago, g);
+	}
+
+	@Override
+	void _serialize(ByteBufferWriter writer) {
+
+		PlayerCharacter pc = null;
+
+		if(ago.getObjectType().equals(GameObjectType.PlayerCharacter)) {
+			pc = (PlayerCharacter) ago;
+		}
+
+		if(pc != null && g != null && g.getObjectUUID() != 0) {
+			Guild n = g.getNation();
+			if(n == null) {
+				n = Guild.getErrantNation();
+			}
+			
+			writer.putInt(ago.getObjectType().ordinal());
+			writer.putInt(ago.getObjectUUID());
+
+			writer.putInt(0);
+			writer.putInt(0);
+			writer.putInt(0);
+			writer.putInt(0);
+
+			writer.putInt(1);
+			writer.putInt(0);
+			writer.putInt(0);
+
+			writer.putString(g.getName());	//No Effect?
+			writer.putInt(0);	//Pad
+
+			GuildTag._serializeForDisplay(g.getGuildTag(),writer);	//Also a waste of space...
+
+			writer.putInt(1);
+			writer.putInt(0);
+			writer.putInt(0);
+			writer.putString(n.getName());	//No Effect?
+			writer.putInt(0);
+			writer.putInt(0);
+
+			GuildTag._serializeForDisplay(n.getGuildTag(),writer);
+
+			writer.putInt(1);
+
+			writer.putString(g.getName());	//Guild Name
+			writer.putString(g.getMotto());	//TODO Motto
+			writer.putString(n.getName());	//Nation Name
+
+			writer.putInt(GuildStatusController.getRank(pc.getGuildStatus()));	//Rank
+			writer.putInt(GuildStatusController.getTitle(pc.getGuildStatus()));	//Title
+			writer.putInt(g.getCharter());
+
+			if(g.getNation().equals(Guild.getErrantNation()))
+				writer.putString("Errant");
+			else
+				writer.putString("City");
+			writer.putString("Guild Leader");
+			writer.putString("");	//Nation Leader - I believe
+
+			DateTime now = DateTime.now();
+			writer.putDateTime(now);
+
+			writer.put((byte) 1);
+			writer.put((byte) 1);
+			writer.put((byte) 0);
+			writer.put((byte) 0);
+
+			GuildTag._serializeForDisplay(g.getGuildTag(),writer);
+			GuildTag._serializeForDisplay(g.getNation().getGuildTag(),writer);
+			writer.putInt(0);
+		} else {
+			writer.putLong(0);
+
+			writer.putInt(0);
+			writer.putInt(0);
+			writer.putInt(0);
+			writer.putInt(0);
+
+			writer.putInt(0);
+			writer.putInt(0);
+			writer.putInt(0);
+
+			writer.put((byte) 0);
+
+			GuildTag._serializeForDisplay(GuildTag.ERRANT,writer);
+			GuildTag._serializeForDisplay(GuildTag.ERRANT,writer);
+
+			writer.putInt(0);
+		}
+	}
+}
diff --git a/src/engine/net/client/msg/guild/GuildListMsg.java b/src/engine/net/client/msg/guild/GuildListMsg.java
new file mode 100644
index 00000000..17404166
--- /dev/null
+++ b/src/engine/net/client/msg/guild/GuildListMsg.java
@@ -0,0 +1,228 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg.guild;
+
+import engine.Enum;
+import engine.Enum.GameObjectType;
+import engine.gameManager.SessionManager;
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.net.client.msg.ClientNetMsg;
+import engine.objects.Guild;
+import engine.objects.GuildHistory;
+import engine.objects.GuildStatusController;
+import engine.objects.PlayerCharacter;
+
+import java.util.ArrayList;
+
+
+public class GuildListMsg extends ClientNetMsg {
+
+	private GuildListMessageType glm;
+
+	/**
+	 * Type 1 Constructor	- Guild Roster
+	 */
+	public GuildListMsg(Guild g) {
+		super(Protocol.SENDMEMBERENTRY);
+		this.glm = new GuildListMessageType1(g);
+	}
+
+	/**
+	 * Type 4 Constructor  - Guild History
+	 */
+	public GuildListMsg(PlayerCharacter pc) {
+		super(Protocol.SENDMEMBERENTRY);
+		this.glm = new GuildListMessageType2(pc);
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public GuildListMsg(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.SENDMEMBERENTRY, origin, reader);
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied ByteBufferWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+
+		//TODO Find Default and null check this
+		this.glm._serialize(writer);
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied ByteBufferReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		/*
+		 *
+		 * The Server should never receive this message directly.
+		 * Instances in recordings will need to be decoded by hand,
+		 * converting from our format to the standard format will
+		 * cause more problems than its worth to fix.
+		 *
+		this.unknown01 = reader.getInt();
+		this.unknown02 = reader.getInt();
+		this.unknown03 = reader.get();
+		this.unknown04 = reader.getInt();
+		this.unknown05 = reader.get();
+		this.unknown06 = reader.getInt();
+		int size = reader.getInt();
+		for (int i = 0; i < size; i++) {
+			GuildTableList gt = new GuildTableList();
+			reader.getInt(); // Player Character ID Type
+			gt.setUUID(reader.getInt());
+			gt.setName(reader.getString());
+			gt.setActionType(reader.get());
+			gt.setGuildTitle(reader.getInt());
+			gt.setGuildRank(reader.getInt());
+			gt.setUnknown02(reader.getInt());
+			gt.setUnknown03(reader.getInt());
+			gt.setUnknown04(reader.getInt());
+			gt.setUnknown05(reader.getInt());
+			gt.setUnknown06(reader.getInt());
+			gt.setUnknown07(reader.getInt());
+		}
+		 */
+	}
+
+	@Override
+	protected int getPowerOfTwoBufferSize() {
+		// Larger size for historically larger opcodes
+		return 15;
+	}
+}
+
+abstract class GuildListMessageType {
+	public ArrayList<Guild> history = new ArrayList<>();
+
+	abstract void _serialize(ByteBufferWriter writer);
+}
+
+class GuildListMessageType1 extends GuildListMessageType {
+
+	private Guild g;
+
+	public GuildListMessageType1(Guild g) {
+		this.g = g;
+	}
+
+	@Override
+	void _serialize(ByteBufferWriter writer) {
+		Enum.GuildType gt = Enum.GuildType.getGuildTypeFromInt(g.getCharter());
+
+		writer.putInt(1);
+		writer.putInt(gt.ordinal());	//Charter Type
+		writer.putInt(1);	//Always 1?
+		writer.put((byte) 1);
+		writer.put((byte) 0);
+		writer.putInt(gt.getNumberOfRanks());	//Number of Ranks
+		
+		
+		ArrayList<PlayerCharacter> guildRoster = Guild.GuildRoster(g);
+		writer.putInt(guildRoster.size() + g.getBanishList().size());
+
+		// Send guild list of each player
+		for (PlayerCharacter player : guildRoster) {
+			
+			byte isActive = SessionManager.getPlayerCharacterByID(player.getObjectUUID()) != null ? (byte)1 : (byte)0;
+			writer.putInt(Enum.GameObjectType.PlayerCharacter.ordinal());
+			writer.putInt(player.getObjectUUID());
+			writer.putString(player.getCombinedName());
+			writer.put(isActive);	//Active?
+			writer.putInt(GuildStatusController.getTitle(player.getGuildStatus()));
+			writer.putInt(GuildStatusController.getRank(player.getGuildStatus()));
+			writer.putInt(0); // 1/2 has no effect
+			writer.putInt(0); // 1 has no effect
+			writer.putInt(0); // 1 has no effect
+			writer.putInt(0); // 1 has no effect
+			writer.putInt(GuildStatusController.getRank(player.getGuildStatus()));
+			writer.putInt(0); // window failed to open when set to 1
+		}
+		
+		for (PlayerCharacter banished : g.getBanishList()){
+			byte isActive = SessionManager.getPlayerCharacterByID(banished.getObjectUUID()) != null ? (byte)1 : (byte)0;
+			writer.putInt(Enum.GameObjectType.PlayerCharacter.ordinal());
+			writer.putInt(banished.getObjectUUID());
+			writer.putString(banished.getCombinedName());
+			writer.put(isActive);	//Active?
+			writer.putInt(GuildStatusController.getTitle(banished.getGuildStatus()));
+			writer.putInt(3);
+			writer.putInt(0); // 1/2 has no effect
+			writer.putInt(0); // 1 has no effect
+			writer.putInt(0); // 1 has no effect
+			writer.putInt(0); // 1 has no effect
+			writer.putInt(3);
+			writer.putInt(0); // window failed to open when set to 1
+		}
+	}
+}
+
+class GuildListMessageType2 extends GuildListMessageType {
+
+	private PlayerCharacter pc;
+
+	public GuildListMessageType2(PlayerCharacter pc) {
+		this.pc = pc;
+	}
+
+	@Override
+	void _serialize(ByteBufferWriter writer) {
+
+		Guild g = pc.getGuild();
+
+		writer.putInt(4);
+
+		writer.putInt(0);
+		writer.putInt(0);
+		writer.putInt(0);
+
+		writer.put((byte) 0);
+		writer.put((byte) 0);
+		writer.putInt(1);
+		writer.putInt(pc.getObjectType().ordinal());
+		writer.putInt(pc.getObjectUUID());
+		writer.putString(pc.getCombinedName());
+
+		writer.put((byte) 1);
+		writer.putInt(GuildStatusController.getTitle(pc.getGuildStatus()));	//Title Maybe?
+		writer.putInt(GuildStatusController.getRank(pc.getGuildStatus()));	//Rank?
+
+		writer.putInt(pc.getRaceToken());	//race token
+		writer.putInt(pc.getBaseClassToken());	//class token
+
+		writer.putInt(2);	//PAD
+		writer.putInt(pc.getLevel());
+		writer.putInt(g.getCharter());
+
+		//TODO Get Guild History from the DB
+		//ArrayList<GuildHistory> history = DbManager.GuildQueries.GET_GUILD_HISTORY_OF_PLAYER((int)pc.getPlayerUUID());
+		writer.putInt(pc.getGuildHistory().size());	//Number of Entries
+
+		for(GuildHistory guildHistory : pc.getGuildHistory()) {
+			writer.putInt(guildHistory.getHistoryType().getType());
+			writer.putInt(GameObjectType.Guild.ordinal());
+			writer.putInt((int) guildHistory.getGuildID());
+			writer.putString(guildHistory.getGuildName());
+			writer.putInt(0);
+			writer.putDateTime(guildHistory.getTime());
+		}
+	}
+
+}
diff --git a/src/engine/net/client/msg/guild/GuildUnknownMsg.java b/src/engine/net/client/msg/guild/GuildUnknownMsg.java
new file mode 100644
index 00000000..6eb32778
--- /dev/null
+++ b/src/engine/net/client/msg/guild/GuildUnknownMsg.java
@@ -0,0 +1,67 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg.guild;
+
+
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.net.client.msg.ClientNetMsg;
+
+public class GuildUnknownMsg extends ClientNetMsg {
+
+	private int unknown01;
+	private int unknown02;
+	private int unknown03;
+	private byte unknownByte;
+
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public GuildUnknownMsg() {
+		super(Protocol.UPDATECLIENTALLIANCES);
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the ByteBuffer into a message. If a BufferUnderflow occurs (based on reading past the limit) then this constructor Throws that Exception to the caller.
+	 */
+	public GuildUnknownMsg(AbstractConnection origin, ByteBufferReader reader)
+			 {
+		super(Protocol.UPDATECLIENTALLIANCES, origin, reader);
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied ByteBufferWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+		writer.putInt(0);
+		writer.putInt(0);
+		writer.putInt(0);
+		writer.put((byte)1);
+//		writer.putInt(this.unknown01);
+//		writer.putInt(this.unknown02);
+//		writer.putInt(this.unknown02);
+//		writer.putInt(this.unknownByte);
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied ByteBufferReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		this.unknown01 = reader.getInt();
+		this.unknown02 = reader.getInt();
+		this.unknown02 = reader.getInt();
+		this.unknownByte = reader.get();
+	}
+}
diff --git a/src/engine/net/client/msg/guild/InviteToGuildMsg.java b/src/engine/net/client/msg/guild/InviteToGuildMsg.java
new file mode 100644
index 00000000..a5692ac4
--- /dev/null
+++ b/src/engine/net/client/msg/guild/InviteToGuildMsg.java
@@ -0,0 +1,196 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg.guild;
+
+
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.net.client.msg.ClientNetMsg;
+import engine.objects.GuildTag;
+
+public class InviteToGuildMsg extends ClientNetMsg {
+
+	private int response;
+	private int sourceType;
+	private int sourceUUID;
+	private int targetType;
+	private int targetUUID;
+	private int unknown01;
+	private String message;
+	private GuildTag gt;
+	private String guildName;
+	private int guildType;
+	private int guildID;
+	private String targetName;
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public InviteToGuildMsg() {
+		super(Protocol.INVITETOGUILD);
+		this.response = 0;
+		this.message = "No error message";
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public InviteToGuildMsg(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.INVITETOGUILD, origin, reader);
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied ByteBufferWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+		writer.putInt(this.response);
+		writer.putLong(this.sourceUUID);
+		writer.putLong(this.targetUUID);
+		writer.putInt(this.unknown01);
+		writer.putString(this.message);
+		writer.putInt(this.gt.backgroundColor01);
+		writer.putInt(this.gt.backgroundColor02);
+		writer.putInt(this.gt.symbolColor);
+		writer.putInt(this.gt.symbol);
+		writer.putInt(this.gt.backgroundDesign);
+		writer.putString(this.guildName);
+		writer.putInt(this.guildType);
+		writer.putInt(this.guildID);
+		writer.putString(this.targetName);
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied ByteBufferReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		this.response = reader.getInt();
+		this.sourceType = reader.getInt();
+		this.sourceUUID = reader.getInt();
+		this.targetType = reader.getInt();
+		this.targetUUID = reader.getInt();
+		this.unknown01 = reader.getInt();
+		this.message = reader.getString();
+		this.gt = new GuildTag(reader);
+		this.guildName = reader.getString();
+		this.guildType = reader.getInt();
+		this.guildID = reader.getInt();
+		this.targetName = reader.getString();
+	}
+
+	public GuildTag getGuildTag() {
+		return this.gt;
+	}
+
+	public void setGuildTag(GuildTag gt) {
+		this.gt = gt;
+	}
+
+	/**
+	 * @return the response
+	 */
+	public int getResponse() {
+		return response;
+	}
+
+	/**
+	 * @param response
+	 *            the response to set
+	 */
+	public void setResponse(int response) {
+		this.response = response;
+	}
+
+	/**
+	 * @return the sourceUUID
+	 */
+	public int getSourceUUID() {
+		return sourceUUID;
+	}
+
+	/**
+	 * @param sourceUUID
+	 *            the sourceUUID to set
+	 */
+	public void setSourceUUID(int sourceUUID) {
+		this.sourceUUID = sourceUUID;
+	}
+
+	/**
+	 * @return the targetUUID
+	 */
+	public int getTargetUUID() {
+		return targetUUID;
+	}
+
+	/**
+	 * @param targetUUID
+	 *            the targetUUID to set
+	 */
+	public void setTargetUUID(int targetUUID) {
+		this.targetUUID = targetUUID;
+	}
+
+	/**
+	 * @param guildName
+	 *            the guildName to set
+	 */
+	public void setGuildName(String guildName) {
+		this.guildName = guildName;
+	}
+
+	/**
+	 * @param guildID
+	 *            the guildID to set
+	 */
+	public void setGuildUUID(int guildID) {
+		this.guildID = guildID;
+	}
+
+	/**
+	 * @return the targetName
+	 */
+	public String getTargetName() {
+		return targetName;
+	}
+
+	/**
+	 * @param targetName
+	 *            the targetName to set
+	 */
+	public void setTargetName(String targetName) {
+		this.targetName = targetName;
+	}
+
+	public void setSourceType(int sourceType) {
+		this.sourceType = sourceType;
+	}
+
+	public void setTargetType(int targetType) {
+		this.targetType = targetType;
+	}
+
+	public void setGuildType(int guildType) {
+		this.guildType = guildType;
+	}
+
+    /**
+     * @return the targetType
+     */
+    public int getTargetType() {
+        return targetType;
+    }
+}
diff --git a/src/engine/net/client/msg/guild/InviteToSubMsg.java b/src/engine/net/client/msg/guild/InviteToSubMsg.java
new file mode 100644
index 00000000..3784d696
--- /dev/null
+++ b/src/engine/net/client/msg/guild/InviteToSubMsg.java
@@ -0,0 +1,201 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg.guild;
+
+import engine.Enum.GameObjectType;
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.net.client.msg.ClientNetMsg;
+import engine.objects.GuildTag;
+
+public class InviteToSubMsg extends ClientNetMsg {
+
+	//12d67402 0000f062 56253600 0010b062 d7821800 00000000
+	//10000000 4e006f0020006500720072006f00720020006d00650073007300610067006500
+	//0a000000 03000000 07000000 a0000000 04000000
+	//17000000 4800650072006f006500730020006f0066002000530065006100200044006f006700730020005200650073007400
+	//0000200a 33000000 00000000 01000000
+
+	//12d67402 0000d062 11fe1700 00009063 3ee91300 00000000
+	//10000000 4e006f0020006500720072006f00720020006d00650073007300610067006500
+	//08000000 08000000 07000000 96000000 0d000000
+	//11000000 42006c006f006f00640020004d006f006f006e00200052006900730069006e006700
+	//0000c006 64540000 01000000 00000000
+
+	//12d67402 0000e062 db2d0000 00005063 116e1100 00000000
+	//10000000 4e006f0020006500720072006f00720020006d00650073007300610067006500
+	//05000000 11000000 0e000000 95000000 05000000
+	//09000000 530074006f0072006d0068006f006c006400
+	//0000c006 56300000 01000000 00000000
+
+	private int sourceUUID;
+	private int targetUUID;
+	private int unknown01;
+	private String message;
+	private GuildTag gt;
+	private String guildName;
+	private int guildUUID;
+	private int unknown02;
+	private int unknown03;
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public InviteToSubMsg() {
+		super(Protocol.INVITEGUILDFEALTY);
+		this.message = "No error message";
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public InviteToSubMsg(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.INVITEGUILDFEALTY, origin, reader);
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied ByteBufferWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+                writer.putInt(GameObjectType.PlayerCharacter.ordinal());
+		writer.putInt(this.sourceUUID);
+                writer.putInt(GameObjectType.PlayerCharacter.ordinal());
+		writer.putInt(this.targetUUID);
+		writer.putInt(this.unknown01);
+		writer.putString(this.message);
+		writer.putInt(this.gt.backgroundColor01);
+		writer.putInt(this.gt.backgroundColor02);
+		writer.putInt(this.gt.symbolColor);
+		writer.putInt(this.gt.symbol);
+		writer.putInt(this.gt.backgroundDesign);
+		writer.putString(this.guildName);
+		writer.putInt(GameObjectType.Guild.ordinal());
+		writer.putInt(this.guildUUID);
+		writer.putInt(this.unknown02);
+		writer.putInt(this.unknown03);
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied ByteBufferReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		reader.getInt();
+                this.sourceUUID = reader.getInt();
+                reader.getInt();
+		this.targetUUID = reader.getInt();
+		this.unknown01 = reader.getInt();
+		this.message = reader.getString();
+		this.gt = new GuildTag(reader);
+		this.guildName = reader.getString();
+		reader.getInt(); // Padding for Object Type
+		this.guildUUID = reader.getInt();
+		this.unknown02 = reader.getInt();
+		this.unknown03 = reader.getInt();
+	}
+
+	public GuildTag getGuildTag() {
+		return this.gt;
+	}
+
+	public void setGuildTag(GuildTag gt) {
+		this.gt = gt;
+	}
+
+	/**
+	 * @return the sourceUUID
+	 */
+	public int getSourceUUID() {
+		return sourceUUID;
+	}
+
+	/**
+	 * @return the targetUUID
+	 */
+	public int getTargetUUID() {
+		return targetUUID;
+	}
+
+	/**
+	 * @return the unknown01
+	 */
+	public int getUnknown01() {
+		return unknown01;
+	}
+
+	/**
+	 * @param unknown01
+	 *            the unknown01 to set
+	 */
+	public void setUnknown01(int unknown01) {
+		this.unknown01 = unknown01;
+	}
+
+	/**
+	 * @return the message
+	 */
+	public String getMessage() {
+		return message;
+	}
+
+	/**
+	 * @param message
+	 *            the message to set
+	 */
+	public void setMessage(String message) {
+		this.message = message;
+	}
+
+	/**
+	 * @return the guildName
+	 */
+	public String getGuildName() {
+		return guildName;
+	}
+
+	/**
+	 * @param guildName
+	 *            the guildName to set
+	 */
+	public void setGuildName(String guildName) {
+		this.guildName = guildName;
+	}
+
+
+	public int getUnknown02() {
+		return unknown02;
+	}
+
+	public void setUnknown02(int unknown02) {
+		this.unknown02 = unknown02;
+	}
+
+	public int getUnknown03() {
+		return unknown03;
+	}
+
+	public void setUnknown03(int unknown03) {
+		this.unknown03 = unknown03;
+	}
+
+	public int getGuildUUID() {
+		return guildUUID;
+	}
+
+	public void setGuildUUID(int guildUUID) {
+		this.guildUUID = guildUUID;
+	}
+}
diff --git a/src/engine/net/client/msg/guild/LeaveGuildMsg.java b/src/engine/net/client/msg/guild/LeaveGuildMsg.java
new file mode 100644
index 00000000..8ee24ce3
--- /dev/null
+++ b/src/engine/net/client/msg/guild/LeaveGuildMsg.java
@@ -0,0 +1,105 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg.guild;
+
+
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.net.client.msg.ClientNetMsg;
+
+public class LeaveGuildMsg extends ClientNetMsg {
+
+	private int response;
+	private String message;
+	private long sourceID;
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public LeaveGuildMsg() {
+		super(Protocol.LEAVEGUILD);
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the ByteBuffer into a message. If a BufferUnderflow occurs (based on reading past the limit) then this constructor Throws that Exception to the caller.
+	 */
+	public LeaveGuildMsg(AbstractConnection origin, ByteBufferReader reader)
+			 {
+		super(Protocol.LEAVEGUILD, origin, reader);
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied ByteBufferWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+		writer.putInt(this.response);
+		writer.putString(this.message);
+		writer.putLong(this.sourceID);
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied ByteBufferReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		this.response = reader.getInt();
+		this.message = reader.getString();
+		this.sourceID = reader.getLong();
+	}
+
+	/**
+	 * @return the response
+	 */
+	public int getResponse() {
+		return response;
+	}
+
+	/**
+	 * @param response
+	 *            the response to set
+	 */
+	public void setResponse(int response) {
+		this.response = response;
+	}
+
+	/**
+	 * @return the message
+	 */
+	public String getMessage() {
+		return message;
+	}
+
+	/**
+	 * @param message
+	 *            the message to set
+	 */
+	public void setMessage(String message) {
+		this.message = message;
+	}
+
+	/**
+	 * @return the sourceID
+	 */
+	public long getSourceID() {
+		return sourceID;
+	}
+
+	/**
+	 * @param sourceID
+	 *            the sourceID to set
+	 */
+	public void setSourceID(long sourceID) {
+		this.sourceID = sourceID;
+	}
+
+}
diff --git a/src/engine/net/client/msg/guild/MOTDCommitMsg.java b/src/engine/net/client/msg/guild/MOTDCommitMsg.java
new file mode 100644
index 00000000..58b70016
--- /dev/null
+++ b/src/engine/net/client/msg/guild/MOTDCommitMsg.java
@@ -0,0 +1,110 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg.guild;
+
+
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.net.client.msg.ClientNetMsg;
+
+public class MOTDCommitMsg extends ClientNetMsg {
+
+	private String message;
+	private int unknown01;
+	private int type;
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public MOTDCommitMsg() {
+		super(Protocol.SETMOTD);
+		this.message = "";
+		this.unknown01 = 0;
+		this.type = 0;
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public MOTDCommitMsg(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.SETMOTD, origin, reader);
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied ByteBufferWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+		writer.putString(this.message);
+		writer.putInt(this.unknown01);
+		writer.putInt(this.type);
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied ByteBufferReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		this.message = reader.getString();
+		this.unknown01 = reader.getInt();
+		this.type = reader.getInt();
+	}
+
+	/**
+	 * @return the message
+	 */
+	public String getMessage() {
+		return message;
+	}
+
+	/**
+	 * @param message
+	 *            the message to set
+	 */
+	public void setMessage(String message) {
+		this.message = message;
+	}
+
+	/**
+	 * @return the unknown01
+	 */
+	public int getUnknown01() {
+		return unknown01;
+	}
+
+	/**
+	 * @param unknown01
+	 *            the unknown01 to set
+	 */
+	public void setUnknown01(int unknown01) {
+		this.unknown01 = unknown01;
+	}
+
+	/**
+	 * @return the type
+	 */
+	public int getType() {
+		return type;
+	}
+
+	/**
+	 * @param type
+	 *            the type to set
+	 */
+	public void setType(int type) {
+		this.type = type;
+	}
+
+}
diff --git a/src/engine/net/client/msg/guild/MOTDMsg.java b/src/engine/net/client/msg/guild/MOTDMsg.java
new file mode 100644
index 00000000..ec33b534
--- /dev/null
+++ b/src/engine/net/client/msg/guild/MOTDMsg.java
@@ -0,0 +1,133 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg.guild;
+
+
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.net.client.msg.ClientNetMsg;
+
+public class MOTDMsg extends ClientNetMsg {
+
+	private int type;
+	private byte response;
+	private int unknown01;
+	private String message;
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public MOTDMsg() {
+		super(Protocol.MOTD);
+		this.type = 0;
+		this.response = (byte) 0;
+		this.unknown01 = 0;
+		this.message = "";
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public MOTDMsg(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.MOTD, origin, reader);
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied ByteBufferWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+		writer.putInt(this.type);
+		writer.put(this.response);
+		if (this.response == (byte) 1) {
+			writer.putInt(this.unknown01);
+			writer.putString(this.message);
+		}
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied ByteBufferReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		this.type = reader.getInt();
+		this.response = reader.get();
+		if (this.response == (byte) 1) {
+			this.unknown01 = reader.getInt();
+			this.message = reader.getString();
+		}
+	}
+
+	/**
+	 * @return the type
+	 */
+	public int getType() {
+		return type;
+	}
+
+	/**
+	 * @param type
+	 *            the type to set
+	 */
+	public void setType(int type) {
+		this.type = type;
+	}
+
+	/**
+	 * @return the response
+	 */
+	public byte getResponse() {
+		return response;
+	}
+
+	/**
+	 * @param response
+	 *            the response to set
+	 */
+	public void setResponse(byte response) {
+		this.response = response;
+	}
+
+	/**
+	 * @return the unknown01
+	 */
+	public int getUnknown01() {
+		return unknown01;
+	}
+
+	/**
+	 * @param unknown01
+	 *            the unknown01 to set
+	 */
+	public void setUnknown01(int unknown01) {
+		this.unknown01 = unknown01;
+	}
+
+	/**
+	 * @return the message
+	 */
+	public String getMessage() {
+		return message;
+	}
+
+	/**
+	 * @param message
+	 *            the message to set
+	 */
+	public void setMessage(String message) {
+		this.message = message;
+	}
+
+}
diff --git a/src/engine/net/client/msg/guild/ReqGuildListMsg.java b/src/engine/net/client/msg/guild/ReqGuildListMsg.java
new file mode 100644
index 00000000..69d8491f
--- /dev/null
+++ b/src/engine/net/client/msg/guild/ReqGuildListMsg.java
@@ -0,0 +1,56 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg.guild;
+
+
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.net.client.msg.ClientNetMsg;
+
+public class ReqGuildListMsg extends ClientNetMsg {
+
+	private long nationCompID;
+	private long guildToSubCompID;
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public ReqGuildListMsg() {
+		super(Protocol.REQUESTGUILDLIST);
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the ByteBuffer into a message. If a BufferUnderflow occurs (based on reading past the limit) then this constructor Throws that Exception to the caller.
+	 */
+	public ReqGuildListMsg(AbstractConnection origin, ByteBufferReader reader)
+			 {
+		super(Protocol.REQUESTGUILDLIST, origin, reader);
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied ByteBufferWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied ByteBufferReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		reader.getInt();
+		reader.getString();
+	}
+
+}
diff --git a/src/engine/net/client/msg/guild/SendGuildEntryMsg.java b/src/engine/net/client/msg/guild/SendGuildEntryMsg.java
new file mode 100644
index 00000000..73105b86
--- /dev/null
+++ b/src/engine/net/client/msg/guild/SendGuildEntryMsg.java
@@ -0,0 +1,106 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg.guild;
+
+
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.net.client.msg.ClientNetMsg;
+import engine.objects.Guild;
+import engine.objects.GuildTag;
+import engine.objects.PlayerCharacter;
+
+import java.util.ArrayList;
+
+public class SendGuildEntryMsg extends ClientNetMsg {
+
+	private PlayerCharacter pc;
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public SendGuildEntryMsg(PlayerCharacter pc) {
+		super(Protocol.SENDGUILDENTRY);
+		this.pc = pc;
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the ByteBuffer into a message. If a BufferUnderflow occurs (based on reading past the limit) then this constructor Throws that Exception to the caller.
+	 */
+	public SendGuildEntryMsg(AbstractConnection origin, ByteBufferReader reader)
+			 {
+		super(Protocol.SENDGUILDENTRY, origin, reader);
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied ByteBufferWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+		
+		ArrayList<Guild>subsAndSovs = new ArrayList<>();
+		
+		Guild nation = pc.getGuild().getNation();
+		if (pc.getGuild() != nation)
+			subsAndSovs.add(nation);
+		subsAndSovs.addAll(pc.getGuild().getSubGuildList());
+		
+		
+		writer.putInt(1);
+		//GuildState
+		//Petitioner = 2, Sworn = 4 , Nation = 5, protectorate = 6,  city-State = 7(nation), province = 8,
+	
+		writer.putInt(subsAndSovs.size());
+		writer.putInt(1);
+		if (subsAndSovs.size() > 0){
+		
+			for (Guild guild : subsAndSovs){
+				int state = guild.getGuildState().getStateID();
+				
+					writer.putInt(guild.getObjectType().ordinal());
+					writer.putInt(guild.getObjectUUID());
+				
+					writer.putString(guild.getName());
+					
+					//TODO set Alliance date
+					writer.putShort((short)1);
+					writer.putInt(0);
+					writer.putShort((short)0);
+					writer.put((byte)0);
+					
+					writer.putInt(state);
+					GuildTag._serializeForDisplay(guild.getGuildTag(),writer);
+					if (guild == nation)
+						writer.putInt(2); // Break Fealty
+					else
+						writer.putInt(1); // Dismiss, Swear In.
+			}
+		}
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied ByteBufferReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		
+	}
+
+	public PlayerCharacter getPc() {
+		return pc;
+	}
+
+	public void setPc(PlayerCharacter pc) {
+		this.pc = pc;
+	}
+
+}
diff --git a/src/engine/net/client/msg/guild/SwearInGuildMsg.java b/src/engine/net/client/msg/guild/SwearInGuildMsg.java
new file mode 100644
index 00000000..480f97b6
--- /dev/null
+++ b/src/engine/net/client/msg/guild/SwearInGuildMsg.java
@@ -0,0 +1,78 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg.guild;
+
+
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.net.client.msg.ClientNetMsg;
+
+public class SwearInGuildMsg extends ClientNetMsg {
+
+	private int guildType;
+	private int guildUUID;
+
+	private String message;
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public SwearInGuildMsg() {
+		super(Protocol.SWEARINGUILD);
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the ByteBuffer into a message. If a BufferUnderflow occurs (based on reading past the limit) then this constructor Throws that Exception to the caller.
+	 */
+	public SwearInGuildMsg(AbstractConnection origin, ByteBufferReader reader)
+			 {
+		super(Protocol.SWEARINGUILD, origin, reader);
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied ByteBufferWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied ByteBufferReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		
+		this.guildType = reader.getInt();
+		this.guildUUID = reader.getInt();
+		reader.getInt();
+		this.message = reader.getString();
+	}
+
+	/**
+	 * @return the targetType
+	 */
+
+	public String getMessage() {
+		return message;
+	}
+
+	public void setMessage(String message) {
+		this.message = message;
+	}
+
+
+	public int getGuildUUID() {
+		return guildUUID;
+	}
+
+}
diff --git a/src/engine/net/client/msg/guild/SwearInMsg.java b/src/engine/net/client/msg/guild/SwearInMsg.java
new file mode 100644
index 00000000..8a7f5b75
--- /dev/null
+++ b/src/engine/net/client/msg/guild/SwearInMsg.java
@@ -0,0 +1,123 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg.guild;
+
+
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.net.client.msg.ClientNetMsg;
+
+public class SwearInMsg extends ClientNetMsg {
+
+	private int targetType;
+	private int targetID;
+	private int unknown01;
+	private String message;
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public SwearInMsg() {
+		super(Protocol.ACTIVATEPLEDGE);
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the ByteBuffer into a message. If a BufferUnderflow occurs (based on reading past the limit) then this constructor Throws that Exception to the caller.
+	 */
+	public SwearInMsg(AbstractConnection origin, ByteBufferReader reader)
+			 {
+		super(Protocol.ACTIVATEPLEDGE, origin, reader);
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied ByteBufferWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+		writer.putInt(this.targetType);
+		writer.putInt(this.targetID);
+		writer.putInt(this.unknown01);
+		writer.putString(this.message);
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied ByteBufferReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		this.targetType = reader.getInt();
+		this.targetID = reader.getInt();
+		this.unknown01 = reader.getInt();
+		this.message = reader.getString();
+	}
+
+	/**
+	 * @return the targetType
+	 */
+	public int getTargetType() {
+		return targetType;
+	}
+
+	/**
+	 * @param targetType
+	 *            the targetType to set
+	 */
+	public void setTargetType(int targetType) {
+		this.targetType = targetType;
+	}
+
+	/**
+	 * @return the targetID
+	 */
+	public int getTargetID() {
+		return targetID;
+	}
+
+	/**
+	 * @param targetID
+	 *            the targetID to set
+	 */
+	public void setTargetID(int targetID) {
+		this.targetID = targetID;
+	}
+
+	/**
+	 * @return the unknown01
+	 */
+	public int getUnknown01() {
+		return unknown01;
+	}
+
+	/**
+	 * @param unknown01
+	 *            the unknown01 to set
+	 */
+	public void setUnknown01(int unknown01) {
+		this.unknown01 = unknown01;
+	}
+
+	/**
+	 * @return the message
+	 */
+	public String getMessage() {
+		return message;
+	}
+
+	/**
+	 * @param message
+	 *            the message to set
+	 */
+	public void setMessage(String message) {
+		this.message = message;
+	}
+
+}
diff --git a/src/engine/net/client/msg/login/CharSelectScreenMsg.java b/src/engine/net/client/msg/login/CharSelectScreenMsg.java
new file mode 100644
index 00000000..47585c29
--- /dev/null
+++ b/src/engine/net/client/msg/login/CharSelectScreenMsg.java
@@ -0,0 +1,294 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg.login;
+
+
+import engine.exception.SerializationException;
+import engine.net.AbstractConnection;
+import engine.net.AbstractNetMsg;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.net.client.msg.ClientNetMsg;
+import engine.objects.Account;
+import engine.objects.PlayerCharacter;
+import engine.session.Session;
+import org.pmw.tinylog.Logger;
+
+import java.util.ArrayList;
+
+
+public class CharSelectScreenMsg extends ClientNetMsg {
+
+	private int numChars;
+	private int selectedIndex;
+	private int static01;
+	private byte static02;
+	private ArrayList<PlayerCharacter> chars;
+	private boolean fromCommit;
+	private Account account;
+
+	/**
+	 * Special Constructor
+	 *
+	 * @param s
+	 */
+	public CharSelectScreenMsg(Session s) {
+		this(s, false);
+	}
+
+	/**
+	 * Special Constructor
+	 *
+	 * @param s
+	 * @param fromCommit
+	 */
+	public CharSelectScreenMsg(Session s, boolean fromCommit) {
+		super(Protocol.CHARSELECTSCREEN);
+		this.fromCommit = fromCommit;
+		this.chars = new ArrayList<>();
+		// get this account
+		this.account = s.getAccount();
+
+		// Get all the character records for this account
+		chars = new ArrayList<>(this.account.characterMap.values());
+
+		if (chars == null) {
+			this.chars = new ArrayList<>();
+		}
+
+		// idiot check the quantity of the ArrayList/numChars
+		this.numChars = chars.size();
+		if (this.numChars > 7) {
+			Logger.error("Account '" + this.account.getUname() + "' has more than 7 characters.");
+
+			this.numChars = 7;
+		}
+
+		// Get the last character used (As a composite ID).
+		int lastChar = s.getAccount().getLastCharIDUsed();
+
+		// Look it up for the index #
+		this.selectedIndex = 0;
+
+		for (PlayerCharacter pc : chars)
+			if (pc.getObjectUUID() == lastChar)
+				break;
+			else
+				selectedIndex++;
+
+		// idiot check the index #
+		if (this.selectedIndex < 0) {
+			this.selectedIndex = 0;
+		}
+		if (this.selectedIndex > 6) {
+			this.selectedIndex = 6;
+		}
+
+		this.static01 = 7;
+		this.static02 = (byte) 1;
+	}
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public CharSelectScreenMsg() {
+		super(Protocol.CHARSELECTSCREEN);
+		this.chars = new ArrayList<>();
+		this.selectedIndex = 0;
+		this.static01 = 7;
+		this.static02 = (byte) 1;
+		this.fromCommit = false;
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public CharSelectScreenMsg(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.CHARSELECTSCREEN, origin, reader);
+
+		this.chars = new ArrayList<>();
+	}
+
+	/**
+	 * @see AbstractNetMsg#getPowerOfTwoBufferSize()
+	 */
+	@Override
+	protected int getPowerOfTwoBufferSize() {
+		//Larger size for historically larger opcodes
+		return (17); // 2^17 == 131,072
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied ByteBufferWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+
+		if (this.account == null)
+			Logger.error( "failed to find account for message");
+
+		// Double check char belongs to this account
+		for (int i = 0; i < this.numChars; ++i) {
+			if (this.chars.get(i) == null)
+				Logger.error("failed to find character");
+			if (this.chars.get(i).getAccount() == null)
+				Logger.error("failed to find account for character "
+						+ this.chars.get(i).getObjectUUID());
+			if (this.chars.get(i).getAccount().getObjectUUID() != this.account.getObjectUUID()) {
+				this.chars.remove(i);
+				this.numChars--;
+
+				Logger.error( "This character doesn't belong to this account.");
+
+			}
+		}
+
+		writer.putInt(this.numChars); // 4bytes
+		writer.putInt(this.selectedIndex); // 4bytes
+		writer.putInt(this.static01); // 4bytes
+		writer.put(this.static02); // 1 byte
+
+		for (int i = 0; i < this.numChars; ++i) {
+			try {
+				if (!fromCommit)
+					PlayerCharacter.serializeForClientMsgLogin(this.chars.get(i),writer);
+				else
+					PlayerCharacter.serializeForClientMsgCommit(this.chars.get(i),writer);
+			} catch (SerializationException e) {
+				Logger.error( "failed to serialize character " + this.chars.get(i).getObjectUUID());
+				// Handled already.
+			}
+		}
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied ByteBufferReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		this.numChars = reader.getInt();
+		this.selectedIndex = reader.getInt();
+
+		this.static01 = reader.monitorInt(0, "CharSelectScreenMsg-01");
+		this.static02 = reader.monitorByte((byte) 0, "CharSelectScreenMsg-02");
+
+		// TODO is this correct?!?!?
+	}
+
+	/**
+	 * @return the numChars
+	 */
+	public int getNumChars() {
+		return numChars;
+	}
+
+	/**
+	 * @param numChars
+	 *            the numChars to set
+	 */
+	public void setNumChars(int numChars) {
+		this.numChars = numChars;
+	}
+
+	/**
+	 * @return the selectedIndex
+	 */
+	public int getSelectedIndex() {
+		return selectedIndex;
+	}
+
+	/**
+	 * @param selectedIndex
+	 *            the selectedIndex to set
+	 */
+	public void setSelectedIndex(int selectedIndex) {
+		this.selectedIndex = selectedIndex;
+	}
+
+	/**
+	 * @return the static01
+	 */
+	public int getStatic01() {
+		return static01;
+	}
+
+	/**
+	 * @param static01
+	 *            the static01 to set
+	 */
+	public void setStatic01(int static01) {
+		this.static01 = static01;
+	}
+
+	/**
+	 * @return the static02
+	 */
+	public byte getStatic02() {
+		return static02;
+	}
+
+	/**
+	 * @param static02
+	 *            the static02 to set
+	 */
+	public void setStatic02(byte static02) {
+		this.static02 = static02;
+	}
+
+	/**
+	 * @return the chars
+	 */
+	public ArrayList<PlayerCharacter> getChars() {
+		return chars;
+	}
+
+	/**
+	 * @param chars
+	 *            the chars to set
+	 */
+	public void setChars(ArrayList<PlayerCharacter> chars) {
+		this.chars = chars;
+	}
+
+	/**
+	 * @return the fromCommit
+	 */
+	public boolean isFromCommit() {
+		return fromCommit;
+	}
+
+	/**
+	 * @param fromCommit
+	 *            the fromCommit to set
+	 */
+	public void setFromCommit(boolean fromCommit) {
+		this.fromCommit = fromCommit;
+	}
+
+	/**
+	 * @return the account
+	 */
+	public Account getAccount() {
+		return account;
+	}
+
+	/**
+	 * @param account
+	 *            the account to set
+	 */
+	public void setAccount(Account account) {
+		this.account = account;
+	}
+
+}
diff --git a/src/engine/net/client/msg/login/ClientLoginInfoMsg.java b/src/engine/net/client/msg/login/ClientLoginInfoMsg.java
new file mode 100644
index 00000000..5d38b673
--- /dev/null
+++ b/src/engine/net/client/msg/login/ClientLoginInfoMsg.java
@@ -0,0 +1,267 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg.login;
+
+
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.net.client.msg.ClientNetMsg;
+
+public class ClientLoginInfoMsg extends ClientNetMsg {
+
+	private String uname;
+	private String pword;
+
+	private int unknown01;
+	private int unknown02;
+
+	private String os;
+
+	private int unknown03;
+	private int unknown04;
+	private int unknown05;
+	private int unknown06;
+	private int unknown07;
+	private int unknown08;
+	private short unknown09;
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public ClientLoginInfoMsg(String uName, String pWord, String os) {
+		super(Protocol.LOGIN);
+		this.uname = uName;
+		this.pword = pWord;
+		this.os = os;
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public ClientLoginInfoMsg(AbstractConnection origin, ByteBufferReader reader)
+			 {
+		super(Protocol.LOGIN, origin, reader);
+	}
+
+	/**
+	 * This is the Copy constructor.
+	 */
+	public ClientLoginInfoMsg(ClientLoginInfoMsg msg) {
+		super(Protocol.LOGIN, msg);
+        this.uname = msg.uname;
+        this.pword = msg.pword;
+        this.os = msg.os;
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied ByteBufferWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+		writer.putString(this.uname);
+		writer.putString(this.pword);
+		writer.putInt(this.unknown01);
+		writer.putInt(this.unknown02);
+		writer.putString(this.os);
+		writer.putInt(this.unknown03);
+		writer.putInt(this.unknown04);
+		writer.putInt(this.unknown05);
+		writer.putInt(this.unknown06);
+		writer.putInt(this.unknown07);
+		writer.putInt(this.unknown08);
+		writer.putShort(this.unknown09);
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied ByteBufferReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		this.uname = reader.getString();
+		this.pword = reader.getString();
+
+		this.unknown01 = reader.monitorInt(0, "ClientLoginInfoMsg 01");
+		this.unknown02 = reader.monitorInt(0, "ClientLoginInfoMsg 02");
+
+		this.os = reader.getString();
+
+		this.unknown03 = reader.monitorInt(0, "ClientLoginInfoMsg 03");
+		this.unknown04 = reader.monitorInt(0, "ClientLoginInfoMsg 04");
+		this.unknown05 = reader.monitorInt(0, "ClientLoginInfoMsg 05");
+		this.unknown06 = reader.monitorInt(0, "ClientLoginInfoMsg 06");
+		this.unknown07 = reader.monitorInt(0, "ClientLoginInfoMsg 07");
+		this.unknown08 = reader.monitorInt(0, "ClientLoginInfoMsg 08");
+		this.unknown09 = reader
+				.monitorShort((short) 0, "ClientLoginInfoMsg 09");
+
+	}
+
+	/**
+	 * @return the uname
+	 */
+	public String getUname() {
+		return uname;
+	}
+
+	/**
+	 * @return the pword
+	 */
+	public String getPword() {
+		return pword;
+	}
+
+	/**
+	 * @return the os
+	 */
+	public String getOs() {
+		return os;
+	}
+
+	/**
+	 * @return the unknown01
+	 */
+	public int getUnknown01() {
+		return unknown01;
+	}
+
+	/**
+	 * @param unknown01
+	 *            the unknown01 to set
+	 */
+	public void setUnknown01(int unknown01) {
+		this.unknown01 = unknown01;
+	}
+
+	/**
+	 * @return the unknown02
+	 */
+	public int getUnknown02() {
+		return unknown02;
+	}
+
+	/**
+	 * @param unknown02
+	 *            the unknown02 to set
+	 */
+	public void setUnknown02(int unknown02) {
+		this.unknown02 = unknown02;
+	}
+
+	/**
+	 * @return the unknown03
+	 */
+	public int getUnknown03() {
+		return unknown03;
+	}
+
+	/**
+	 * @param unknown03
+	 *            the unknown03 to set
+	 */
+	public void setUnknown03(int unknown03) {
+		this.unknown03 = unknown03;
+	}
+
+	/**
+	 * @return the unknown04
+	 */
+	public int getUnknown04() {
+		return unknown04;
+	}
+
+	/**
+	 * @param unknown04
+	 *            the unknown04 to set
+	 */
+	public void setUnknown04(int unknown04) {
+		this.unknown04 = unknown04;
+	}
+
+	/**
+	 * @return the unknown05
+	 */
+	public int getUnknown05() {
+		return unknown05;
+	}
+
+	/**
+	 * @param unknown05
+	 *            the unknown05 to set
+	 */
+	public void setUnknown05(int unknown05) {
+		this.unknown05 = unknown05;
+	}
+
+	/**
+	 * @return the unknown06
+	 */
+	public int getUnknown06() {
+		return unknown06;
+	}
+
+	/**
+	 * @param unknown06
+	 *            the unknown06 to set
+	 */
+	public void setUnknown06(int unknown06) {
+		this.unknown06 = unknown06;
+	}
+
+	/**
+	 * @return the unknown07
+	 */
+	public int getUnknown07() {
+		return unknown07;
+	}
+
+	/**
+	 * @param unknown07
+	 *            the unknown07 to set
+	 */
+	public void setUnknown07(int unknown07) {
+		this.unknown07 = unknown07;
+	}
+
+	/**
+	 * @return the unknown08
+	 */
+	public int getUnknown08() {
+		return unknown08;
+	}
+
+	/**
+	 * @param unknown08
+	 *            the unknown08 to set
+	 */
+	public void setUnknown08(int unknown08) {
+		this.unknown08 = unknown08;
+	}
+
+	/**
+	 * @return the unknown09
+	 */
+	public short getUnknown09() {
+		return unknown09;
+	}
+
+	/**
+	 * @param unknown09
+	 *            the unknown09 to set
+	 */
+	public void setUnknown09(short unknown09) {
+		this.unknown09 = unknown09;
+	}
+
+}
diff --git a/src/engine/net/client/msg/login/CommitNewCharacterMsg.java b/src/engine/net/client/msg/login/CommitNewCharacterMsg.java
new file mode 100644
index 00000000..5078e7df
--- /dev/null
+++ b/src/engine/net/client/msg/login/CommitNewCharacterMsg.java
@@ -0,0 +1,457 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+
+
+
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg.login;
+
+
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.net.client.msg.ClientNetMsg;
+
+public class CommitNewCharacterMsg extends ClientNetMsg {
+
+	private String firstName;
+	private String lastName;
+	private int serverID;
+	private int hairStyle;
+	private int beardStyle;
+	private int skinColor;
+	private int hairColor;
+	private int beardColor;
+	private int kit;
+	private int numRunes;
+	private int[] runes;
+	private int numStats;
+	private int strengthMod;
+	private int dexterityMod;
+	private int constitutionMod;
+	private int intelligenceMod;
+	private int spiritMod;
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public CommitNewCharacterMsg() {
+		super(Protocol.CREATECHAR);
+		runes = new int[23];
+		strengthMod = 0;
+		dexterityMod = 0;
+		constitutionMod = 0;
+		intelligenceMod = 0;
+		spiritMod = 0;
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public CommitNewCharacterMsg(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.CREATECHAR, origin, reader);
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied ByteBufferWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+
+		writer.putString(this.firstName);
+		writer.putString(this.lastName);
+
+		writer.putInt(this.serverID);
+		writer.putInt(0);
+		writer.putInt(this.hairStyle);
+		writer.putInt(0);
+		writer.putInt(this.beardStyle);
+		writer.putInt(this.skinColor);
+		writer.putInt(this.hairColor);
+		writer.putInt(this.beardColor);
+		writer.putInt(this.kit);
+		for (int i = 0; i < 23; i++) {
+			writer.putInt(0);
+			writer.putInt(this.runes[i]);
+			writer.putInt(0);
+			writer.putInt(0);
+		}
+		writer.putInt(this.numStats);
+		if (this.strengthMod != 0) {
+			writer.putInt(0x8AC3C0E6);
+			writer.putInt(this.strengthMod);
+		}
+		if (this.dexterityMod != 0) {
+			writer.putInt(0xE07B3336);
+			writer.putInt(this.dexterityMod);
+		}
+		if (this.constitutionMod != 0) {
+			writer.putInt(0xB15DC77E);
+			writer.putInt(this.constitutionMod);
+		}
+		if (this.intelligenceMod != 0) {
+			writer.putInt(0xFF665EC3);
+			writer.putInt(this.intelligenceMod);
+		}
+		if (this.spiritMod != 0) {
+			writer.putInt(0xACB82E33);
+			writer.putInt(this.spiritMod);
+		}
+
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied ByteBufferReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		runes = new int[23];
+		runes = new int[23];
+		strengthMod = 0;
+		dexterityMod = 0;
+		constitutionMod = 0;
+		intelligenceMod = 0;
+		spiritMod = 0;
+
+		this.firstName = reader.getString();
+		this.lastName = reader.getString();
+		this.serverID = reader.getInt();
+
+		reader.monitorInt(0, "CommitNewCharacter 01");
+
+		this.hairStyle = reader.getInt();
+
+		reader.monitorInt(0, "CommitNewCharacter 02");
+
+		this.beardStyle = reader.getInt();
+		this.skinColor = reader.getInt();
+		this.hairColor = reader.getInt();
+		this.beardColor = reader.getInt();
+		this.kit = reader.getInt();
+		this.clearRunes();
+		int runeCount = 0;
+		for (int i = 0; i < 23; i++) {
+
+			reader.monitorInt(0, "CommitNewCharacter 03-" + i);
+
+			this.runes[i] = reader.getInt();
+
+			reader.monitorInt(0, "CommitNewCharacter 04-" + i);
+			reader.monitorInt(0, "CommitNewCharacter 05-" + i);
+
+			if (this.runes[i] != 0)
+				runeCount++;
+		}
+		this.numRunes = runeCount;
+		this.numStats = reader.getInt();
+		int stattype;
+		for (int i = 0; i < this.numStats; i++) {
+			stattype = reader.getInt();
+			if (stattype == 0x8AC3C0E6)
+				this.strengthMod = reader.getInt();
+			else if (stattype == 0xE07B3336)
+				this.dexterityMod = reader.getInt();
+			else if (stattype == 0xB15DC77E)
+				this.constitutionMod = reader.getInt();
+			else if (stattype == 0xFF665EC3)
+				this.intelligenceMod = reader.getInt();
+			else if (stattype == 0xACB82E33)
+				this.spiritMod = reader.getInt();
+		}
+	}
+
+
+	public void clearRunes() {
+		for (int i = 0; i < 23; i++)
+			this.runes[i] = 0;
+	}
+
+	public int getRace() {
+		for (int i = 0; i < 23; i++)
+			if (this.runes[i] > 1999 && this.runes[i] < 2030)
+				return this.runes[i];
+		return 0;
+	}
+
+	public int getBaseClass() {
+		for (int i = 0; i < 23; i++)
+			if (this.runes[i] > 2499 && this.runes[i] < 2504)
+				return this.runes[i];
+		return 0;
+	}
+
+	/**
+	 * @return the firstName
+	 */
+	public String getFirstName() {
+		return firstName;
+	}
+
+	/**
+	 * @param firstName
+	 *            the firstName to set
+	 */
+	public void setFirstName(String firstName) {
+		this.firstName = firstName;
+	}
+
+	/**
+	 * @return the lastName
+	 */
+	public String getLastName() {
+		return lastName;
+	}
+
+	/**
+	 * @param lastName
+	 *            the lastName to set
+	 */
+	public void setLastName(String lastName) {
+		this.lastName = lastName;
+	}
+
+	/**
+	 * @return the serverID
+	 */
+	public int getServerID() {
+		return serverID;
+	}
+
+	/**
+	 * @param serverID
+	 *            the serverID to set
+	 */
+	public void setServerID(int serverID) {
+		this.serverID = serverID;
+	}
+
+	/**
+	 * @return the hairStyle
+	 */
+	public int getHairStyle() {
+		return hairStyle;
+	}
+
+	/**
+	 * @param hairStyle
+	 *            the hairStyle to set
+	 */
+	public void setHairStyle(int hairStyle) {
+		this.hairStyle = hairStyle;
+	}
+
+	/**
+	 * @return the beardStyle
+	 */
+	public int getBeardStyle() {
+		return beardStyle;
+	}
+
+	/**
+	 * @param beardStyle
+	 *            the beardStyle to set
+	 */
+	public void setBeardStyle(int beardStyle) {
+		this.beardStyle = beardStyle;
+	}
+
+	/**
+	 * @return the skinColor
+	 */
+	public int getSkinColor() {
+		return skinColor;
+	}
+
+	/**
+	 * @param skinColor
+	 *            the skinColor to set
+	 */
+	public void setSkinColor(int skinColor) {
+		this.skinColor = skinColor;
+	}
+
+	/**
+	 * @return the hairColor
+	 */
+	public int getHairColor() {
+		return hairColor;
+	}
+
+	/**
+	 * @param hairColor
+	 *            the hairColor to set
+	 */
+	public void setHairColor(int hairColor) {
+		this.hairColor = hairColor;
+	}
+
+	/**
+	 * @return the beardColor
+	 */
+	public int getBeardColor() {
+		return beardColor;
+	}
+
+	/**
+	 * @param beardColor
+	 *            the beardColor to set
+	 */
+	public void setBeardColor(int beardColor) {
+		this.beardColor = beardColor;
+	}
+
+	/**
+	 * @return the kit
+	 */
+	public int getKit() {
+		return kit;
+	}
+
+	/**
+	 * @param kit
+	 *            the kit to set
+	 */
+	public void setKit(int kit) {
+		this.kit = kit;
+	}
+
+	/**
+	 * @return the runeCount
+	 */
+	public int getNumRunes() {
+		return numRunes;
+	}
+
+	/**
+	 * @param numRunes
+	 *            the runeCount to set
+	 */
+	public void setNumRunes(int numRunes) {
+		this.numRunes = numRunes;
+	}
+
+	/**
+	 * @return the runes
+	 */
+	public int[] getRunes() {
+		return runes;
+	}
+
+	/**
+	 * @param runes
+	 *            the runes to set
+	 */
+	public void setRunes(int[] runes) {
+		this.runes = runes;
+	}
+
+	/**
+	 * @return the numStats
+	 */
+	public int getNumStats() {
+		return numStats;
+	}
+
+	/**
+	 * @param numStats
+	 *            the numStats to set
+	 */
+	public void setNumStats(int numStats) {
+		this.numStats = numStats;
+	}
+
+	/**
+	 * @return the strengthMod
+	 */
+	public int getStrengthMod() {
+		return strengthMod;
+	}
+
+	/**
+	 * @param strengthMod
+	 *            the strengthMod to set
+	 */
+	public void setStrengthMod(int strengthMod) {
+		this.strengthMod = strengthMod;
+	}
+
+	/**
+	 * @return the dexterityMod
+	 */
+	public int getDexterityMod() {
+		return dexterityMod;
+	}
+
+	/**
+	 * @param dexterityMod
+	 *            the dexterityMod to set
+	 */
+	public void setDexterityMod(int dexterityMod) {
+		this.dexterityMod = dexterityMod;
+	}
+
+	/**
+	 * @return the constitutionMod
+	 */
+	public int getConstitutionMod() {
+		return constitutionMod;
+	}
+
+	/**
+	 * @param constitutionMod
+	 *            the constitutionMod to set
+	 */
+	public void setConstitutionMod(int constitutionMod) {
+		this.constitutionMod = constitutionMod;
+	}
+
+	/**
+	 * @return the intelligenceMod
+	 */
+	public int getIntelligenceMod() {
+		return intelligenceMod;
+	}
+
+	/**
+	 * @param intelligenceMod
+	 *            the intelligenceMod to set
+	 */
+	public void setIntelligenceMod(int intelligenceMod) {
+		this.intelligenceMod = intelligenceMod;
+	}
+
+	/**
+	 * @return the spiritMod
+	 */
+	public int getSpiritMod() {
+		return spiritMod;
+	}
+
+	/**
+	 * @param spiritMod
+	 *            the spiritMod to set
+	 */
+	public void setSpiritMod(int spiritMod) {
+		this.spiritMod = spiritMod;
+	}
+
+}
diff --git a/src/engine/net/client/msg/login/DeleteCharacterMsg.java b/src/engine/net/client/msg/login/DeleteCharacterMsg.java
new file mode 100644
index 00000000..be4efab2
--- /dev/null
+++ b/src/engine/net/client/msg/login/DeleteCharacterMsg.java
@@ -0,0 +1,75 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+
+package engine.net.client.msg.login;
+
+import engine.Enum.GameObjectType;
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.net.client.msg.ClientNetMsg;
+
+public class DeleteCharacterMsg extends ClientNetMsg {
+
+    private int characterUUID;
+    private String firstName;
+    private String serverName;
+
+    /**
+     * This is the general purpose constructor.
+     */
+    public DeleteCharacterMsg() {
+        super(Protocol.REMOVECHAR);
+    }
+
+    /**
+     * This constructor is used by NetMsgFactory. It attempts to deserialize the
+     * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+     * past the limit) then this constructor Throws that Exception to the
+     * caller.
+     */
+    public DeleteCharacterMsg(AbstractConnection origin, ByteBufferReader reader)
+             {
+        super(Protocol.REMOVECHAR, origin, reader);
+    }
+
+    /**
+     * Serializes the subclass specific items to the supplied ByteBufferWriter.
+     */
+    @Override
+    protected void _serialize(ByteBufferWriter writer) {
+        writer.putInt(GameObjectType.PlayerCharacter.ordinal());
+        writer.putInt(this.characterUUID);
+        writer.putString(this.firstName);
+        writer.putString(this.serverName);
+    }
+
+    /**
+     * Deserializes the subclass specific items from the supplied
+     * ByteBufferReader.
+     */
+    
+    @Override
+    protected void _deserialize(ByteBufferReader reader)  {
+        reader.getInt(); // Object Type Padding
+        this.characterUUID = reader.getInt();
+        this.firstName = reader.getString();
+        this.serverName = reader.getString();
+    }
+
+    /**
+     * @return the characterUUID
+     */
+    public int getCharacterUUID() {
+        return characterUUID;
+    }
+
+}
diff --git a/src/engine/net/client/msg/login/GameServerIPRequestMsg.java b/src/engine/net/client/msg/login/GameServerIPRequestMsg.java
new file mode 100644
index 00000000..5e548e36
--- /dev/null
+++ b/src/engine/net/client/msg/login/GameServerIPRequestMsg.java
@@ -0,0 +1,63 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg.login;
+
+import engine.Enum.GameObjectType;
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.net.client.msg.ClientNetMsg;
+
+public class GameServerIPRequestMsg extends ClientNetMsg {
+
+	private int characterUUID;
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public GameServerIPRequestMsg() {
+		super(Protocol.SELECTCHAR);
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the ByteBuffer into a message. If a BufferUnderflow occurs (based on reading past the limit) then this constructor Throws that Exception to the caller.
+	 */
+	public GameServerIPRequestMsg(AbstractConnection origin, ByteBufferReader reader)
+			 {
+		super(Protocol.SELECTCHAR, origin, reader);
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied ByteBufferWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+            writer.putInt(GameObjectType.PlayerCharacter.ordinal());
+            writer.putInt(this.characterUUID);
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied ByteBufferReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+            reader.getInt(); // Object Type Padding
+            this.characterUUID = reader.getInt();
+	}
+
+	/**
+	 * @return the characterUUID
+	 */
+	public int getCharacterUUID() {
+		return characterUUID;
+	}
+
+}
diff --git a/src/engine/net/client/msg/login/GameServerIPResponseMsg.java b/src/engine/net/client/msg/login/GameServerIPResponseMsg.java
new file mode 100644
index 00000000..35061578
--- /dev/null
+++ b/src/engine/net/client/msg/login/GameServerIPResponseMsg.java
@@ -0,0 +1,85 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg.login;
+
+
+import engine.gameManager.ConfigManager;
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.net.client.msg.ClientNetMsg;
+
+public class GameServerIPResponseMsg extends ClientNetMsg {
+
+	private String ip;
+	private int port;
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public GameServerIPResponseMsg(String ip, int port) {
+		super(Protocol.GAMESERVERIPRESPONSE);
+		this.ip = ip;
+		this.port = port;
+	}
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public GameServerIPResponseMsg( ) {
+		super(Protocol.GAMESERVERIPRESPONSE);
+		this.ip = ConfigManager.MB_PUBLIC_ADDR.getValue();
+		this.port = Integer.parseInt(ConfigManager.MB_WORLD_PORT.getValue());
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public GameServerIPResponseMsg(AbstractConnection origin,
+			ByteBufferReader reader)  {
+		super(Protocol.GAMESERVERIPRESPONSE, origin, reader);
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied ByteBufferWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+		writer.putString(this.ip);
+		writer.putInt(this.port);
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied ByteBufferReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		this.ip = reader.getString();
+		this.port = reader.getInt();
+	}
+
+	/**
+	 * @return the ip
+	 */
+	public String getIp() {
+		return ip;
+	}
+
+	/**
+	 * @return the port
+	 */
+	public int getPort() {
+		return port;
+	}
+}
diff --git a/src/engine/net/client/msg/login/InvalidNameMsg.java b/src/engine/net/client/msg/login/InvalidNameMsg.java
new file mode 100644
index 00000000..10dc4b77
--- /dev/null
+++ b/src/engine/net/client/msg/login/InvalidNameMsg.java
@@ -0,0 +1,89 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg.login;
+
+
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.net.client.msg.ClientNetMsg;
+import engine.server.MBServerStatics;
+
+public class InvalidNameMsg extends ClientNetMsg {
+
+	private String FirstName;
+	private String LastName;
+	private int errorCode;
+	private int serverID;
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public InvalidNameMsg(String FirstName, String LastName,
+			int errorCode) {
+		super(Protocol.NAMEVERIFY);
+		this.FirstName = FirstName;
+		this.LastName = LastName;
+		this.errorCode = errorCode;
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the ByteBuffer into a message. If a BufferUnderflow occurs (based on reading past the limit) then this constructor Throws that Exception to the caller.
+	 */
+	public InvalidNameMsg(AbstractConnection origin, ByteBufferReader reader)
+			 {
+		super(Protocol.NAMEVERIFY, origin, reader);
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied ByteBufferWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+		writer.putString(this.FirstName);
+		writer.putString(this.LastName);
+		writer.putInt(MBServerStatics.worldMapID);
+		writer.putInt(errorCode);
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied ByteBufferReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		this.FirstName = reader.getString();
+		this.LastName = reader.getString();
+		this.serverID = reader.getInt();
+		reader.monitorInt(0, "InvalidNameMsg 01");
+	}
+
+
+	/**
+	 * @return the firstName
+	 */
+	public String getFirstName() {
+		return FirstName;
+	}
+
+	/**
+	 * @return the lastName
+	 */
+	public String getLastName() {
+		return LastName;
+	}
+
+	/**
+	 * @return the errorCode
+	 */
+	public int getErrorCode() {
+		return errorCode;
+	}
+
+}
diff --git a/src/engine/net/client/msg/login/LoginErrorMsg.java b/src/engine/net/client/msg/login/LoginErrorMsg.java
new file mode 100644
index 00000000..9decaaa5
--- /dev/null
+++ b/src/engine/net/client/msg/login/LoginErrorMsg.java
@@ -0,0 +1,72 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg.login;
+
+
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.net.client.msg.ClientNetMsg;
+
+public class LoginErrorMsg extends ClientNetMsg {
+
+	private int reason;
+	private String message;
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public LoginErrorMsg(int Reason, String message) {
+		super(Protocol.LOGINFAILED);
+		this.reason = Reason;
+		this.message = message;
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the ByteBuffer into a message. If a BufferUnderflow occurs (based on reading past the limit) then this constructor Throws that Exception to the caller.
+	 */
+	public LoginErrorMsg(AbstractConnection origin, ByteBufferReader reader)
+			 {
+		super(Protocol.LOGINFAILED, origin, reader);
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied ByteBufferWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+		writer.putInt(this.reason);
+		writer.putString(this.message);
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied ByteBufferReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		this.reason = reader.getInt();
+		this.message = reader.getString();
+	}
+
+	/**
+	 * @return the reason
+	 */
+	public int getReason() {
+		return reason;
+	}
+
+	/**
+	 * @return the message
+	 */
+	public String getMessage() {
+		return message;
+	}
+}
diff --git a/src/engine/net/client/msg/login/ServerStatusMsg.java b/src/engine/net/client/msg/login/ServerStatusMsg.java
new file mode 100644
index 00000000..241bf8b2
--- /dev/null
+++ b/src/engine/net/client/msg/login/ServerStatusMsg.java
@@ -0,0 +1,80 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg.login;
+
+
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.net.client.msg.ClientNetMsg;
+
+public class ServerStatusMsg extends ClientNetMsg {
+
+	private int serverID;
+	private byte isUp;
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public ServerStatusMsg(int serverID, byte isUp) {
+		super(Protocol.ARCSERVERSTATUS);
+		this.serverID = serverID;
+		this.isUp = isUp;
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the
+	 * ByteBuffer into a message. If a BufferUnderflow occurs (based on reading
+	 * past the limit) then this constructor Throws that Exception to the
+	 * caller.
+	 */
+	public ServerStatusMsg(AbstractConnection origin, ByteBufferReader reader)  {
+		super(Protocol.ARCSERVERSTATUS, origin, reader);
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied NetMsgWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+		writer.putInt(this.serverID);
+		writer.put(this.isUp);
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied NetMsgReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		this.serverID = reader.getInt();
+		this.isUp = reader.get();
+	}
+
+	public int getServerID() {
+		return this.serverID;
+	}
+	
+	public byte getIsUp() {
+		return this.isUp;
+	}
+
+	public boolean isUp() {
+		return (this.isUp == 0x01);
+	}
+
+	public void setServerID(int value) {
+		this.serverID = value;
+	}
+	
+	public void setIsUp(byte value) {
+		this.isUp = value;
+	}
+}
diff --git a/src/engine/net/client/msg/login/VersionInfoMsg.java b/src/engine/net/client/msg/login/VersionInfoMsg.java
new file mode 100644
index 00000000..bd6eb78a
--- /dev/null
+++ b/src/engine/net/client/msg/login/VersionInfoMsg.java
@@ -0,0 +1,72 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.net.client.msg.login;
+
+
+import engine.net.AbstractConnection;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.client.Protocol;
+import engine.net.client.msg.ClientNetMsg;
+
+public class VersionInfoMsg extends ClientNetMsg {
+
+	private String majorVersion;
+	private String minorVersion;
+
+	/**
+	 * This is the general purpose constructor.
+	 */
+	public VersionInfoMsg(String majorVersion, String minorVersion) {
+		super(Protocol.VERSIONINFO);
+		this.majorVersion = majorVersion;
+		this.minorVersion = minorVersion;
+	}
+
+	/**
+	 * This constructor is used by NetMsgFactory. It attempts to deserialize the ByteBuffer into a message. If a BufferUnderflow occurs (based on reading past the limit) then this constructor Throws that Exception to the caller.
+	 */
+	public VersionInfoMsg(AbstractConnection origin, ByteBufferReader reader)
+			 {
+		super(Protocol.VERSIONINFO, origin, reader);
+	}
+
+	/**
+	 * Serializes the subclass specific items to the supplied ByteBufferWriter.
+	 */
+	@Override
+	protected void _serialize(ByteBufferWriter writer) {
+		writer.putString(this.majorVersion);
+		writer.putString(this.minorVersion);
+	}
+
+	/**
+	 * Deserializes the subclass specific items from the supplied ByteBufferReader.
+	 */
+	@Override
+	protected void _deserialize(ByteBufferReader reader)  {
+		this.majorVersion = reader.getString();
+		this.minorVersion = reader.getString();
+	}
+
+	/**
+	 * @return the majorVersion
+	 */
+	public String getMajorVersion() {
+		return majorVersion;
+	}
+
+	/**
+	 * @return the minorVersion
+	 */
+	public String getMinorVersion() {
+		return minorVersion;
+	}
+}
diff --git a/src/engine/objects/AbstractCharacter.java b/src/engine/objects/AbstractCharacter.java
new file mode 100644
index 00000000..244f91c8
--- /dev/null
+++ b/src/engine/objects/AbstractCharacter.java
@@ -0,0 +1,1974 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.objects;
+
+import engine.Enum;
+import engine.Enum.*;
+import engine.InterestManagement.InterestManager;
+import engine.InterestManagement.WorldGrid;
+import engine.exception.SerializationException;
+import engine.gameManager.*;
+import engine.job.AbstractJob;
+import engine.job.JobContainer;
+import engine.job.JobScheduler;
+import engine.jobs.ChantJob;
+import engine.jobs.PersistentAoeJob;
+import engine.jobs.TrackJob;
+import engine.math.AtomicFloat;
+import engine.math.Bounds;
+import engine.math.Vector3fImmutable;
+import engine.net.ByteBufferWriter;
+import engine.net.Dispatch;
+import engine.net.DispatchMessage;
+import engine.net.client.msg.MoveToPointMsg;
+import engine.powers.EffectsBase;
+import engine.server.MBServerStatics;
+import org.pmw.tinylog.Logger;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+public abstract class AbstractCharacter extends AbstractWorldObject {
+
+	protected String firstName;
+	protected String lastName;
+	protected short statStrCurrent;
+	protected short statDexCurrent;
+	protected short statConCurrent;
+	protected short statIntCurrent;
+	protected short statSpiCurrent;
+	protected short unusedStatPoints;
+	protected short level;
+	protected int exp;
+	protected Vector3fImmutable bindLoc;
+	protected Vector3fImmutable faceDir;
+	protected Guild guild;
+	protected byte runningTrains;
+	protected ConcurrentHashMap<Integer, CharacterPower> powers;
+	protected  ConcurrentHashMap<String, CharacterSkill> skills;
+	protected final CharacterItemManager charItemManager;
+
+	// Variables NOT to be stored in db
+	protected boolean sit = false;
+	protected boolean walkMode;
+	protected boolean combat = false;
+
+	protected Vector3fImmutable startLoc = Vector3fImmutable.ZERO;
+	protected Vector3fImmutable endLoc = Vector3fImmutable.ZERO;
+    private float desiredAltitude = 0;
+	private long takeOffTime = 0;
+	protected boolean itemCasting = false;
+
+	// nextEndLoc is used to store the next end location when someone is clicking
+	// around the ground while other timers like changeAltitude are still
+	// ticking down so that mobs/players following dont just move away to your projected location
+	protected Vector3fImmutable nextEndLoc = Vector3fImmutable.ZERO;
+
+	protected float speed;
+	protected AtomicFloat stamina = new AtomicFloat();
+	protected float staminaMax;
+	protected AtomicFloat mana = new AtomicFloat();
+	protected float manaMax;                                            // Health/Mana/Stamina
+	protected AtomicBoolean isAlive = new AtomicBoolean(true);
+	protected Resists resists = new Resists("Genric");
+	protected AbstractWorldObject combatTarget;
+	protected ConcurrentHashMap<String, JobContainer> timers;
+	protected ConcurrentHashMap<String, Long> timestamps;
+	protected int atrHandOne;
+	protected int atrHandTwo;
+	protected int minDamageHandOne;
+	protected int maxDamageHandOne;
+	protected int minDamageHandTwo;
+	protected int maxDamageHandTwo;
+	protected float rangeHandOne;
+	protected float rangeHandTwo;
+	protected float speedHandOne;
+	protected float speedHandTwo;
+	protected int defenseRating;
+	protected boolean isActive; // <-Do not use this for deleting character!
+	protected float altitude = 0; // 0=on terrain, 1=tier 1, 2=tier 2, etc.
+	protected ConcurrentHashMap<Integer, JobContainer> recycleTimers;
+	protected PlayerBonuses bonuses;
+	protected JobContainer lastChant;
+	protected boolean isCasting = false;
+	private final ReentrantReadWriteLock healthLock = new ReentrantReadWriteLock();
+	private final ReentrantReadWriteLock teleportLock = new ReentrantReadWriteLock();
+	protected long lastSetLocUpdate = 0L;
+	protected int inBuilding = -1; // -1 not in building 0 on ground floor, 1 on first floor etc
+	protected int inBuildingID = 0;
+	protected int inFloorID = -1;
+	protected int liveCounter = 0;
+
+	protected int debug = 0;
+	private float hateValue = 0;
+	private long lastHateUpdate = 0;
+	private boolean collided = false;
+	protected Regions lastRegion = null;
+	
+	protected boolean movingUp = false;
+	
+
+	/**
+	 * No Id Constructor
+	 */
+	public AbstractCharacter(
+			final String firstName,
+			final String lastName,
+			final short statStrCurrent,
+			final short statDexCurrent,
+			final short statConCurrent,
+			final short statIntCurrent,
+			final short statSpiCurrent,
+			final short level,
+			final int exp,
+			final Vector3fImmutable bindLoc,
+			final Vector3fImmutable currentLoc,
+			final Vector3fImmutable faceDir,
+			final Guild guild,
+			final byte runningTrains
+			) {
+		super();
+		this.firstName = firstName;
+		this.lastName = lastName;
+
+		this.statStrCurrent = statStrCurrent;
+		this.statDexCurrent = statDexCurrent;
+		this.statConCurrent = statConCurrent;
+		this.statIntCurrent = statIntCurrent;
+		this.statSpiCurrent = statSpiCurrent;
+		this.level = level;
+		this.exp = exp;
+		this.walkMode = true;
+		this.bindLoc = bindLoc;
+		if (ConfigManager.serverType.equals(Enum.ServerType.WORLDSERVER))
+		this.setLoc(currentLoc);
+		this.faceDir = faceDir;
+		this.guild = guild;
+		this.runningTrains = runningTrains;
+		this.powers = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW);
+		this.skills = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW);
+		this.initializeCharacter();
+
+		// Dangerous to use THIS in a constructor!!!
+		this.charItemManager = new CharacterItemManager(this);
+	}
+
+	/**
+	 * Normal Constructor
+	 */
+	public AbstractCharacter(
+			final String firstName,
+			final String lastName,
+			final short statStrCurrent,
+			final short statDexCurrent,
+			final short statConCurrent,
+			final short statIntCurrent,
+			final short statSpiCurrent,
+			final short level,
+			final int exp,
+			final Vector3fImmutable bindLoc,
+			final Vector3fImmutable currentLoc,
+			final Vector3fImmutable faceDir,
+			final Guild guild,
+			final byte runningTrains,
+			final int newUUID
+			) {
+
+		super(newUUID);
+		this.firstName = firstName;
+		this.lastName = lastName;
+
+		this.statStrCurrent = statStrCurrent;
+		this.statDexCurrent = statDexCurrent;
+		this.statConCurrent = statConCurrent;
+		this.statIntCurrent = statIntCurrent;
+		this.statSpiCurrent = statSpiCurrent;
+		this.level = level;
+		this.exp = exp;
+		this.walkMode = true;
+
+		this.bindLoc = bindLoc;
+		if (ConfigManager.serverType.equals(Enum.ServerType.WORLDSERVER))
+		this.setLoc(currentLoc);
+		this.faceDir = faceDir;
+		this.guild = guild;
+
+		this.runningTrains = runningTrains;
+		this.powers = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW);
+		this.skills = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW);
+		this.initializeCharacter();
+
+		// Dangerous to use THIS in a constructor!!!
+		this.charItemManager = new CharacterItemManager(this);
+	}
+
+	/**
+	 * ResultSet Constructor for players
+	 */
+	public AbstractCharacter(
+			final ResultSet rs,
+			final boolean isPlayer
+			) throws SQLException {
+		super(rs);
+
+		this.firstName = rs.getString("char_firstname");
+		this.lastName = rs.getString("char_lastname");
+
+		this.level = 1;
+		this.exp = rs.getInt("char_experience");
+		this.walkMode = false;
+
+		this.bindLoc = new Vector3fImmutable(0f, 0f, 0f);
+		this.endLoc = Vector3fImmutable.ZERO;
+
+		this.faceDir = Vector3fImmutable.ZERO;
+
+		final int guildID = rs.getInt("GuildUID");
+		final Guild errantGuild = Guild.getErrantGuild();
+
+		if (guildID == errantGuild.getObjectUUID()) {
+			this.guild = errantGuild;
+		}
+		else {
+			this.guild = Guild.getGuild(guildID);
+			if (this.guild == null) {
+				this.guild = Guild.getErrantGuild();
+			}
+		}
+		
+		if (this.guild == null)
+			this.guild = errantGuild;
+
+		this.skills = new ConcurrentHashMap<>();
+		this.powers = new ConcurrentHashMap<>();
+		this.initializeCharacter();
+
+		// Dangerous to use THIS in a constructor!!!
+		this.charItemManager = new CharacterItemManager(this);
+	}
+
+	/**
+	 * ResultSet Constructor for NPC/Mobs
+	 */
+	public AbstractCharacter(final ResultSet rs) throws SQLException {
+		super(rs);
+
+		this.firstName = "";
+		this.lastName = "";
+
+		this.statStrCurrent = (short) 0;
+		this.statDexCurrent = (short) 0;
+		this.statConCurrent = (short) 0;
+		this.statIntCurrent = (short) 0;
+		this.statSpiCurrent = (short) 0;
+
+		this.unusedStatPoints = (short) 0;
+
+		this.level = (short) 0; // TODO get this from MobsBase later
+		this.exp = 1;
+		this.walkMode = true;
+
+		//this.bindLoc = new Vector3fImmutable(rs.getFloat("spawnX"), rs.getFloat("spawnY"), rs.getFloat("spawnZ"));
+		this.bindLoc = Vector3fImmutable.ZERO;
+		//setLoc(this.bindLoc);
+
+		this.faceDir = Vector3fImmutable.ZERO;
+
+		this.runningTrains = (byte) 0;
+
+		this.skills = new ConcurrentHashMap<>();
+		this.powers = new ConcurrentHashMap<>();
+		initializeCharacter();
+
+		// Dangerous to use THIS in a constructor!!!
+		this.charItemManager = new CharacterItemManager(this);
+	}
+
+	/**
+	 * ResultSet Constructor for static Mobs
+	 */
+	public AbstractCharacter(final ResultSet rs, final int objectUUID) throws SQLException {
+
+		super(objectUUID);
+
+		this.firstName = "";
+		this.lastName = "";
+
+		this.statStrCurrent = (short) 0;
+		this.statDexCurrent = (short) 0;
+		this.statConCurrent = (short) 0;
+		this.statIntCurrent = (short) 0;
+		this.statSpiCurrent = (short) 0;
+
+		this.unusedStatPoints = (short) 0;
+
+		this.level = (short) 0; // TODO get this from MobsBase later
+		this.exp = 1;
+		this.walkMode = true;
+
+		this.bindLoc = new Vector3fImmutable(rs.getFloat("spawnX"), rs.getFloat("spawnY"), rs.getFloat("spawnZ"));
+
+		if (ConfigManager.serverType.equals(Enum.ServerType.WORLDSERVER))
+		this.setLoc(this.bindLoc);
+		this.endLoc = Vector3fImmutable.ZERO;
+
+
+		this.faceDir = Vector3fImmutable.ZERO;
+
+		final int guildID = rs.getInt("GuildID");
+		
+		if (guildID == Guild.getErrantGuild().getObjectUUID()) {
+			this.guild = Guild.getErrantGuild();
+		}
+		else {
+			this.guild = Guild.getGuild(guildID);
+		}
+		
+		if (this.guild == null)
+			this.guild = Guild.getErrantGuild();
+
+		this.runningTrains = (byte) 0;
+		this.skills = new ConcurrentHashMap<>();
+		this.powers = new ConcurrentHashMap<>();
+
+		this.initializeCharacter();
+
+		// Dangerous to use THIS in a constructor!!!
+		this.charItemManager = new CharacterItemManager(this);
+	}
+
+	private void initializeCharacter() {
+		this.timers = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW);
+		this.timestamps = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW);
+		final long l = System.currentTimeMillis();
+		this.timestamps.put("Health Recovery", l);
+		this.timestamps.put("Stamina Recovery", l);
+		this.timestamps.put("Mana Recovery", l);
+		this.recycleTimers = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW);
+	}
+
+	protected abstract ConcurrentHashMap<Integer, CharacterPower> initializePowers();
+
+	private byte aoecntr = 0;
+
+	public final void addPersistantAoe(
+			final String name,
+			final int duration,
+			final PersistentAoeJob asj,
+			final EffectsBase eb,
+			final int trains
+			) {
+		if (!isAlive()) {
+			return;
+		}
+		final JobContainer jc = JobScheduler.getInstance().scheduleJob(asj, duration);
+		final Effect eff = new Effect(jc, eb, trains);
+		aoecntr++;
+		this.effects.put(name + aoecntr, eff);
+		eff.setPAOE();
+	}
+
+	public final void setLastChant(
+			final int duration,
+			final ChantJob cj
+			) {
+		if (!isAlive()) {
+			return;
+		}
+		if (this.lastChant != null) {
+			this.lastChant.cancelJob();
+		}
+		this.lastChant = JobScheduler.getInstance().scheduleJob(cj, duration);
+	}
+
+
+	public final void cancelLastChant() {
+		if (this.lastChant != null) {
+			this.lastChant.cancelJob();
+			this.lastChant = null;
+		}
+	}
+
+	public final void cancelLastChantIfSame(final Effect eff) {
+		if (eff == null || this.lastChant == null) {
+			return;
+		}
+		final AbstractJob aj = this.lastChant.getJob();
+		if (aj == null || (!(aj instanceof ChantJob))) {
+			return;
+		}
+		final int token = ((ChantJob) aj).getPowerToken();
+		if (eff.getPowerToken() == token && token != 0) {
+			this.cancelLastChant();
+		}
+	}
+
+	/*
+	 * Getters
+	 */
+	public final short getUnusedStatPoints() {
+		return this.unusedStatPoints;
+	}
+
+	public final void setUnusedStatPoints(final short value) {
+		this.unusedStatPoints = value;
+	}
+
+	public final CharacterItemManager getCharItemManager() {
+		return this.charItemManager;
+	}
+
+	public final void setDebug(
+			final int value,
+			final boolean toggle
+			) {
+		if (toggle) {
+			this.debug |= value; //turn on debug
+		}
+		else {
+			this.debug &= ~value; //turn off debug
+		}
+	}
+
+	public final boolean getDebug(final int value) {
+		return ((this.debug & value) != 0);
+	}
+
+	@Override
+	public String getName() {
+		if (this.firstName.length() == 0 && this.lastName.length() == 0) {
+			return "Unnamed " + '(' + this.getObjectUUID() + ')';
+		}
+		else if (this.lastName.length() == 0) {
+			return this.getFirstName();
+		}
+		else {
+			return this.getFirstName() + ' ' + this.getLastName();
+		}
+	}
+
+	public String getFirstName() {
+		return this.firstName;
+	}
+
+	public String getLastName() {
+		return this.lastName;
+	}
+
+	public void setFirstName(final String name) {
+		this.firstName = name;
+	}
+
+	public void setLastName(final String name) {
+		this.lastName = name;
+	}
+
+	public final short getStatStrCurrent() {
+		return this.statStrCurrent;
+	}
+
+	public final short getStatDexCurrent() {
+		return this.statDexCurrent;
+	}
+
+	public final short getStatConCurrent() {
+		return this.statConCurrent;
+	}
+
+	public final short getStatIntCurrent() {
+		return this.statIntCurrent;
+	}
+
+	public final short getStatSpiCurrent() {
+		return this.statSpiCurrent;
+	}
+
+	public final void setStatStrCurrent(final short value) {
+		this.statStrCurrent = (value < 1) ? (short) 1 : value;
+	}
+
+	public final void setStatDexCurrent(final short value) {
+		this.statDexCurrent = (value < 1) ? (short) 1 : value;
+	}
+
+	public final void setStatConCurrent(final short value) {
+		this.statConCurrent = (value < 1) ? (short) 1 : value;
+	}
+
+	public final void setStatIntCurrent(final short value) {
+		this.statIntCurrent = (value < 1) ? (short) 1 : value;
+	}
+
+	public final void setStatSpiCurrent(final short value) {
+		this.statSpiCurrent = (value < 1) ? (short) 1 : value;
+	}
+
+	public short getLevel() {
+		return this.level;
+	}
+
+	public void setLevel(final short value) {
+		this.level = value;
+	}
+
+	public final boolean isActive() {
+		return this.isActive;
+	}
+
+	public final void setActive(final boolean value) {
+		this.isActive = value;
+	}
+
+	public final Resists getResists() {
+		if (this.resists == null)
+			return Resists.getResists(0);
+		return this.resists;
+	}
+
+	public final void setResists(final Resists value) {
+		this.resists = value;
+	}
+
+	public final int getExp() {
+		return this.exp;
+	}
+
+	public final void setExp(final int value) {
+		this.exp = value;
+	}
+
+	public final void setLastPower(final JobContainer jc) {
+		if (this.timers != null) {
+			this.timers.put("LastPower", jc);
+		}
+	}
+
+	public final JobContainer getLastPower() {
+		if (this.timers == null) {
+			return null;
+		}
+		return this.timers.get("LastPower");
+	}
+
+	public final void clearLastPower() {
+		if (this.timers != null) {
+			this.timers.remove("LastPower");
+		}
+	}
+
+	public final void setLastItem(final JobContainer jc) {
+		if (this.timers != null) {
+			this.timers.put("LastItem", jc);
+		}
+	}
+
+	public final JobContainer getLastItem() {
+		if (this.timers == null) {
+			return null;
+		}
+		return this.timers.get("LastItem");
+	}
+
+	public final void clearLastItem() {
+		if (this.timers != null) {
+			this.timers.remove("LastItem");
+		}
+	}
+
+	public final int getIsSittingAsInt() {
+		if (!this.isAlive()) {
+			return 1;
+		}
+
+		if (this.sit) {
+			return 4;
+		}
+		else {
+			if (this.isMoving())
+			return 7;
+			else
+				return 5;
+		}
+	}
+
+	public final int getIsWalkingAsInt() {
+		if (this.walkMode) {
+			return 1;
+		}
+		return 2;
+	}
+
+	public final int getIsCombatAsInt() {
+		if (this.combat) {
+			return 2;
+		}
+		return 1;
+	}
+
+	public final int getIsFlightAsInt() {
+		if (this.altitude > 0) {
+			return 3;
+		}
+	
+		if (this.getObjectType().equals(GameObjectType.PlayerCharacter))
+			if (((PlayerCharacter)this).isLastSwimming())
+			return 1; //swimming
+	
+		return 2; //ground
+	}
+
+
+	public final void clearTimer(final String name) {
+		if (this.timers != null) {
+			this.timers.remove(name);
+		}
+	}
+
+	public abstract Vector3fImmutable getBindLoc();
+
+
+	public final Vector3fImmutable getFaceDir() {
+		return this.faceDir;
+	}
+
+	public final Vector3fImmutable getStartLoc() {
+		return this.startLoc;
+	}
+
+	public final Vector3fImmutable getEndLoc() {
+		return this.endLoc;
+	}
+
+	public final Vector3fImmutable getNextEndLoc() {
+		// this is only used when users are changing their end
+		// location while a timer like changeAltitude is ticking down
+		return this.nextEndLoc;
+	}
+
+	public final void stopMovement(Vector3fImmutable stopLoc) {
+		
+		
+		locationLock.writeLock().lock();
+		
+		try{
+				this.setLoc(stopLoc);
+				this.endLoc = Vector3fImmutable.ZERO;
+				this.resetLastSetLocUpdate();
+		}catch(Exception e){
+			Logger.error(e);
+		}finally{
+			locationLock.writeLock().unlock();
+		}
+	}
+
+	public final boolean isMoving() {
+
+		// I might be on my way but my movement is paused
+		// due to a flight alt change
+		//TODO who the fuck wrote changeHeightJob. FIX THIS.
+
+
+		if (this.endLoc.equals(Vector3fImmutable.ZERO))
+			return false;
+		
+		if (this.takeOffTime != 0)
+			return false;
+		
+		if (this.isCasting && this.getObjectType().equals(GameObjectType.PlayerCharacter))
+			return false;
+
+		return true;
+	}
+	
+	
+	public final boolean useFlyMoveRegen() {
+
+		
+		if (this.endLoc.x != 0 && this.endLoc.z != 0)
+			return true;
+		
+		return false;
+	}
+
+	public boolean asciiLastName() {
+		return true;
+	}
+
+	public final ConcurrentHashMap<String, CharacterSkill> getSkills() {
+		return this.skills;
+	}
+
+	public final ConcurrentHashMap<Integer, CharacterPower> getPowers() {
+		return this.powers;
+	}
+
+	public final int getInBuilding() {
+		return this.inBuilding;
+	}
+
+	public Guild getGuild() {
+		return this.guild;
+	}
+
+	public int getGuildUUID() {
+			return this.guild.getObjectUUID();
+	}
+
+
+	public final int getRank() {
+		return (this.level / 10);
+	}
+
+	public final int getAtrHandOne() {
+		return this.atrHandOne;
+	}
+
+	public final int getAtrHandTwo() {
+		return this.atrHandTwo;
+	}
+
+	public final int getMinDamageHandOne() {
+		return this.minDamageHandOne;
+	}
+
+	public final int getMaxDamageHandOne() {
+		return this.maxDamageHandOne;
+	}
+
+	public final int getMinDamageHandTwo() {
+		return this.minDamageHandTwo;
+	}
+
+	public final int getMaxDamageHandTwo() {
+		return this.maxDamageHandTwo;
+	}
+
+	public final int getDefenseRating() {
+		return this.defenseRating;
+	}
+
+	public final float getRangeHandOne() {
+		return this.rangeHandOne;
+	}
+
+	public final float getRangeHandTwo() {
+		return this.rangeHandTwo;
+	}
+
+	public final float getSpeedHandOne() {
+		return this.speedHandOne;
+	}
+
+	public final float getSpeedHandTwo() {
+		return this.speedHandTwo;
+	}
+
+	public final float getRange() {
+
+		// Treb range does not appear to be set here
+		// what gives?
+
+		if (this.getObjectType() == GameObjectType.Mob) {
+			Mob mob = (Mob) this;
+			if (mob.isSiege()) {
+				return 300;
+			}
+		}
+		if (this.rangeHandOne > this.rangeHandTwo) {
+			return this.rangeHandOne;
+		}
+		return this.rangeHandTwo;
+	}
+
+	public abstract float getPassiveChance(
+			final String type,
+			final int attackerLevel,
+			final boolean fromCombat);
+
+	public abstract float getSpeed();
+
+	public static int getBankCapacity() {
+		return 500;
+	}
+
+	public final int getBankCapacityRemaining() {
+		return (AbstractCharacter.getBankCapacity() - this.charItemManager.getBankWeight());
+	}
+
+	public static int getVaultCapacity() {
+		return 5000;
+	}
+
+	public final int getVaultCapacityRemaining() {
+		return (AbstractCharacter.getVaultCapacity() - this.charItemManager.getVaultWeight());
+	}
+
+	public final ArrayList<Item> getInventory() {
+		return this.getInventory(false);
+	}
+
+	public final ArrayList<Item> getInventory(final boolean getGold) {
+		if (this.charItemManager == null) {
+			return new ArrayList<>();
+		}
+		return this.charItemManager.getInventory(getGold);
+	}
+
+	@Override
+	public Vector3fImmutable getLoc() {
+
+		return super.getLoc();
+	}
+	
+	public Vector3fImmutable getMovementLoc() {
+
+		if (this.endLoc.equals(Vector3fImmutable.ZERO))
+			return super.getLoc();		
+		if (this.takeOffTime != 0)
+			return super.getLoc();
+		
+		return super.getLoc().moveTowards(this.endLoc, this.getSpeed() * ((System.currentTimeMillis() - lastSetLocUpdate) * .001f));
+
+	}
+
+	/*
+	 * Setters
+	 */
+	public void setGuild(final Guild value) {
+		this.guild = value;
+	}
+
+	public final void setBindLoc(final float x, final float y, final float z) {
+		this.bindLoc = new Vector3fImmutable(x, y, z);
+	}
+
+	public final void setEndLoc(final Vector3fImmutable value) {
+		if(value.x > MBServerStatics.MAX_PLAYER_X_LOC)
+			return;
+		if (value.z < MBServerStatics.MAX_PLAYER_Y_LOC)
+			return;
+
+		this.endLoc = value;
+		// reset the location timer so our next call to getLoc is correct
+		this.resetLastSetLocUpdate();
+	}
+
+	public final void resetLastSetLocUpdate() {
+		this.lastSetLocUpdate = System.currentTimeMillis();
+	}
+
+	public final void setBindLoc(final Vector3fImmutable value) {
+		this.bindLoc = value;
+	}
+
+	@Override
+	public final void setLoc(final Vector3fImmutable value) {
+		super.setLoc(value); // set the location in the world
+		this.resetLastSetLocUpdate();
+		//Logger.info("AbstractCharacter", "Setting char location to :" + value.getX()  + " " + value.getZ());
+	}
+
+	public final void setFaceDir(final Vector3fImmutable value) {
+		this.faceDir = value;
+	}
+
+	public void setIsCasting(final boolean isCasting) {
+		this.isCasting = isCasting;
+	}
+
+	public final boolean isCasting() {
+		return this.isCasting;
+	}
+
+	@Override
+	public final boolean isAlive() {
+		return this.isAlive.get();
+	}
+
+	public final boolean isSafeMode() {
+
+		if (this.resists == null)
+			return false;
+
+		for (Effect eff: this.getEffects().values()){
+			if (eff.getEffectToken() == -1661750486)
+				return true;
+		}
+		return this.resists.immuneToAll();
+	}
+
+	public abstract void killCharacter(final AbstractCharacter killer);
+
+	public abstract void killCharacter(final String reason);
+
+	/**
+	 * Determines if the character is in a lootable state.
+	 *
+	 * @return True if lootable.
+	 */
+	public abstract boolean canBeLooted();
+	/*
+	 * Utils
+	 */
+
+	public float calcHitBox() {
+		if (this.getObjectType() == GameObjectType.PlayerCharacter) {
+			// hit box radius is str/100 (gets diameter of hitbox) /2 (as we want a radius)
+			// note this formula is guesswork
+			if (MBServerStatics.COMBAT_TARGET_HITBOX_DEBUG) {
+				Logger.info( "Hit box radius for " + this.getFirstName() + " is " + (this.statStrCurrent / 200f));
+			}
+			return ((PlayerCharacter) this).getStrForClient() / 200f;
+			//TODO CALCULATE MOB HITBOX BECAUSE FAIL  EMU IS FAIL!!!!!!!
+		}
+		else if (this.getObjectType() == GameObjectType.Mob) {
+			if (MBServerStatics.COMBAT_TARGET_HITBOX_DEBUG) {
+				Logger.info("Hit box radius for " + this.getFirstName() + " is " + ((Mob) this).getMobBase().getHitBoxRadius());
+			}
+			return ((Mob) this).getMobBase().getHitBoxRadius();
+		}
+		return 0f;
+	}
+
+	public final boolean isSit() {
+		return this.sit;
+	}
+
+	public final boolean isWalk() {
+		return this.walkMode;
+	}
+
+	public final boolean isCombat() {
+		return this.combat;
+	}
+
+	public final void setSit(final boolean value) {
+
+		if (this.sit != value) {
+			// change sit/stand and sync location
+			this.sit = value;
+			if (value == true) // we have been told to sit
+			{
+				this.stopMovement(this.getLoc());
+			}
+		}
+
+	}
+
+	public final void setWalkMode(final boolean value) {
+		// sync movement location as getLoc gets where we are at the exact moment in time (i.e. not the last updated loc)
+		this.setLoc(this.getLoc());
+		if (this.walkMode == value) {
+			return;
+		}
+		else {
+			this.walkMode = value;
+		}
+	}
+
+	public final void setCombat(final boolean value) {
+		this.combat = value;
+	}
+
+	public final void setInBuilding(final int floor) {
+		this.inBuilding = floor;
+	}
+
+	public final AbstractWorldObject getCombatTarget() {
+		return this.combatTarget;
+	}
+
+	public final void setCombatTarget(final AbstractWorldObject value) {
+		this.combatTarget = value;
+	}
+
+	public final ConcurrentHashMap<String, JobContainer> getTimers() {
+		if (this.timers == null) {
+			this.timers = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW);
+		}
+		return this.timers;
+	}
+
+	public final int getLiveCounter() {
+		return this.liveCounter;
+	}
+
+	public final void addTimer(
+			final String name,
+			final AbstractJob asj,
+			final int duration
+			) {
+		final JobContainer jc = JobScheduler.getInstance().scheduleJob(asj, duration);
+		if (this.timers == null) {
+			this.timers = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW);
+		}
+		this.timers.put(name, jc);
+	}
+
+	public final void renewTimer(
+			final String name,
+			final AbstractJob asj,
+			final int duration
+			) {
+		this.cancelTimer(name);
+		this.addTimer(name, asj, duration);
+	}
+
+	public final ConcurrentHashMap<Integer, JobContainer> getRecycleTimers() {
+		return this.recycleTimers;
+	}
+
+	public final ConcurrentHashMap<String, Long> getTimestamps() {
+		return this.timestamps;
+	}
+
+	public final long getTimeStamp(final String name) {
+		if (this.timestamps.containsKey(name)) {
+			return this.timestamps.get(name);
+		}
+		return 0L;
+	}
+
+	public final void setTimeStamp(final String name, final long value) {
+		this.timestamps.put(name, value);
+	}
+
+	public final void setTimeStampNow(final String name) {
+		this.timestamps.put(name, System.currentTimeMillis());
+	}
+
+	public final void cancelTimer(final String name) {
+		cancelTimer(name, true);
+	}
+
+	public final void cancelTimer(final String name, final boolean jobRunning) {
+		if (this.timers == null) {
+			this.timers = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW);
+		}
+		if (this.timers.containsKey(name)) {
+			if (jobRunning) {
+				this.timers.get(name).cancelJob();
+			}
+			this.timers.remove(name);
+		}
+	}
+
+	public final float modifyHealth(
+			final float value,
+			final AbstractCharacter attacker,
+			final boolean fromCost) {
+		
+		try{
+			
+			try{
+			boolean ready = this.healthLock.writeLock().tryLock(1, TimeUnit.SECONDS);
+			
+			while (!ready)
+					ready = this.healthLock.writeLock().tryLock(1, TimeUnit.SECONDS);
+
+		if (!this.isAlive())
+			return 0;
+		
+		Float oldHealth, newHealth;
+
+			if (!this.isAlive())
+				return 0f;
+
+			oldHealth = this.health.get();
+			newHealth = oldHealth + value;
+
+			if (newHealth > this.healthMax)
+				newHealth = healthMax;
+
+			 this.health.set(newHealth);
+		
+		if (newHealth <= 0) {
+			if (this.isAlive.compareAndSet(true, false)) {
+				killCharacter(attacker);
+				return newHealth - oldHealth;
+			}
+			else
+				return 0f; //already dead, don't send damage again
+		}                                 // past this lock!
+
+		//TODO why is Handle REtaliate and cancelontakedamage in modifyHealth? shouldnt this be outside this method?
+		if (value < 0f && !fromCost) {
+			this.cancelOnTakeDamage();
+			CombatManager.handleRetaliate(this, attacker);
+		}
+
+		return newHealth - oldHealth;
+			}finally{
+				this.healthLock.writeLock().unlock();
+			}
+		
+		} catch (InterruptedException e) {
+			// TODO Auto-generated catch block
+			e.printStackTrace();
+		}
+		return 0;
+	}
+
+	public float getCurrentHitpoints() {
+		return this.health.get();
+	}
+
+	public final float modifyMana(
+			final float value,
+			final AbstractCharacter attacker
+			) {
+		return this.modifyMana(value, attacker, false);
+	}
+
+	public final float modifyMana(
+			final float value,
+			final AbstractCharacter attacker,
+			final boolean fromCost
+			) {
+
+		if (!this.isAlive()) {
+			return 0f;
+		}
+		boolean worked = false;
+		Float oldMana = 0f, newMana = 0f;
+		while (!worked) {
+			oldMana = this.mana.get();
+			newMana = oldMana + value;
+			if (newMana > this.manaMax) {
+				newMana = manaMax;
+			}
+			else if (newMana < 0) {
+				newMana = 0f;
+			}
+			worked = this.mana.compareAndSet(oldMana, newMana);
+		}
+		if (value < 0f && !fromCost) {
+			this.cancelOnTakeDamage();
+			CombatManager.handleRetaliate(this, attacker);
+		}
+		return newMana - oldMana;
+	}
+
+	public final float modifyStamina(
+			final float value,
+			final AbstractCharacter attacker
+			) {
+		return this.modifyStamina(value, attacker, false);
+	}
+
+	public final float modifyStamina(
+			final float value,
+			final AbstractCharacter attacker,
+			final boolean fromCost
+			) {
+
+		if (!this.isAlive()) {
+			return 0f;
+		}
+		boolean worked = false;
+		Float oldStamina = 0f, newStamina = 0f;
+		while (!worked) {
+			oldStamina = this.stamina.get();
+			newStamina = oldStamina + value;
+			if (newStamina > this.staminaMax) {
+				newStamina = staminaMax;
+			}
+			else if (newStamina < 0) {
+				newStamina = 0f;
+			}
+			worked = this.stamina.compareAndSet(oldStamina, newStamina);
+		}
+		if (value < 0f && !fromCost) {
+			this.cancelOnTakeDamage();
+			CombatManager.handleRetaliate(this, attacker);
+		}
+		return newStamina - oldStamina;
+	}
+
+	public final float setMana(
+			final float value,
+			final AbstractCharacter attacker
+			) {
+		return setMana(value, attacker, false);
+	}
+
+	public final float setMana(
+			final float value,
+			final AbstractCharacter attacker,
+			final boolean fromCost
+			) {
+
+		if (!this.isAlive()) {
+			return 0f;
+		}
+		boolean worked = false;
+		Float oldMana = 0f, newMana = 0f;
+		while (!worked) {
+			oldMana = this.mana.get();
+			newMana = value;
+			if (newMana > this.manaMax) {
+				newMana = manaMax;
+			}
+			else if (newMana < 0) {
+				newMana = 0f;
+			}
+			worked = this.mana.compareAndSet(oldMana, newMana);
+		}
+		if (oldMana > newMana && !fromCost) {
+			this.cancelOnTakeDamage();
+			CombatManager.handleRetaliate(this, attacker);
+		}
+		return newMana - oldMana;
+	}
+
+	public final float setStamina(
+			final float value,
+			final AbstractCharacter attacker
+			) {
+		return setStamina(value, attacker, false);
+	}
+
+	public final float setStamina(
+			final float value,
+			final AbstractCharacter attacker,
+			final boolean fromCost
+			) {
+
+		if (!this.isAlive()) {
+			return 0f;
+		}
+		boolean worked = false;
+		Float oldStamina = 0f, newStamina = 0f;
+		while (!worked) {
+			oldStamina = this.stamina.get();
+			newStamina = value;
+			if (newStamina > this.staminaMax) {
+				newStamina = staminaMax;
+			}
+			else if (newStamina < 0) {
+				newStamina = 0f;
+			}
+			worked = this.stamina.compareAndSet(oldStamina, newStamina);
+		}
+		if (oldStamina > newStamina && !fromCost) {
+			this.cancelOnTakeDamage();
+			CombatManager.handleRetaliate(this, attacker);
+		}
+		return newStamina - oldStamina;
+
+	}
+
+	public final float getStamina() {
+		if (this.getObjectType() == GameObjectType.Mob)
+			return this.getStaminaMax();
+		return this.stamina.get();
+	}
+
+	public final float getMana() {
+		if (this.getObjectType() == GameObjectType.Mob)
+			return this.getManaMax();
+		return this.mana.get();
+	}
+
+	public final float getStaminaMax() {
+		if (this.getObjectType() == GameObjectType.Mob)
+			return 2000;
+		return this.staminaMax;
+	}
+
+	public final float getManaMax() {
+		if (this.getObjectType() == GameObjectType.Mob)
+			return 2000;
+		return this.manaMax;
+	}
+
+	public final PlayerBonuses getBonuses() {
+			return this.bonuses;
+	}
+
+	public void teleport(final Vector3fImmutable targetLoc) {
+		locationLock.writeLock().lock();
+		try{
+			MovementManager.translocate(this, targetLoc, null);
+			MovementManager.sendRWSSMsg(this);
+		}catch(Exception e){
+			Logger.error(e);
+		}finally{
+			locationLock.writeLock().unlock();
+		}
+	}
+	
+	
+	public void teleportToObject(final AbstractWorldObject worldObject) {
+		locationLock.writeLock().lock();
+		try{
+			MovementManager.translocateToObject(this, worldObject);
+		}catch(Exception e){
+			Logger.error(e);
+		}finally{
+			locationLock.writeLock().unlock();
+		}
+	}
+
+
+
+	/*
+	 * Serializing
+	 */
+	
+	public static void _serializeForClientMsg(AbstractCharacter abstractCharacter,final ByteBufferWriter writer) throws SerializationException {
+		AbstractCharacter.__serializeForClientMsg(abstractCharacter,writer);
+	}
+
+	public static void __serializeForClientMsg(AbstractCharacter abstractCharacter,final ByteBufferWriter writer) throws SerializationException {
+	}
+
+
+	public static void serializeForClientMsgOtherPlayer(AbstractCharacter abstractCharacter,final ByteBufferWriter writer) throws SerializationException {
+	}
+
+	public static void serializeForClientMsgOtherPlayer(AbstractCharacter abstractCharacter,final ByteBufferWriter writer, final boolean asciiLastName) throws SerializationException {
+	
+		switch (abstractCharacter.getObjectType()){
+		case PlayerCharacter:
+			PlayerCharacter.serializePlayerForClientMsgOtherPlayer((PlayerCharacter)abstractCharacter, writer, asciiLastName);
+			break;
+		case Mob:
+			Mob.serializeMobForClientMsgOtherPlayer((Mob)abstractCharacter, writer,asciiLastName);
+			break;
+		case NPC:
+			NPC.serializeNpcForClientMsgOtherPlayer((NPC)abstractCharacter, writer, asciiLastName);
+			break;
+		}
+		
+		
+		//TODO INPUT SWITCH CASE ON GAME OBJECTS TO CALL SPECIFIC METHODS.
+	}
+
+	public static final void serializeForTrack(AbstractCharacter abstractCharacter, final ByteBufferWriter writer, boolean isGroup) {
+		writer.putInt(abstractCharacter.getObjectType().ordinal());
+		writer.putInt(abstractCharacter.getObjectUUID());
+
+		if (abstractCharacter.getObjectType().equals(GameObjectType.PlayerCharacter)) {
+			writer.putString(abstractCharacter.getName());
+		}
+		else {
+			writer.putString(abstractCharacter.getFirstName());
+		}
+		writer.put(isGroup ? (byte) 1 : (byte) 0);
+		if (abstractCharacter.guild != null) {
+			Guild.serializeForTrack(abstractCharacter.guild,writer);
+		}
+		else {
+			Guild.serializeErrantForTrack(writer);
+		}
+	}
+
+	/*
+	 * Cancel effects upon actions
+	 */
+	public final void cancelOnAttack() { // added to one spot
+
+		boolean changed = false;
+
+		for (String s : this.effects.keySet()) {
+
+			Effect eff = this.effects.get(s);
+			
+			if (eff == null)
+				continue;
+			if (eff.cancelOnAttack() && eff.cancel()) {
+				eff.cancelJob();
+				this.effects.remove(s);
+				changed = true;
+			}
+		}
+
+		if (changed) {
+			applyBonuses();
+		}
+
+		PowersManager.cancelOnAttack(this);
+	}
+
+	public final void cancelOnAttackSwing() { // added
+		boolean changed = false;
+		for (String s : this.effects.keySet()) {
+			Effect eff = this.effects.get(s);
+			if (eff == null)
+				continue;
+			if (eff.cancelOnAttackSwing() && eff.cancel()) {
+				//System.out.println("canceling on AttackSwing");
+				eff.cancelJob();
+				this.effects.remove(s);
+				changed = true;
+			}
+		}
+		if (changed) {
+			applyBonuses();
+		}
+		PowersManager.cancelOnAttackSwing(this);
+	}
+
+	public final void cancelOnCast() {
+		boolean changed = false;
+		for (String s : this.effects.keySet()) {
+			Effect eff = this.effects.get(s);
+			
+			if (eff == null)
+				continue;
+			if (eff.cancelOnCast() && eff.cancel()) {
+
+				// Don't cancel the track effect on the character being tracked
+				if (eff.getJob() != null && eff.getJob() instanceof TrackJob) {
+					if (((TrackJob) eff.getJob()).getSource().getObjectUUID()
+							== this.getObjectUUID()) {
+						continue;
+					}
+				}
+
+				//System.out.println("canceling on Cast");
+				eff.cancelJob();
+				this.effects.remove(s);
+				changed = true;
+			}
+		}
+		if (changed) {
+			applyBonuses();
+		}
+		PowersManager.cancelOnCast(this);
+	}
+
+	public final void cancelOnSpell() {
+		boolean changed = false;
+		for (String s : this.effects.keySet()) {
+			Effect eff = this.effects.get(s);
+			if (eff == null)
+				continue;
+			if (eff.cancelOnCastSpell() && eff.cancel()) {
+				//System.out.println("canceling on CastSpell");
+				eff.cancelJob();
+				this.effects.remove(s);
+				changed = true;
+			}
+		}
+		if (changed) {
+			applyBonuses();
+		}
+		PowersManager.cancelOnSpell(this);
+	}
+
+	public final void cancelOnMove() { // added
+		boolean changed = false;
+		for (String s : this.effects.keySet()) {
+			Effect eff = this.effects.get(s);
+			if (eff == null)
+				continue;
+			if (eff.cancelOnMove() && eff.cancel()) {
+				//System.out.println("canceling on Move");
+				eff.cancelJob();
+				this.effects.remove(s);
+				changed = true;
+			}
+		}
+		if (changed) {
+			applyBonuses();
+		}
+		PowersManager.cancelOnMove(this);
+	}
+
+	public final void cancelOnSit() { // added
+		boolean changed = false;
+		for (String s : this.effects.keySet()) {
+			Effect eff = this.effects.get(s);
+			if (eff == null)
+				continue;
+			if (eff.cancelOnSit() && eff.cancel()) {
+				//System.out.println("canceling on Sit");
+				eff.cancelJob();
+				this.effects.remove(s);
+				changed = true;
+			}
+		}
+		if (changed) {
+			applyBonuses();
+		}
+		PowersManager.cancelOnSit(this);
+	}
+
+	public final void cancelOnTakeDamage() {
+		boolean changed = false;
+		for (String s : this.effects.keySet()) {
+			Effect eff = this.effects.get(s);
+			if (eff == null)
+				continue;
+			if (eff.cancelOnTakeDamage() && eff.cancel()) {
+				//System.out.println("canceling on Take Damage");
+				eff.cancelJob();
+				this.effects.remove(s);
+				changed = true;
+			}
+		}
+		if (changed) {
+			applyBonuses();
+		}
+		PowersManager.cancelOnTakeDamage(this);
+	}
+
+	public final void cancelOnTakeDamage(final DamageType type, final float amount) {
+		boolean changed = false;
+		for (String s : this.effects.keySet()) {
+			Effect eff = this.effects.get(s);
+			if (eff == null)
+				continue;
+			if (eff.cancelOnTakeDamage(type, amount) && eff.cancel()) {
+				eff.cancelJob();
+				this.effects.remove(s);
+				changed = true;
+			}
+		}
+		if (changed) {
+			applyBonuses();
+		}
+	}
+
+	public final Effect getDamageAbsorber() {
+		for (String s : this.effects.keySet()) {
+			Effect eff = this.effects.get(s);
+			if (eff == null)
+				continue;
+			if (eff.isDamageAbsorber()) {
+				return eff;
+			}
+		}
+		return null;
+	}
+
+	public final void cancelOnUnEquip() {
+		boolean changed = false;
+		for (String s : this.effects.keySet()) {
+			Effect eff = this.effects.get(s);
+			if (eff == null)
+				continue;
+			if (eff.cancelOnUnEquip() && eff.cancel()) {
+				//System.out.println("canceling on UnEquip");
+				eff.cancelJob();
+				this.effects.remove(s);
+				changed = true;
+			}
+		}
+		if (changed) {
+			applyBonuses();
+		}
+		PowersManager.cancelOnUnEquip(this);
+	}
+
+	public final void cancelOnStun() {
+		boolean changed = false;
+		for (String s : this.effects.keySet()) {
+			Effect eff = this.effects.get(s);
+			
+			if (eff == null){
+				Logger.error("null effect for " + this.getObjectUUID() + " : effect " + s);
+				continue;
+			}
+			if (eff.cancelOnStun() && eff.cancel()) {
+				//System.out.println("canceling on Stun");
+				eff.cancelJob();
+				this.effects.remove(s);
+				changed = true;
+			}
+		}
+		if (changed) {
+			applyBonuses();
+		}
+		PowersManager.cancelOnStun(this);
+	}
+
+	//Call to apply any new effects to player
+	public synchronized void applyBonuses() {
+		//tell the player to applyBonuses because something has changed
+
+		//start running the bonus calculations
+		try{
+		runBonuses();
+		}catch(Exception e){
+			Logger.error("Error in run bonuses for object UUID " + this.getObjectUUID());
+			Logger.error(e);
+		}
+	}
+
+	//Don't call this function directly. linked from ac.applyBonuses()
+	//through BonusCalcJob. Designed to only run from one worker thread
+	public final void runBonuses() {
+		// synchronized with getBonuses()
+		synchronized (this.bonuses) {
+			try {
+				//run until no new bonuses are applied
+
+				// clear bonuses and reapply rune bonuses
+				if (this.getObjectType().equals(GameObjectType.PlayerCharacter)) {
+					this.bonuses.calculateRuneBaseEffects((PlayerCharacter) this);
+				}
+				else {
+					this.bonuses.clearRuneBaseEffects();
+				}
+
+				// apply effect bonuses
+				for (Effect eff : this.effects.values()) {
+					eff.applyBonus(this);
+				}
+
+				//apply item bonuses for equipped items
+				ConcurrentHashMap<Integer, Item> equip = null;
+
+				if (this.charItemManager != null) {
+					equip = this.charItemManager.getEquipped();
+				}
+				if (equip != null) {
+					for (Item item : equip.values()) {
+						item.clearBonuses();
+						if (item != null) {
+							ConcurrentHashMap<String, Effect> effects = item.getEffects();
+							if (effects != null) {
+								for (Effect eff : effects.values()) {
+									eff.applyBonus(item, this);
+								}
+							}
+						}
+					}
+				}
+
+				//recalculate passive defenses
+				if (this.getObjectType().equals(GameObjectType.PlayerCharacter)) {
+					((PlayerCharacter) this).setPassives();
+				}
+
+			
+
+				// recalculate everything
+				if (this.getObjectType().equals(GameObjectType.PlayerCharacter)) {
+					PlayerCharacter pc = (PlayerCharacter) this;
+
+					//calculate item bonuses
+					pc.calculateItemBonuses();
+
+					//recalculate formulas
+					pc.recalculatePlayerStats(true);
+					
+
+				}
+				else if (this.getObjectType().equals(GameObjectType.Mob)) {
+					Mob mob = (Mob) this;
+
+					//recalculate formulas
+					mob.recalculateStats();
+				}
+			} catch (Exception e) {
+				Logger.error( e);
+			}
+		}
+	}
+
+	public static void runBonusesOnLoad(PlayerCharacter pc) {
+		// synchronized with getBonuses()
+		synchronized (pc.bonuses) {
+		try {
+			//run until no new bonuses are applied
+
+			// clear bonuses and reapply rune bonuses
+			if (pc.getObjectType() ==  GameObjectType.PlayerCharacter) {
+				pc.bonuses.calculateRuneBaseEffects(pc);
+			}
+			else {
+				pc.bonuses.clearRuneBaseEffects();
+			}
+
+			// apply effect bonuses
+			for (Effect eff : pc.effects.values()) {
+				eff.applyBonus(pc);
+			}
+
+			//apply item bonuses for equipped items
+			ConcurrentHashMap<Integer, Item> equip = null;
+
+			if (pc.charItemManager != null)
+				equip = pc.charItemManager.getEquipped();
+
+			if (equip != null) {
+				for (Item item : equip.values()) {
+					item.clearBonuses();
+					if (item != null) {
+						ConcurrentHashMap<String, Effect> effects = item.getEffects();
+						if (effects != null) {
+							for (Effect eff : effects.values()) {
+								eff.applyBonus(item, pc);
+							}
+						}
+					}
+				}
+			}
+
+			//recalculate passive defenses
+			pc.setPassives();
+
+			//flip the active bonus set for synchronization purposes
+			//do this after all bonus updates, but before recalculations.
+			// recalculate everything
+			//calculate item bonuses
+			pc.calculateItemBonuses();
+
+			//recalculate formulas
+			PlayerCharacter.recalculatePlayerStatsOnLoad(pc);
+
+		} catch (Exception e) {
+			Logger.error( e);
+		}
+		
+		}
+		// TODO remove later, for debugging.
+		//this.bonuses.printBonuses();
+
+	}
+
+	public int getInBuildingID() {
+		return inBuildingID;
+	}
+
+	public void setInBuildingID(int inBuildingID) {
+		this.inBuildingID = inBuildingID;
+	}
+
+	public float getHateValue() {
+		if (this.hateValue <= 0) {
+			this.hateValue = 0;
+			return hateValue;
+		}
+
+		if (this.lastHateUpdate == 0) {
+			this.lastHateUpdate = System.currentTimeMillis();
+			return this.hateValue;
+		}
+		long duration = System.currentTimeMillis() - this.lastHateUpdate;
+		//convert duration to seconds and multiply Hate Delimiter.
+		float modAmount = duration / 1000 * MBServerStatics.PLAYER_HATE_DELIMITER;
+		this.hateValue -= modAmount;
+		this.lastHateUpdate = System.currentTimeMillis();
+		return this.hateValue;
+	}
+
+	public void setHateValue(float hateValue) {
+		this.lastHateUpdate = System.currentTimeMillis();
+		this.hateValue = hateValue;
+	}
+
+	public int getInFloorID() {
+		return inFloorID;
+	}
+
+	public void setInFloorID(int inFloorID) {
+		this.inFloorID = inFloorID;
+	}
+
+	public boolean isCollided() {
+		return collided;
+	}
+
+	public void setCollided(boolean collided) {
+		this.collided = collided;
+	}
+	
+	
+	public static void SetBuildingLevelRoom(AbstractCharacter character, int buildingID, int buildingLevel, int room, Regions region){
+		character.inBuildingID = buildingID;
+		character.inBuilding = buildingLevel;
+		character.inFloorID = room;
+		character.lastRegion = region;
+	}
+
+	public static Regions InsideBuildingRegion(AbstractCharacter player){
+
+		Regions currentRegion = null;
+		HashSet<AbstractWorldObject> buildings = WorldGrid.getObjectsInRangePartial(player, 300, MBServerStatics.MASK_BUILDING);
+
+		for (AbstractWorldObject awo: buildings){
+
+			Building building = (Building)awo;
+
+			if (building.getBounds() == null)
+				continue;
+
+			if (building.getBounds().getRegions() == null)
+				continue;
+
+			for (Regions region : building.getBounds().getRegions()){
+				//TODO ADD NEW REGION CODE
+			}
+		}
+		return currentRegion;
+	}
+
+	public static Regions InsideBuildingRegionGoingDown(AbstractCharacter player){
+
+		HashSet<AbstractWorldObject> buildings = WorldGrid.getObjectsInRangePartial(player, 1000, MBServerStatics.MASK_BUILDING);
+
+		Regions tempRegion = null;
+		for (AbstractWorldObject awo: buildings){
+
+			Building building = (Building)awo;
+			if (building.getBounds() == null)
+				continue;
+			
+			if (!Bounds.collide(player.getLoc(), building.getBounds()))
+				continue;
+
+			for (Regions region : building.getBounds().getRegions()){
+				
+				if (!region.isPointInPolygon(player.getLoc()))
+					continue;
+				
+				if (!region.isOutside())
+					continue;
+				if (tempRegion == null)
+					tempRegion = region;
+				
+				if (tempRegion.highLerp.y < region.highLerp.y)
+					tempRegion = region;
+			}
+			
+			if (tempRegion != null)
+				break;
+		}
+		return tempRegion;
+	}
+
+	public float getDesiredAltitude() {
+		return desiredAltitude;
+	}
+
+	public void setDesiredAltitude(float desiredAltitude) {
+		this.desiredAltitude = desiredAltitude;
+	}
+
+	
+	public long getTakeOffTime() {
+		return takeOffTime;
+	}
+
+	public void setTakeOffTime(long takeOffTime) {
+		this.takeOffTime = takeOffTime;
+	}
+	
+	public static boolean CanFly(AbstractCharacter flyer){
+		boolean canFly = false;
+		PlayerBonuses bonus = flyer.getBonuses();
+
+		if (bonus != null && !bonus.getBool(ModType.NoMod, SourceType.Fly) && bonus.getBool(ModType.Fly,SourceType.None) && flyer.isAlive())
+			canFly = true;
+		
+		return canFly;
+		
+	}
+
+	public boolean isItemCasting() {
+		return itemCasting;
+	}
+
+	public void setItemCasting(boolean itemCasting) {
+		this.itemCasting = itemCasting;
+	}
+	
+	public static void MoveInsideBuilding(PlayerCharacter source, AbstractCharacter ac){
+		MoveToPointMsg moveMsg = new MoveToPointMsg();
+		moveMsg.setPlayer(ac);
+        moveMsg.setTarget(ac, BuildingManager.getBuildingFromCache(ac.inBuildingID));
+		
+		Dispatch dispatch = Dispatch.borrow(source, moveMsg);
+		DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.PRIMARY);
+		
+	}
+	
+	//updates
+	public void update(){
+	}
+	public void updateRegen(){
+	}
+	public void updateMovementState(){
+	}
+	public void updateLocation(){
+	}
+	public void updateFlight(){
+	}
+	
+	
+	public void dynamicUpdate(UpdateType updateType){
+	if (this.updateLock.writeLock().tryLock()){
+		try{
+			switch(updateType){
+			case ALL:
+				update();
+				break;
+			case REGEN:
+				updateRegen();
+				break;
+			case LOCATION:
+				update();
+				break;
+			case MOVEMENTSTATE:
+				update();
+				break;
+			case FLIGHT:
+				updateFlight();
+				break;
+			}
+			
+		}catch(Exception e){
+			Logger.error(e);
+		}finally{
+			this.updateLock.writeLock().unlock();
+		}
+	}
+	
+	}
+
+	public Regions getLastRegion() {
+		return lastRegion;
+	}
+
+	public boolean isMovingUp() {
+		return movingUp;
+	}
+
+	public void setMovingUp(boolean movingUp) {
+		this.movingUp = movingUp;
+	}	
+	public static void UpdateRegion(AbstractCharacter worldObject){
+			worldObject.region = AbstractWorldObject.GetRegionByWorldObject(worldObject);
+	}
+	
+	public static void teleport(AbstractCharacter worldObject, final Vector3fImmutable targetLoc) {
+		worldObject.locationLock.writeLock().lock();
+		try{
+			MovementManager.translocate(worldObject, targetLoc,null);
+			if (worldObject.getObjectType().equals(GameObjectType.PlayerCharacter))
+			InterestManager.INTERESTMANAGER.HandleLoadForTeleport((PlayerCharacter)worldObject);
+		}catch(Exception e){
+			Logger.error(e);
+		}finally{
+			worldObject.locationLock.writeLock().unlock();
+		}
+	}
+}
diff --git a/src/engine/objects/AbstractGameObject.java b/src/engine/objects/AbstractGameObject.java
new file mode 100644
index 00000000..aad22fbf
--- /dev/null
+++ b/src/engine/objects/AbstractGameObject.java
@@ -0,0 +1,235 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.objects;
+
+import engine.Enum.GameObjectType;
+import engine.gameManager.BuildingManager;
+import engine.gameManager.DbManager;
+import engine.job.JobContainer;
+import engine.job.JobScheduler;
+import engine.jobs.DatabaseUpdateJob;
+import engine.server.MBServerStatics;
+import org.pmw.tinylog.Logger;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.concurrent.ConcurrentHashMap;
+
+
+public abstract class AbstractGameObject {
+	private GameObjectType objectType = GameObjectType.unknown;
+	private int objectUUID;
+
+	private byte ver = 1;
+
+	private ConcurrentHashMap<String, JobContainer> databaseJobs = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW);
+
+	/**
+	 * No Table ID Constructor
+	 */
+	public AbstractGameObject() {
+		super();
+		setObjectType();
+		this.objectUUID = MBServerStatics.NO_DB_ROW_ASSIGNED_YET;
+	}
+
+	/**
+	 * Normal Constructor
+	 */
+	public AbstractGameObject(int objectUUID) {
+		this();
+		this.objectUUID = objectUUID;
+	}
+
+	/**
+	 * ResultSet Constructor
+	 *
+	 * @param rs
+	 *            ResultSet containing record for this object
+     */
+	public AbstractGameObject(ResultSet rs, int objectUUID) throws SQLException {
+		this();
+		this.objectUUID = objectUUID;
+	}
+
+	/**
+	 * ResultSet Constructor; assumes first column in ResultSet is ID
+	 *
+	 * @param rs
+	 *            ResultSet containing record for this object
+	 */
+	public AbstractGameObject(ResultSet rs) throws SQLException {
+		this(rs, rs.getInt(1));
+	}
+
+	/*
+	 * Getters
+	 */
+	public GameObjectType getObjectType() {
+		return this.objectType;
+	}
+
+	protected final void setObjectType() {
+		try {
+          this.objectType = GameObjectType.valueOf(this.getClass().getSimpleName());
+		} catch (SecurityException | IllegalArgumentException e) {
+			Logger.error("Failed to find class " + this.getClass().getSimpleName()
+					+ " in GameObjectTypes file. Defaulting ObjectType to 0.");
+		}
+	}
+	
+	public int getObjectUUID() {
+		return this.objectUUID;
+	}
+
+	protected void setObjectUUID(int objectUUID) {
+		this.objectUUID = objectUUID;
+	}
+
+	public byte getVer() {
+		return this.ver;
+	}
+
+	public void incVer() {
+		this.ver++;
+		if (this.ver == (byte)-1) //-1 reserved
+			this.ver++;
+	}
+
+	/*
+	 * Util
+	 */
+
+	public static int extractUUID(GameObjectType type, long compositeID) {
+		if (type == null || type == GameObjectType.unknown || compositeID == 0L) {
+			return -1;
+		}
+		int out = (int) compositeID;
+		if (out > Long.MAX_VALUE || out < 0) {
+			Logger.error("There was a problem reverse calculating a UUID from a compositeID. \tcomposID: "
+					+ compositeID + " \ttype: " + type.toString() + "\tresult: " + out);
+		}
+		return out;
+	}
+
+	public static GameObjectType extractTypeID(long compositeID) {
+                int ordinal = (int) (compositeID >>> 32);
+		return GameObjectType.values()[ordinal];
+	}
+
+	public boolean equals(AbstractGameObject obj) {
+		
+            if (obj == null)
+			return false;
+
+        if (obj.objectType != this.objectType) {
+			return false;
+		}
+
+        return obj.getObjectUUID() == this.getObjectUUID();
+    }
+
+	public void removeFromCache() {
+		DbManager.removeFromCache(this);
+	}
+
+	/**
+	 * Generates a {@link PreparedStatementShared} based on the specified query.
+	 * <p>
+	 * If {@link AbstractGameObject} Database functions  will properly release
+	 * the PreparedStatementShared upon completion. If these functions are not
+	 * used, then {@link PreparedStatementShared#release release()} must be
+	 * called when finished with this object.
+	 *
+	 * @param sql
+	 *            The SQL string used to generate the PreparedStatementShared
+	 * @return {@link PreparedStatementShared}
+	 * @throws {@link SQLException}
+	 **/
+	protected static PreparedStatementShared prepareStatement(String sql) throws SQLException {
+		return new PreparedStatementShared(sql);
+	}
+
+	public ConcurrentHashMap<String, JobContainer> getDatabaseJobs() {
+		return this.databaseJobs;
+	}
+
+	public void addDatabaseJob(String type, int duration) {
+                DatabaseUpdateJob updateJob;
+                
+		if (databaseJobs.containsKey(type))
+			return;
+                
+		updateJob = new DatabaseUpdateJob(this, type);
+		JobContainer jc = JobScheduler.getInstance().scheduleJob(updateJob, duration);
+		databaseJobs.put(type, jc);
+	}
+
+	public void removeDatabaseJob(String type, boolean canceled) {
+		if (databaseJobs.containsKey(type)) {
+			if (canceled) {
+				JobContainer jc = databaseJobs.get(type);
+				if (jc != null)
+					jc.cancelJob();
+			}
+			databaseJobs.remove(type);
+		}
+	}
+	
+	public static AbstractGameObject getFromTypeAndID(long compositeID) {
+		int objectTypeID = extractTypeOrdinal(compositeID);
+		int tableID = extractTableID(objectTypeID, compositeID);
+		GameObjectType objectType = GameObjectType.values()[objectTypeID];
+
+		switch (objectType) {
+		case PlayerCharacter:
+			return PlayerCharacter.getPlayerCharacter(tableID);
+
+		case NPC:
+			return NPC.getNPC(tableID);
+
+		case Mob:
+			return Mob.getMob(tableID);
+
+		case Building:
+			return BuildingManager.getBuilding(tableID);
+
+		case Guild:
+			return Guild.getGuild(tableID);
+
+		case Item:
+			return Item.getFromCache(tableID);
+
+		case MobLoot:
+			return MobLoot.getFromCache(tableID);
+
+		default:
+			Logger.error("Failed to convert compositeID to AbstractGameObject. " + "Unsupported type encountered. "
+					+ "CompositeID: " + compositeID + " ObjectType: 0x" + Integer.toHexString(objectTypeID) + " TableID: " + tableID);
+		}
+		return null;
+	}
+	
+	public static int extractTypeOrdinal(long compositeID) {
+		return (int) (compositeID >>> 32);
+	}
+	public static int extractTableID(int type, long compositeID) {
+		if (type == 0 || compositeID == 0L) {
+			return -1;
+		}
+        return (int) compositeID;
+	}
+
+	/*
+	 * Abstract Methods
+	 */
+	
+	public abstract void updateDatabase();
+}
diff --git a/src/engine/objects/AbstractIntelligenceAgent.java b/src/engine/objects/AbstractIntelligenceAgent.java
new file mode 100644
index 00000000..f3f3c601
--- /dev/null
+++ b/src/engine/objects/AbstractIntelligenceAgent.java
@@ -0,0 +1,244 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.objects;
+
+import engine.Enum;
+import engine.Enum.GameObjectType;
+import engine.Enum.ModType;
+import engine.Enum.SourceType;
+import engine.InterestManagement.WorldGrid;
+import engine.ai.MobileFSM.STATE;
+import engine.gameManager.ZoneManager;
+import engine.math.Vector3fImmutable;
+import engine.net.Dispatch;
+import engine.net.DispatchMessage;
+import engine.net.client.msg.PetMsg;
+import engine.server.MBServerStatics;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+
+
+public abstract class AbstractIntelligenceAgent extends AbstractCharacter {
+	private boolean assist = false;
+	private AbstractCharacter callForHelpAggro = null;
+	private int type = 0; //Mob: 0, Pet: 1, Guard: 2
+	protected Vector3fImmutable lastBindLoc;
+	private boolean clearAggro = false;
+
+
+	public AbstractIntelligenceAgent(ResultSet rs) throws SQLException {
+		super(rs);
+	}
+
+	public AbstractIntelligenceAgent(ResultSet rs, boolean isPlayer)
+			throws SQLException {
+		super(rs, isPlayer);
+	}
+
+
+	public AbstractIntelligenceAgent(ResultSet rs, 
+			int UUID) throws SQLException {
+		super(rs, UUID);
+	}
+
+	public AbstractIntelligenceAgent( String firstName,
+			String lastName, short statStrCurrent, short statDexCurrent,
+			short statConCurrent, short statIntCurrent, short statSpiCurrent,
+			short level, int exp, boolean sit, boolean walk, boolean combat,
+			Vector3fImmutable bindLoc, Vector3fImmutable currentLoc, Vector3fImmutable faceDir,
+			short healthCurrent, short manaCurrent, short stamCurrent,
+			Guild guild, byte runningTrains) {
+		super(firstName, lastName, statStrCurrent, statDexCurrent, statConCurrent,
+				statIntCurrent, statSpiCurrent, level, exp, bindLoc,
+				currentLoc, faceDir, guild,
+				runningTrains);
+	}
+
+	public AbstractIntelligenceAgent(String firstName,
+			String lastName, short statStrCurrent, short statDexCurrent,
+			short statConCurrent, short statIntCurrent, short statSpiCurrent,
+			short level, int exp, boolean sit, boolean walk, boolean combat,
+			Vector3fImmutable bindLoc, Vector3fImmutable currentLoc, Vector3fImmutable faceDir,
+			short healthCurrent, short manaCurrent, short stamCurrent,
+			Guild guild, byte runningTrains, int newUUID) {
+		super(firstName, lastName, statStrCurrent, statDexCurrent, statConCurrent,
+				statIntCurrent, statSpiCurrent, level, exp, bindLoc,
+				currentLoc, faceDir, guild,
+				runningTrains, newUUID);
+	}
+
+	@Override
+	public void setObjectTypeMask(int mask) {
+		mask |= MBServerStatics.MASK_IAGENT;
+		super.setObjectTypeMask(mask);
+	}
+
+	/* AI Job Management */
+
+	public MobBase getMobBase() {
+
+		if (this.getObjectType().equals(GameObjectType.Mob))
+			return this.getMobBase();
+		return null;
+	}
+
+	public void setCallForHelpAggro(AbstractCharacter ac) {
+		this.callForHelpAggro = ac;
+	}
+
+	public AbstractCharacter getCallForHelpAggro() {
+		return callForHelpAggro;
+	}
+
+	public void setMob() {
+		this.type = 0;
+	}
+
+	public void setPet(PlayerCharacter owner, boolean summoned) {
+		if (summoned)
+			this.type = 1; //summoned
+		else
+			this.type = 2; //charmed
+		if (this.getObjectType().equals(GameObjectType.Mob)) {
+			((Mob)this).setOwner(owner);
+		}
+	}
+
+	public void setGuard() {
+		this.type = 3;
+	}
+
+	public boolean isMob() {
+		return (this.type == 0);
+	}
+
+	public boolean isPet() {
+		return (this.type == 1 || this.type == 2);
+	}
+
+	public boolean isSummonedPet() {
+		return (this.type == 1);
+	}
+
+	public boolean isCharmedPet() {
+		return (this.type == 2);
+	}
+
+	public boolean isGuard() {
+		return (this.type == 3);
+	}
+
+	public boolean assist() {
+		return this.assist;
+	}
+
+	public void setAssist(boolean value) {
+		this.assist = value;
+	}
+
+	public void toggleAssist() {
+		this.assist = (this.assist) ? false : true;
+	}
+
+	public int getDBID() {
+
+		if (this.getObjectType().equals(GameObjectType.Mob))
+			return this.getDBID();
+		return 0;
+	}
+
+	public boolean clearAggro() {
+		return clearAggro;
+	}
+
+	public void setClearAggro(boolean value) {
+		this.clearAggro = value;
+	}
+
+	public Vector3fImmutable getLastBindLoc() {
+		if (this.lastBindLoc == null)
+			this.lastBindLoc = this.getBindLoc();
+		return this.lastBindLoc;
+	}
+
+	public PlayerCharacter getOwner() {
+
+		if (this .getObjectType().equals(GameObjectType.Mob))
+			return this.getOwner();
+		return null;
+	}
+
+	public boolean getSafeZone() {
+		ArrayList<Zone>allIn = ZoneManager.getAllZonesIn(this.getLoc());
+		for (Zone zone : allIn) {
+			if (zone.getSafeZone() == (byte)1)
+				return true;
+		}
+		return false;
+		//return this.safeZone;
+	}
+
+	public abstract AbstractWorldObject getFearedObject();
+
+	public float getAggroRange() {
+		float ret = MBServerStatics.AI_BASE_AGGRO_RANGE;
+		if (this.bonuses != null)
+			ret *= (1 +this.bonuses.getFloatPercentAll(ModType.ScanRange, SourceType.None));
+		return ret;
+	}
+
+	public void dismiss() {
+
+		if (this.isPet()) {
+
+			if (this.isSummonedPet()) { //delete summoned pet
+
+				WorldGrid.RemoveWorldObject(this);
+				if (this.getObjectType() == GameObjectType.Mob){
+					((Mob)this).setState(STATE.Disabled);
+					if (((Mob)this).getParentZone() != null)
+						((Mob)this).getParentZone().zoneMobSet.remove(this);
+				}
+
+			} else { //revert charmed pet
+				this.setMob();
+				this.setCombatTarget(null);
+				//				if (this.isAlive())
+				//					WorldServer.updateObject(this);
+			}
+			//clear owner
+			PlayerCharacter owner = this.getOwner();
+
+			//close pet window
+			if (owner != null) {
+				Mob pet = owner.getPet();
+				PetMsg pm = new PetMsg(5, null);
+				Dispatch dispatch = Dispatch.borrow(owner, pm);
+				DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+
+				if (pet != null && pet.getObjectUUID() == this.getObjectUUID())
+					owner.setPet(null);
+
+				if (this.getObjectType().equals(GameObjectType.Mob))
+					((Mob)this).setOwner(null);
+			}
+
+
+		}
+	}
+	
+	
+	
+	
+	
+}
+
diff --git a/src/engine/objects/AbstractWorldObject.java b/src/engine/objects/AbstractWorldObject.java
new file mode 100644
index 00000000..61e69d31
--- /dev/null
+++ b/src/engine/objects/AbstractWorldObject.java
@@ -0,0 +1,638 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.objects;
+
+import engine.Enum.DispatchChannel;
+import engine.Enum.EffectSourceType;
+import engine.Enum.GameObjectType;
+import engine.Enum.GridObjectType;
+import engine.InterestManagement.HeightMap;
+import engine.InterestManagement.WorldGrid;
+import engine.job.AbstractScheduleJob;
+import engine.job.JobContainer;
+import engine.job.JobScheduler;
+import engine.jobs.NoTimeJob;
+import engine.math.AtomicFloat;
+import engine.math.Bounds;
+import engine.math.Vector3f;
+import engine.math.Vector3fImmutable;
+import engine.net.Dispatch;
+import engine.net.DispatchMessage;
+import engine.net.client.ClientConnection;
+import engine.net.client.msg.UpdateEffectsMsg;
+import engine.powers.EffectsBase;
+import engine.server.MBServerStatics;
+import org.pmw.tinylog.Logger;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.locks.ReadWriteLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+public abstract class AbstractWorldObject extends AbstractGameObject {
+
+	private String name = "";
+
+	protected final ReadWriteLock locationLock = new ReentrantReadWriteLock(true);
+	protected final ReadWriteLock updateLock = new ReentrantReadWriteLock(true);
+	
+	protected Vector3fImmutable loc = new Vector3fImmutable(0.0f, 0.0f, 0.0f);
+	private byte tier = 0;
+	private Vector3f rot = new Vector3f(0.0f, 0.0f, 0.0f);
+	protected AtomicFloat health = new AtomicFloat();
+	public float healthMax;
+	protected boolean load = true;
+	protected ConcurrentHashMap<String, Effect> effects = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW);
+	private int objectTypeMask = 0;
+	private Bounds bounds;
+	
+	public int gridX = -1;
+	public int gridZ = -1;
+	
+	protected GridObjectType gridObjectType;
+	
+	protected float altitude = 0;
+	protected Regions region;
+	protected boolean movingUp = false;
+	public Regions landingRegion = null;
+	public Vector3fImmutable lastLoc = Vector3fImmutable.ZERO;
+
+	/**
+	 * No Id Constructor
+	 */
+	public AbstractWorldObject() {
+		super();
+	}
+
+	/**
+	 * Normal Constructor
+	 */
+	public AbstractWorldObject(int objectUUID) {
+		super(objectUUID);
+	}
+
+	
+	/**
+	 * ResultSet Constructor
+	 */
+	public AbstractWorldObject(ResultSet rs) throws SQLException {
+		super(rs);
+	}
+
+	//this should be called to handle any after load functions.
+	public abstract void runAfterLoad();
+
+	/*
+	 * Getters
+	 */
+	public float getHealth() {
+
+
+		return this.health.get();
+
+	}
+	public float getCurrentHitpoints(){
+		return this.health.get();
+	}
+
+	public float getHealthMax() {
+		return this.healthMax;
+	}
+
+	public ConcurrentHashMap<String, Effect> getEffects() {
+		return this.effects;
+	}
+
+	//Add new effect
+	public void addEffect(String name, int duration, AbstractScheduleJob asj, EffectsBase eb, int trains) {
+
+		if (!isAlive() && eb.getToken() != 1672601862) {
+			return;
+		}
+		JobContainer jc = JobScheduler.getInstance().scheduleJob(asj, duration);
+		Effect eff = new Effect(jc, eb, trains);
+		this.effects.put(name, eff);
+		applyAllBonuses();
+	}
+
+	public Effect addEffectNoTimer(String name, EffectsBase eb, int trains, boolean isStatic) {
+		NoTimeJob ntj = new NoTimeJob(this, name, eb, trains); //infinite timer
+
+		if (this.getObjectType() == GameObjectType.Item || this.getObjectType() == GameObjectType.City){
+			ntj.setEffectSourceType(this.getObjectType().ordinal());
+			ntj.setEffectSourceID(this.getObjectUUID());
+		}
+
+		JobContainer jc = new JobContainer(ntj);
+		Effect eff = new Effect(jc, eb, trains);
+		if (isStatic)
+			eff.setIsStatic(isStatic);
+		this.effects.put(name, eff);
+		applyAllBonuses();
+		return eff;
+	}
+
+	//called when an effect runs it's course
+	public void endEffect(String name) {
+
+		Effect eff = this.effects.get(name);
+		if (eff == null) {
+			return;
+		}
+		if (!isAlive() && eff.getEffectsBase().getToken() != 1672601862) {
+			return;
+		}
+
+		if (eff.cancel()) {
+
+			eff.endEffect();
+			this.effects.remove(name);
+			if (this.getObjectType().equals(GameObjectType.PlayerCharacter))
+			if (name.equals("Flight")){
+				((PlayerCharacter)this).update();
+				PlayerCharacter.GroundPlayer((PlayerCharacter)this);
+			}
+		}
+		applyAllBonuses();
+	}
+
+	public void endEffectNoPower(String name) {
+
+		Effect eff = this.effects.get(name);
+		if (eff == null) {
+			return;
+		}
+		if (!isAlive() && eff.getEffectsBase().getToken() != 1672601862) {
+			return;
+		}
+
+		if (eff.cancel()) {
+			eff.cancelJob();
+			eff.endEffectNoPower();
+			this.effects.remove(name);
+		}
+		applyAllBonuses();
+	}
+
+	//Called to cancel an effect prematurely.
+	public void cancelEffect(String name, boolean overwrite) {
+
+		Effect eff = this.effects.get(name);
+		if (eff == null) {
+			return;
+		}
+		if (!isAlive() && eff.getEffectsBase().getToken() != 1672601862) {
+			return;
+		}
+
+		if (eff.cancel()) {
+			eff.cancelJob();
+			this.effects.remove(name);
+			if (AbstractWorldObject.IsAbstractCharacter(this)) {
+				((AbstractCharacter) this).cancelLastChantIfSame(eff);
+			}
+		}
+		if (!overwrite) {
+			applyAllBonuses();
+		}
+	}
+
+	//Called when an object dies/is destroyed
+	public void clearEffects() {
+		for (String name : this.effects.keySet()) {
+			Effect eff = this.effects.get(name);
+			if (eff == null) {
+				return;
+			}
+
+			//Dont remove deathshroud here!
+			if (eff.getEffectToken() == 1672601862)
+				continue;
+
+			if (eff.cancel()) {
+				if (eff.getPower() == null) {
+					if (!eff.isStatic())
+						eff.endEffectNoPower();
+				}
+				if (!eff.isStatic())
+					eff.cancelJob();
+			}
+
+			this.effects.remove(name);
+		}
+		if (AbstractWorldObject.IsAbstractCharacter(this)) {
+			((AbstractCharacter) this).cancelLastChant();
+		}
+		applyAllBonuses();
+	}
+
+	public void removeEffectBySource(EffectSourceType source, int trains, boolean removeAll) {
+		if (!isAlive() && source.equals(EffectSourceType.DeathShroud) == false) {
+			return;
+		}
+
+		//hacky way to dispell trebs.
+		if (this.getObjectType() == GameObjectType.Mob){
+			Mob mob = (Mob)this;
+			if (mob.isSiege()){
+				if (mob.isPet()){
+					PlayerCharacter petOwner = mob.getOwner();
+					if (petOwner != null && source.equals(EffectSourceType.Effect)){
+						petOwner.dismissPet();
+						return;
+					}
+				}
+			}
+		}
+		boolean changed = false;
+		String toRemove = "";
+		int toRemoveToken = Integer.MAX_VALUE;
+		for (String name : this.effects.keySet()) {
+			Effect eff = this.effects.get(name);
+			if (eff == null) {
+				continue;
+			}
+			if (eff.containsSource(source) && trains >= eff.getTrains()) {
+				if (removeAll) {
+					//remove all effects of source type
+					if (eff.cancel()) {
+						eff.cancelJob();
+					}
+					this.effects.remove(name);
+					changed = true;
+					
+					if (source.equals("Flight")){
+						//ground player
+						if (this.getObjectType().equals(GameObjectType.PlayerCharacter)){
+							((PlayerCharacter)this).update();
+							PlayerCharacter.GroundPlayer((PlayerCharacter)this);
+						}
+					}
+				} else {
+					//find lowest token of source type to remove
+					int tok = eff.getEffectToken();
+					if (tok != 0 && tok < toRemoveToken) {
+						toRemove = name;
+						toRemoveToken = tok;
+					}
+				}
+			}
+		}
+		
+		//WTF IS THIS?
+		if (toRemoveToken < Integer.MAX_VALUE && this.effects.containsKey(toRemove)) {
+			//remove lowest found token of source type
+			Effect eff = this.effects.get(toRemove);
+			if (eff != null) {
+				changed = true;
+				if (eff.cancel()) {
+					eff.cancelJob();
+				}
+				this.effects.remove(toRemove);
+				
+				if (source.equals("Flight")){
+					//ground player
+					if (this.getObjectType().equals(GameObjectType.PlayerCharacter)){
+						((PlayerCharacter)this).update();
+						PlayerCharacter.GroundPlayer((PlayerCharacter)this);
+					}
+				}
+				
+			}
+		}
+		if (changed) {
+			applyAllBonuses();
+		}
+	}
+
+	public void sendAllEffects(ClientConnection cc) {
+		UpdateEffectsMsg msg = new UpdateEffectsMsg(this);
+		Dispatch dispatch = Dispatch.borrow((PlayerCharacter)this, msg);
+		DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.PRIMARY);
+	}
+
+	public void applyAllBonuses() {
+		if (AbstractWorldObject.IsAbstractCharacter(this)) {
+			((AbstractCharacter) this).applyBonuses();
+		}
+	}
+
+	public JobContainer getEffectJobContainer(String name) {
+		Effect ef = this.effects.get(name);
+		if (ef != null) {
+			return ef.getJobContainer();
+		}
+		return null;
+	}
+
+	public AbstractScheduleJob getEffectJob(String name) {
+		Effect ef = this.effects.get(name);
+		if (ef == null) {
+			return null;
+		}
+		JobContainer jc = ef.getJobContainer();
+		if (jc != null) {
+			return (AbstractScheduleJob) jc.getJob();
+		}
+		return null;
+	}
+
+	public boolean containsEffect(int token) {
+		for (Effect eff : this.effects.values()) {
+			if (eff != null) {
+				if (eff.getEffectsBase() != null) {
+					if (eff.getEffectsBase().getToken() == token) {
+						return true;
+					}
+				}
+			}
+		}
+		return false;
+	}
+
+	public int getObjectTypeMask() {
+		return objectTypeMask;
+	}
+
+
+	public Vector3fImmutable getLoc() {
+		return this.loc;
+	}
+
+	public Vector3f getRot() {
+		return rot;
+	}
+
+	public byte getTier() {
+		return tier;
+	}
+
+	public boolean isAlive() {
+		if (AbstractWorldObject.IsAbstractCharacter(this)) {
+			return this.isAlive();
+		} else if (this.getObjectType().equals(GameObjectType.Building)) {
+			return (!(((Building) this).getRank() < 0));
+		} else {
+			return true;
+		}
+	}
+
+	/*
+	 * Setters
+	 */
+	public void setObjectTypeMask(int mask) {
+		this.objectTypeMask = mask;
+	}
+
+	//TODO return false if something goes wrong? resync player?
+	public void setLoc(Vector3fImmutable loc) {
+		locationLock.writeLock().lock();
+		try {
+			if (Float.isNaN(loc.x) || Float.isNaN(loc.z))
+				return;
+			
+			if (loc.equals(Vector3fImmutable.ZERO))
+				return;
+			
+			if (loc.x > MBServerStatics.MAX_WORLD_WIDTH || loc.z < MBServerStatics.MAX_WORLD_HEIGHT)
+				return;
+			this.lastLoc = new Vector3fImmutable(this.loc);
+			this.loc = loc;
+			this.loc = this.loc.setY(HeightMap.getWorldHeight(this) + this.getAltitude());
+			
+			//lets not add mob to world grid if he is currently despawned.
+			if (this.getObjectType().equals(GameObjectType.Mob) && ((Mob)this).despawned)
+				return;
+			
+			//do not add objectUUID 0 to world grid. dunno da fuck this doing why its doing but its doing... da fuck.
+			if (this.getObjectUUID() == 0)
+				return;
+			WorldGrid.addObject(this,loc.x,loc.z);
+			
+		}catch(Exception e){
+			Logger.error("Failed to set location for World Object. Type = " + this.getObjectType().name() + " : Name = " + this.getName());
+			e.printStackTrace();
+		} finally {
+			locationLock.writeLock().unlock();
+		}
+		
+	}
+	
+	public void setY(float y){
+		this.loc = this.loc.setY(y);
+	}
+
+
+	public void setRot(Vector3f rotation) {
+		synchronized (this.rot) {
+			this.rot = rotation;
+		}
+	}
+
+	public void setTier(byte tier) {
+		synchronized (this.rot) {
+			this.tier = tier;
+		}
+	}
+
+	public static int getType() {
+		return 0;
+	}
+
+	public boolean load() {
+		return this.load;
+	}
+
+	/*
+	 * Utils
+	 */
+	public String getName() {
+		if (this.name.length() == 0) {
+			return "Unnamed " + '('
+					+ this.getObjectUUID() + ')';
+		} else {
+			return this.name;
+		}
+	}
+
+	public String getSimpleName() {
+		return this.name;
+	}
+
+
+	/**
+	 * @return the bounds
+	 */
+	public Bounds getBounds() {
+		return bounds;
+	}
+
+	public void setBounds(Bounds bounds) {
+
+		this.bounds = bounds;
+	}
+
+	/**
+	 * @param health the health to set
+	 */
+	public void setHealth(float health) {
+
+		this.health.set(health);
+	}
+
+	public static boolean IsAbstractCharacter(AbstractWorldObject awo){
+		
+		if (awo == null)
+			return false;
+
+		if (awo.getObjectType() == GameObjectType.PlayerCharacter || awo.getObjectType() == GameObjectType.Mob || awo.getObjectType() == GameObjectType.NPC)
+			return true;
+		return false;
+	}
+
+	public static void RemoveFromWorldGrid(AbstractWorldObject gridObjectToRemove){
+		if (gridObjectToRemove.gridX < 0 || gridObjectToRemove.gridZ < 0)
+			return;
+		
+		ConcurrentHashMap<Integer,AbstractWorldObject> gridMap;
+		switch(gridObjectToRemove.gridObjectType){
+		case STATIC:
+			gridMap = WorldGrid.StaticGridMap[gridObjectToRemove.gridX][gridObjectToRemove.gridZ];
+			break;
+		case DYNAMIC:
+				gridMap = WorldGrid.DynamicGridMap[gridObjectToRemove.gridX][gridObjectToRemove.gridZ];
+			break;
+		default:
+			gridMap = WorldGrid.StaticGridMap[gridObjectToRemove.gridX][gridObjectToRemove.gridZ];
+			break;
+		
+		}
+		
+		if (gridMap == null){
+			Logger.info("Null gridmap for Object UUD: " + gridObjectToRemove);
+			return;
+		}
+			
+		
+	
+		
+		gridMap.remove(gridObjectToRemove.getObjectUUID());
+		gridObjectToRemove.gridX = -1;
+		gridObjectToRemove.gridZ = -1;
+		
+	}
+	
+	public static boolean AddToWorldGrid(AbstractWorldObject gridObjectToAdd, int x, int z){
+		try{
+			
+			ConcurrentHashMap<Integer,AbstractWorldObject> gridMap;
+            if (gridObjectToAdd.gridObjectType.equals(GridObjectType.STATIC))
+				gridMap = WorldGrid.StaticGridMap[x][z];
+			else
+				gridMap = WorldGrid.DynamicGridMap[x][z];
+			
+			gridMap.put(gridObjectToAdd.getObjectUUID(),gridObjectToAdd);
+			gridObjectToAdd.gridX = x;
+			gridObjectToAdd.gridZ = z;
+			return true;
+		}catch(Exception e){
+			Logger.error(e);
+			return false;
+		}
+		
+	}
+	
+	public static Regions GetRegionByWorldObject(AbstractWorldObject worldObject){
+		Regions region = null;
+		
+		if (worldObject.getObjectType().equals(GameObjectType.PlayerCharacter))
+			if (((PlayerCharacter)worldObject).isFlying())
+				return null;
+		//Find building
+		for (AbstractWorldObject awo:WorldGrid.getObjectsInRangePartial(worldObject.getLoc(), MBServerStatics.STRUCTURE_LOAD_RANGE, MBServerStatics.MASK_BUILDING)){
+		Building building = (Building)awo;
+		if (!Bounds.collide(worldObject.getLoc(), building.getBounds()))
+			continue;
+		
+		//find regions that intersect x and z, check if object can enter.
+		for (Regions toEnter: building.getBounds().getRegions()){
+			if (toEnter.isPointInPolygon(worldObject.getLoc())){
+				if (Regions.CanEnterRegion(worldObject, toEnter))
+					if (region == null)
+						region = toEnter;
+					else // we're using a low level to high level tree structure, database not always in order low to high.
+						//check for highest level index.
+					if(region != null && toEnter.highLerp.y > region.highLerp.y)
+					region = toEnter;
+					
+				
+			}
+		}
+	}
+	
+		//set players new altitude to region lerp altitude.
+		if (region != null)
+			if (region.center.y == region.highLerp.y)
+				worldObject.loc = worldObject.loc.setY(region.center.y + worldObject.getAltitude());
+			else
+			worldObject.loc = worldObject.loc.setY(region.lerpY(worldObject) + worldObject.getAltitude());
+		
+		return region;
+	}
+	
+	public static Regions GetRegionFromBuilding(Vector3fImmutable worldLoc, Building building){
+		Regions region = null;
+		
+		
+		return region;
+	}
+
+	public float getAltitude() {
+		return altitude;
+	}
+
+
+	public ReadWriteLock getUpdateLock() {
+		return updateLock;
+	}
+
+	public GridObjectType getGridObjectType() {
+		return gridObjectType;
+	}
+
+	public Regions getRegion() {
+		return region;
+	}
+
+
+	public boolean isMovingUp() {
+		return movingUp;
+	}
+
+	public void setMovingUp(boolean movingUp) {
+		this.movingUp = movingUp;
+	}
+
+	public void setRegion(Regions region) {
+		this.region = region;
+	}
+	
+	//used for interestmanager loading and unloading objects to client.
+	// if not in grid, unload from player.
+	public boolean isInWorldGrid(){
+		if (this.gridX == -1 && this.gridZ == -1)
+			return false;
+		
+		return true;
+	}
+	
+
+}
diff --git a/src/engine/objects/Account.java b/src/engine/objects/Account.java
new file mode 100644
index 00000000..f910faf2
--- /dev/null
+++ b/src/engine/objects/Account.java
@@ -0,0 +1,390 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.objects;
+
+import engine.Enum;
+import engine.Enum.DispatchChannel;
+import engine.Enum.ItemContainerType;
+import engine.gameManager.ConfigManager;
+import engine.gameManager.DbManager;
+import engine.gameManager.SessionManager;
+import engine.net.Dispatch;
+import engine.net.DispatchMessage;
+import engine.net.client.ClientConnection;
+import engine.net.client.ClientMessagePump;
+import engine.net.client.msg.*;
+import engine.util.ByteUtils;
+import org.pmw.tinylog.Logger;
+
+import java.io.UnsupportedEncodingException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class Account extends AbstractGameObject {
+
+	private final String uname;
+	private String passwd;
+	private int lastCharIDUsed;
+	private String salt;
+	public String discordAccount;
+	private byte loginAttempts = 0;
+	private long lastLoginFailure = System.currentTimeMillis();
+	public HashMap<Integer, PlayerCharacter> characterMap = new HashMap<>();
+	public static ConcurrentHashMap<String, Integer> AccountsMap = new ConcurrentHashMap<>();
+	private ArrayList<Item> vault = new ArrayList<>();
+	public Item vaultGold = null;
+	public long lastPasswordCheck = 0;
+	public Enum.AccountStatus status;
+
+	public ArrayList<Item> getVault() {
+		return vault;
+	}
+
+	public Account(ResultSet resultSet) throws SQLException {
+		super(resultSet);
+
+		this.uname = resultSet.getString("acct_uname");
+		this.passwd = resultSet.getString("acct_passwd");
+		this.lastCharIDUsed = resultSet.getInt("acct_lastCharUID");
+		this.salt = resultSet.getString("acct_salt");
+		this.discordAccount = resultSet.getString("discordAccount");
+		this.status = Enum.AccountStatus.valueOf(resultSet.getString("status"));
+	}
+
+	public String getUname() {
+		return uname;
+	}
+
+	public String getPasswd() {
+		return passwd;
+	}
+
+	public String getSalt() {
+		return salt;
+	}
+
+	public int getLastCharIDUsed() {
+		return lastCharIDUsed;
+	}
+
+	public byte getLoginAttempts() {
+		return loginAttempts;
+	}
+
+	public long getLastLoginFailure() {
+		return this.lastLoginFailure;
+	}
+
+	public void setLastCharIDUsed(int lastCharIDUsed) {
+		this.lastCharIDUsed = lastCharIDUsed;
+	}
+
+	public void setLastLoginFailure() {
+		this.lastLoginFailure = System.currentTimeMillis();
+	}
+
+	public void setLastCharacter(int uuid) {
+		this.lastCharIDUsed = uuid;
+		//		this.updateDatabase();
+	}
+
+	public void incrementLoginAttempts() {
+		++this.loginAttempts;
+		this.setLastLoginFailure();
+	}
+
+	public void resetLoginAttempts() {
+		this.loginAttempts = 0;
+	}
+
+	/*
+	 * on successfully matching the password, this method additionally calls to
+	 * associateIpToAccount for IPAddress tracking. dokks
+	 */
+	public boolean passIsValid(String pw, String ip, String machineID) throws IllegalArgumentException {
+		boolean result = false;
+		// see if it was entered in plain text first, if the plain text matches,
+		// hash it and save to the database.
+		try {
+			pw = ByteUtils.byteArrayToSafeStringHex(MessageDigest
+					.getInstance("md5").digest(pw.getBytes("UTF-8")))
+					+ salt;
+			pw = ByteUtils.byteArrayToSafeStringHex(MessageDigest
+					.getInstance("md5").digest(pw.getBytes()));
+			result = this.passwd.equals(pw);
+		} catch (    NoSuchAlgorithmException | UnsupportedEncodingException e) {
+			Logger.error( e.toString());
+		}
+
+		if (result) {
+			// TODO: should use an executor here so that we can
+			// fire and forget this update.
+			// this is a valid user, so let's also update the
+			// database with login time and IP.
+			if((ip==null)||(ip.length()==0)) {
+				throw new IllegalArgumentException();
+			}
+		}
+		return result;
+	}
+
+	public ClientConnection getClientConnection() {
+		return SessionManager.getClientConnection(this);
+	}
+
+	public PlayerCharacter getPlayerCharacter() {
+		return SessionManager.getPlayerCharacter(this);
+	}
+
+	@Override
+	public void updateDatabase() {
+		DbManager.AccountQueries.updateDatabase(this);
+	}
+
+	//this should be called to handle any after load functions.
+
+	public void runAfterLoad() {
+
+		try {
+
+			if (ConfigManager.serverType.equals(Enum.ServerType.LOGINSERVER)){
+				ArrayList<PlayerCharacter> playerList = DbManager.PlayerCharacterQueries.GET_CHARACTERS_FOR_ACCOUNT(this.getObjectUUID());
+
+				for(PlayerCharacter player:playerList) {
+					PlayerCharacter.initializePlayer(player);
+					this.characterMap.putIfAbsent(player.getObjectUUID(), player);
+				}
+
+				playerList.clear();
+			}
+
+			if (ConfigManager.serverType.equals(Enum.ServerType.WORLDSERVER)) {
+				this.vault = DbManager.ItemQueries.GET_ITEMS_FOR_ACCOUNT(this.getObjectUUID());
+
+				for (Item item : this.vault) {
+					if (item.getItemBase().getUUID() == 7) {
+						this.vaultGold = item;
+					}
+				}
+
+				if (this.vaultGold == null) {
+					this.vaultGold = Item.newGoldItem(this.getObjectUUID(), ItemBase.getItemBase(7), ItemContainerType.VAULT);
+
+					if (this.vaultGold != null)
+						this.vault.add(this.vaultGold);
+				}
+			}
+
+		} catch (Exception e) {
+			Logger.error( e);
+		}
+	}
+
+	public synchronized void transferItemFromInventoryToVault(TransferItemFromInventoryToVaultMsg msg, ClientConnection origin) {
+
+		PlayerCharacter player = origin.getPlayerCharacter();
+		Dispatch dispatch;
+
+		if (player == null)
+			return;
+
+		if (!ClientMessagePump.NPCVaultBankRangeCheck(player, origin, "vault")) {
+			ClientMessagePump.forceTransferFromVaultToInventory(msg, origin, "You are out of range of the vault.");
+			return;
+		}
+
+		int uuid = msg.getUUID();
+		Item item = Item.getFromCache(uuid);
+
+		if (item == null) {
+			ClientMessagePump.forceTransferFromVaultToInventory(msg, origin, "Can't find the item.");
+			return;
+		}
+
+		//dupe check
+		if (!item.validForInventory(origin, player, player.getCharItemManager()))
+			return;
+
+		if (item.containerType == Enum.ItemContainerType.INVENTORY && player.getCharItemManager().isVaultOpen()) {
+			if (!player.getCharItemManager().hasRoomVault(item.getItemBase().getWeight())) {
+				ClientMessagePump.forceTransferFromVaultToInventory(msg, origin, "There is no room in your vault.");
+				return;
+			}
+
+			if (player.getCharItemManager().moveItemToVault(item)) {
+				this.vault.add(item);
+				dispatch = Dispatch.borrow(player, msg);
+				DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
+			} else
+				ClientMessagePump.forceTransferFromVaultToInventory(msg, origin, "Failed to transfer item.");
+		}
+	}
+
+	public synchronized void transferItemFromVaultToInventory(TransferItemFromVaultToInventoryMsg msg, ClientConnection origin) {
+
+		PlayerCharacter player = origin.getPlayerCharacter();
+		Dispatch dispatch;
+
+		if (player == null)
+			return;
+
+		if (!ClientMessagePump.NPCVaultBankRangeCheck(player, origin, "vault")) {
+			ClientMessagePump.forceTransferFromInventoryToVault(msg, origin, "You are out of range of the vault.");
+			return;
+		}
+
+		CharacterItemManager itemManager = player.getCharItemManager();
+
+		if (itemManager == null) {
+			ClientMessagePump.forceTransferFromInventoryToVault(msg, origin, "Can't find your item manager.");
+			return;
+		}
+
+		Item item = Item.getFromCache(msg.getUUID());
+
+		if (item == null) {
+			ClientMessagePump.forceTransferFromInventoryToVault(msg, origin, "Can't find the item.");
+			return;
+		}
+
+		//dupe check
+		if (!item.validForVault(origin, player, itemManager))
+			return;
+
+		if (item.containerType == Enum.ItemContainerType.VAULT && itemManager.isVaultOpen()) {
+			if (!itemManager.hasRoomInventory(item.getItemBase().getWeight())) {
+				ClientMessagePump.forceTransferFromInventoryToVault(msg, origin, "There is no room in your inventory.");
+				return;
+			}
+			if (itemManager.moveItemToInventory(item)) {
+				this.vault.remove(item);
+
+				dispatch = Dispatch.borrow(player, msg);
+				DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
+
+			} else
+				ClientMessagePump.forceTransferFromInventoryToVault(msg, origin, "Failed to transfer item.");
+		}
+	}
+
+	public synchronized void transferGoldFromVaultToInventory(TransferGoldFromVaultToInventoryMsg msg, ClientConnection origin) {
+
+		PlayerCharacter player = origin.getPlayerCharacter();
+		Dispatch dispatch;
+
+		if (player == null)
+			return;
+
+		Account account = player.getAccount();
+
+		if (account == null)
+			return;
+
+		if (!ClientMessagePump.NPCVaultBankRangeCheck(player, origin, "vault"))
+			return;
+
+		NPC npc = player.getLastNPCDialog();
+
+		if (npc == null)
+			return;
+
+		CharacterItemManager itemManager = player.getCharItemManager();
+
+		if (itemManager == null)
+			return;
+
+		if (itemManager.isVaultOpen() == false)
+			return;
+
+		if (itemManager.moveGoldToInventory(itemManager.getGoldVault(), msg.getAmount()) == false)
+			return;
+
+		OpenVaultMsg open = new OpenVaultMsg(player, npc);
+		ShowVaultInventoryMsg show = new ShowVaultInventoryMsg(player, account, npc); // 37??
+
+		UpdateGoldMsg ugm = new UpdateGoldMsg(player);
+		ugm.configure();
+		dispatch = Dispatch.borrow(player, ugm);
+		DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
+
+		UpdateVaultMsg uvm = new UpdateVaultMsg(account);
+		dispatch = Dispatch.borrow(player, uvm);
+		DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
+
+		dispatch = Dispatch.borrow(player, open);
+		DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
+
+		dispatch = Dispatch.borrow(player, show);
+		DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
+
+	}
+
+	public synchronized void transferGoldFromInventoryToVault(TransferGoldFromInventoryToVaultMsg msg, ClientConnection origin) {
+
+		PlayerCharacter player = origin.getPlayerCharacter();
+		Dispatch dispatch;
+
+		if (player == null)
+			return;
+
+		Account account = player.getAccount();
+
+		if (account == null)
+			return;
+
+		if (!ClientMessagePump.NPCVaultBankRangeCheck(player, origin, "vault"))
+			return;
+
+		CharacterItemManager itemManager = player.getCharItemManager();
+
+		if (itemManager == null)
+			return;
+
+		NPC npc = player.getLastNPCDialog();
+
+		if (npc == null)
+			return;
+
+		// Cannot have bank and vault open concurrently
+		// Dupe prevention
+
+		if (itemManager.isVaultOpen() == false)
+			return;
+
+		// Something went horribly wrong.  Should be log this?
+
+		if (itemManager.moveGoldToVault(itemManager.getGoldInventory(), msg.getAmount()) == false)
+			return;
+
+		UpdateGoldMsg ugm = new UpdateGoldMsg(player);
+		ugm.configure();
+		dispatch = Dispatch.borrow(player, ugm);
+		DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
+
+		UpdateVaultMsg uvm = new UpdateVaultMsg(account);
+		dispatch = Dispatch.borrow(player, uvm);
+		DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
+
+		OpenVaultMsg open = new OpenVaultMsg(player, npc);
+		dispatch = Dispatch.borrow(player, open);
+		DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
+		//
+		//
+		ShowVaultInventoryMsg show = new ShowVaultInventoryMsg(player, account, npc); // 37??
+		dispatch = Dispatch.borrow(player, show);
+		DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
+
+	}
+}
diff --git a/src/engine/objects/Bane.java b/src/engine/objects/Bane.java
new file mode 100644
index 00000000..22370a67
--- /dev/null
+++ b/src/engine/objects/Bane.java
@@ -0,0 +1,650 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+
+package engine.objects;
+
+import engine.Enum;
+import engine.Enum.ProtectionState;
+import engine.Enum.SiegePhase;
+import engine.Enum.SiegeResult;
+import engine.InterestManagement.HeightMap;
+import engine.InterestManagement.WorldGrid;
+import engine.db.archive.BaneRecord;
+import engine.db.archive.DataWarehouse;
+import engine.gameManager.BuildingManager;
+import engine.gameManager.ChatManager;
+import engine.gameManager.DbManager;
+import engine.gameManager.ZoneManager;
+import engine.job.JobScheduler;
+import engine.jobs.ActivateBaneJob;
+import engine.jobs.BaneDefaultTimeJob;
+import engine.math.Vector3fImmutable;
+import engine.net.DispatchMessage;
+import engine.net.client.ClientConnection;
+import engine.net.client.msg.PlaceAssetMsg;
+import engine.net.client.msg.chat.ChatSystemMsg;
+import engine.server.MBServerStatics;
+import org.joda.time.DateTime;
+import org.pmw.tinylog.Logger;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.concurrent.ConcurrentHashMap;
+
+public final class Bane {
+
+    private final int cityUUID;
+    private int ownerUUID;
+    private final int stoneUUID;
+    private DateTime placementDate = null;
+    private DateTime liveDate = null;
+    private BaneDefaultTimeJob defaultTimeJob;
+    private ActivateBaneJob activateBaneJob;
+
+    // Internal cache for banes
+    
+    public static ConcurrentHashMap<Integer, Bane> banes = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW);
+
+    /**
+     * ResultSet Constructor
+     */
+    public Bane(ResultSet rs) throws SQLException {
+
+        Date sqlDateTime;
+        ActivateBaneJob abtj;
+
+        this.cityUUID = rs.getInt("cityUUID");
+        this.ownerUUID = rs.getInt("ownerUUID");
+        this.stoneUUID = rs.getInt("stoneUUID");
+
+        sqlDateTime = rs.getTimestamp("placementDate");
+
+        if (sqlDateTime != null)
+            this.placementDate = new DateTime(sqlDateTime);
+
+        sqlDateTime = rs.getTimestamp("liveDate");
+
+        if (sqlDateTime != null)
+            this.liveDate = new DateTime(sqlDateTime);
+
+        switch (this.getSiegePhase()) {
+
+            case CHALLENGE:
+                break;
+            case WAR:
+
+                // cancel old job if exists
+
+                if (this.activateBaneJob != null)
+                    this.activateBaneJob.cancelJob();
+
+                abtj = new ActivateBaneJob(cityUUID);
+                JobScheduler.getInstance().scheduleJob(abtj, this.liveDate.getMillis());
+                this.activateBaneJob = abtj;
+
+                break;
+            case STANDOFF:
+
+                // cancel old job if exists
+
+                if (this.activateBaneJob != null)
+                    this.activateBaneJob.cancelJob();
+
+                abtj = new ActivateBaneJob(cityUUID);
+                JobScheduler.getInstance().scheduleJob(abtj, this.liveDate.getMillis());
+                this.activateBaneJob = abtj;
+
+                break;
+        }
+
+        if (this.liveDate == null)
+            setDefaultTime();
+    }
+
+    public static boolean summonBanestone(PlayerCharacter player, ClientConnection origin, int rank) {
+
+        Guild baningGuild;
+        Zone cityZone;
+        City targetCity;
+
+        ArrayList<Bane> nationBanes;
+
+        baningGuild = player.getGuild();
+
+        if (baningGuild.getNation().isErrant()) {
+            PlaceAssetMsg.sendPlaceAssetError( origin, 55, ""); // You must be in a Nation
+            return false;
+        }
+
+        if (baningGuild.getNation().isNPCGuild()) {
+            PlaceAssetMsg.sendPlaceAssetError( origin, 72, ""); // Cannot be in an NPC nation
+            return false;
+        }
+
+        if (GuildStatusController.isInnerCouncil(player.getGuildStatus()) == false) {
+            PlaceAssetMsg.sendPlaceAssetError( origin, 10, ""); // You must be a guild leader
+            return false;
+        }
+
+        // Cannot place banestones underwater;
+
+        if (HeightMap.isLocUnderwater(player.getLoc())) {
+            PlaceAssetMsg.sendPlaceAssetError(origin, 6, ""); // Cannot place underwater
+            return false;
+        }
+
+        //  figure out which city we're standing on
+        //  must be within a city's seige Bounds
+
+        targetCity = ZoneManager.getCityAtLocation(player.getLoc());
+
+        if (targetCity == null) {
+            PlaceAssetMsg.sendPlaceAssetError( origin, 59, ""); // No city to siege at this location!
+            return false;
+        }
+
+        if (targetCity.isSafeHold()) {
+            PlaceAssetMsg.sendPlaceAssetError( origin, 15, ""); // Cannot place assets in peace zone
+            return false;
+        }
+
+        if (targetCity.getRank() > rank) {
+            PlaceAssetMsg.sendPlaceAssetError( origin, 60, ""); // Bane rank is too low
+            return false;
+        }
+
+        cityZone = targetCity.getParent();
+
+        // Cannot place assets on a dead tree
+
+        if ((cityZone.isPlayerCity()) &&
+                (City.getCity(cityZone.getPlayerCityUUID()).getTOL().getRank() == -1)) {
+            PlaceAssetMsg.sendPlaceAssetError(origin, 1, "Cannot bane a dead tree!");
+            return false;
+        }
+
+        if (baningGuild.getNation() == targetCity.getGuild().getNation()) {
+            PlaceAssetMsg.sendPlaceAssetError( origin, 20, ""); //Cannot bane yourself!
+            return false;
+        }
+
+        if (targetCity.getTOL() == null) {
+            PlaceAssetMsg.sendPlaceAssetError( origin, 65, ""); // Cannot find tree to target
+            return false;
+        }
+
+        if (targetCity.getBane() != null) {
+            PlaceAssetMsg.sendPlaceAssetError( origin, 23, ""); // Tree is already baned.
+            return false;
+        }
+
+        if (getBaneByAttackerGuild(baningGuild) != null) {
+            PlaceAssetMsg.sendPlaceAssetError( origin, 1, "Your guild has already placed a bane!");
+            return false;
+        }
+
+        nationBanes = getBanesByNation(baningGuild.getNation());
+
+        // A nation can only have 3 concurrent banes
+
+        if (nationBanes.size() == 3) {
+            PlaceAssetMsg.sendPlaceAssetError( origin, 64, ""); // Your nation is already at war and your limit has been reached
+            return false;
+        }
+
+        if (targetCity.isLocationOnCityGrid(player.getLoc()) == true) {
+            PlaceAssetMsg.sendPlaceAssetError( origin, 1, "Cannot place banestone on city grid.");
+            return false;
+        }
+
+        Blueprint blueprint = Blueprint.getBlueprint(24300);  // Banestone
+
+        //Let's drop a banestone!
+
+        Vector3fImmutable localLocation = ZoneManager.worldToLocal(player.getLoc(), cityZone);
+
+        if (localLocation == null) {
+            PlaceAssetMsg.sendPlaceAssetError(player.getClientConnection(), 1, "A Serious error has occurred. Please post details for to ensure transaction integrity");
+            Logger.info("Failed to Convert World coordinates to local zone coordinates");
+            return false;
+        }
+
+        Building stone = DbManager.BuildingQueries.CREATE_BUILDING(
+                cityZone.getObjectUUID(), player.getObjectUUID(), blueprint.getName(), blueprint.getBlueprintUUID(),
+                localLocation, 1.0f, blueprint.getMaxHealth(rank), ProtectionState.PROTECTED, 0, rank,
+                null, blueprint.getBlueprintUUID(), 1, 0.0f);
+
+        if (stone == null) {
+            PlaceAssetMsg.sendPlaceAssetError(player.getClientConnection(), 1, "A Serious error has occurred. Please post details for to ensure transaction integrity");
+            return false;
+        }
+
+        stone.addEffectBit((1 << 19));
+        stone.setMaxHitPoints(stone.getBlueprint().getMaxHealth(stone.getRank()));
+        stone.setCurrentHitPoints(stone.getMaxHitPoints());
+        BuildingManager.setUpgradeDateTime(stone, null, 0);
+
+        //Make the bane
+
+        Bane bane = makeBane(player, targetCity, stone);
+
+        if (bane == null) {
+            //delete bane stone, failed to make bane object
+            DbManager.BuildingQueries.DELETE_FROM_DATABASE(stone);
+            PlaceAssetMsg.sendPlaceAssetError(player.getClientConnection(), 1, "A Serious error has occurred. Please post details for to ensure transaction integrity");
+            return false;
+        }
+
+        WorldGrid.addObject(stone, player);
+
+        //Add bane effect to TOL
+
+        targetCity.getTOL().addEffectBit((1 << 16));
+        targetCity.getTOL().updateEffects();
+        
+        Vector3fImmutable movePlayerOutsideStone = player.getLoc();
+        movePlayerOutsideStone = movePlayerOutsideStone.setX(movePlayerOutsideStone.x + 10);
+        movePlayerOutsideStone = movePlayerOutsideStone.setZ(movePlayerOutsideStone.z + 10);
+        player.teleport(movePlayerOutsideStone);
+
+        // Notify players
+
+        ChatSystemMsg chatMsg = new ChatSystemMsg(null, "[Bane Channel] " + targetCity.getCityName() + " has been baned by " + baningGuild.getName() + ". Standoff phase has begun!");
+        chatMsg.setMessageType(4);
+        chatMsg.setChannel(Enum.ChatChannelType.SYSTEM.getChannelID());
+
+        DispatchMessage.dispatchMsgToAll(chatMsg);
+
+        // Push this event to the DataWarehouse
+
+        BaneRecord baneRecord = BaneRecord.borrow(bane, Enum.RecordEventType.PENDING);
+        DataWarehouse.pushToWarehouse(baneRecord);
+
+        return true;
+    }
+
+    public SiegePhase getSiegePhase() {
+
+        SiegePhase phase;
+
+        if (this.liveDate == null) {
+            phase = SiegePhase.CHALLENGE; //challenge
+            return phase;
+        }
+
+        if (DateTime.now().isAfter(this.liveDate)) {
+            phase = SiegePhase.WAR; //war 
+            return phase;
+        }
+
+        // If code reaches this point we are in standoff mode.
+        phase = SiegePhase.STANDOFF; //standoff   
+
+        return phase;
+    }
+
+    private void setDefaultTime() {
+
+        DateTime timeToSetDefault = new DateTime(this.placementDate);
+        timeToSetDefault = timeToSetDefault.plusDays(1);
+
+        DateTime currentTime = DateTime.now();
+        DateTime defaultTime = new DateTime(this.placementDate);
+        defaultTime = defaultTime.plusDays(2);
+        defaultTime = defaultTime.hourOfDay().setCopy(22);
+        defaultTime = defaultTime.minuteOfHour().setCopy(0);
+        defaultTime = defaultTime.secondOfMinute().setCopy(0);
+
+        if (currentTime.isAfter(timeToSetDefault))
+            this.setLiveDate(defaultTime);
+        else {
+
+            if (this.defaultTimeJob != null)
+                this.defaultTimeJob.cancelJob();
+
+            BaneDefaultTimeJob bdtj = new BaneDefaultTimeJob(this);
+            JobScheduler.getInstance().scheduleJob(bdtj, timeToSetDefault.getMillis());
+            this.defaultTimeJob = bdtj;
+        }
+    }
+
+    public void setLiveDate(DateTime baneTime) {
+
+        if (DbManager.BaneQueries.SET_BANE_TIME(baneTime, this.getCity().getObjectUUID())) {
+            this.liveDate = new DateTime(baneTime);
+
+            // Push event to warehouse
+
+            BaneRecord.updateLiveDate(this, baneTime);
+
+            ChatManager.chatGuildInfo(this.getOwner().getGuild(), "The bane on " + this.getCity().getGuild().getName() + " has been set! Standoff phase has begun!");
+            Guild attackerNation = this.getOwner().getGuild().getNation();
+
+            if (attackerNation != null)
+
+                for (Guild subGuild : attackerNation.getSubGuildList()) {
+
+                    //Don't send the message twice.
+                    if (subGuild.equals(attackerNation))
+                        continue;
+
+                    ChatManager.chatGuildInfo(subGuild, "The bane on " + this.getCity().getGuild().getName() + " has been set! Standoff phase has begun!");
+                }
+
+            ChatManager.chatGuildInfo(this.getCity().getGuild(), "The bane on " + this.getCity().getGuild().getName() + " has been set! Standoff phase has begun!");
+
+            Guild defenderNation = this.getCity().getGuild().getNation();
+
+            if (defenderNation != null) {
+
+                if (defenderNation != this.getCity().getGuild())
+                    ChatManager.chatGuildInfo(defenderNation, "The bane on " + this.getCity().getGuild().getName() + " has been set! Standoff phase has begun!");
+
+                for (Guild subGuild : defenderNation.getSubGuildList()) {
+                    //Don't send the message twice.
+                    if (subGuild == this.getCity().getGuild())
+                        continue;
+                    ChatManager.chatGuildInfo(subGuild, "The bane on " + this.getCity().getGuild().getName() + " has been set! Standoff phase has begun!");
+                }
+            }
+
+            if (activateBaneJob != null)
+                this.activateBaneJob.cancelJob();
+
+            ActivateBaneJob abtj = new ActivateBaneJob(cityUUID);
+
+            JobScheduler.getInstance().scheduleJob(abtj, this.liveDate.getMillis());
+            this.activateBaneJob = abtj;
+        } else {
+            Logger.debug( "error with city " + this.getCity().getName());
+            ChatManager.chatGuildInfo(this.getOwner().getGuild(), "A Serious error has occurred. Please post details for to ensure transaction integrity");
+            ChatManager.chatGuildInfo(this.getCity().getGuild(), "A Serious error has occurred. Please post details for to ensure transaction integrity");
+        }
+
+    }
+
+    /*
+     * Getters
+     */
+    public City getCity() {
+        return City.getCity(this.cityUUID);
+    }
+
+    public PlayerCharacter getOwner() {
+        return (PlayerCharacter) DbManager.getObject(Enum.GameObjectType.PlayerCharacter, ownerUUID);
+    }
+
+    //Call this to prematurely end a bane
+    
+    public boolean remove() {
+
+        Building baneStone;
+
+        baneStone = this.getStone();
+
+        if (baneStone == null) {
+            Logger.debug("Removing bane without a stone.");
+            return false;
+        }
+
+        // Reassert protection contracts
+        
+        this.getCity().protectionEnforced = true;
+
+        // Remove visual effects on Bane and TOL.
+        
+        this.getStone().removeAllVisualEffects();
+        this.getStone().updateEffects();
+
+        this.getCity().getTOL().removeAllVisualEffects();
+        this.getCity().getTOL().updateEffects();
+        
+        // Remove bane from the database
+        
+        if (DbManager.BaneQueries.REMOVE_BANE(this) == false) {
+            Logger.error("Database call failed for city UUID: " + this.getCity().getObjectUUID());
+            return false;
+        }
+
+        // Remove bane from ingame cache
+
+        Bane.banes.remove(cityUUID);
+        
+        // Delete stone from database
+        
+        if (DbManager.BuildingQueries.DELETE_FROM_DATABASE(baneStone) == false) {
+            Logger.error( "Database error when deleting stone object");
+            return false;
+        }
+
+        // Remove object from simulation
+        
+        baneStone.removeFromCache();
+        WorldGrid.RemoveWorldObject(baneStone);
+        WorldGrid.removeObject(baneStone);
+        return true;
+    }
+
+    // Cache access
+    
+    public static Bane getBane(int cityUUID) {
+
+        Bane outBane;
+
+        // Check cache first
+
+        outBane = banes.get(cityUUID);
+
+        // Last resort attempt to load from database
+
+        if (outBane == null)
+            outBane = DbManager.BaneQueries.LOAD_BANE(cityUUID);
+        else
+            return outBane;
+
+        // As we loaded from the db, store it in the internal cache
+
+        if (outBane != null)
+            banes.put(cityUUID, outBane);
+
+        return outBane;
+    }
+
+    //Returns a guild's bane
+    
+    public static Bane getBaneByAttackerGuild(Guild guild) {
+            
+    	
+    	if (guild == null || guild.isErrant())
+    		return null;
+        ArrayList<Bane> baneList;
+        
+        baneList = new ArrayList<>(banes.values());
+        
+            for (Bane bane : baneList) {
+                if (bane.getOwner().getGuild().equals(guild))
+                    return bane;
+            }
+            
+        return null;
+    }
+
+    public static ArrayList<Bane> getBanesByNation(Guild guild) {
+
+        ArrayList<Bane> baneList;
+        ArrayList<Bane> returnList;
+        
+        baneList = new ArrayList<>(banes.values());
+        returnList = new ArrayList<>();
+        
+            for (Bane bane : baneList) {
+                if (bane.getOwner().getGuild().getNation().equals(guild))
+                    returnList.add(bane);
+            }
+            
+        return returnList;
+        }
+
+    public static void addBane(Bane bane) {
+
+        Bane.banes.put(bane.cityUUID, bane);
+
+    }
+
+    public static Bane makeBane(PlayerCharacter owner, City city, Building stone) {
+
+        Bane newBane;
+
+        if (DbManager.BaneQueries.CREATE_BANE(city, owner, stone) == false) {
+            Logger.error("Error writing to database");
+            return null;
+        }
+
+        newBane = DbManager.BaneQueries.LOAD_BANE(city.getObjectUUID());
+
+        return newBane;
+    }
+
+    public final DateTime getPlacementDate() {
+        return placementDate;
+    }
+
+    public final boolean isAccepted() {
+
+        return (this.getSiegePhase() != SiegePhase.CHALLENGE);
+    }
+
+    public final DateTime getLiveDate() {
+        return liveDate;
+    }
+
+    public final void endBane(SiegeResult siegeResult) {
+
+        boolean baneRemoved;
+
+        // No matter what the outcome of a bane, we re-asset
+        // protection contracts at this time.  They don't quite
+        // matter if the city falls, as they are invalidated.
+        
+        this.getCity().protectionEnforced = true;
+        
+        switch (siegeResult) {
+            case DEFEND:
+
+                // Push event to warehouse
+
+                BaneRecord.updateResolution(this, Enum.RecordEventType.DEFEND);
+
+                baneRemoved = remove();
+
+                if (baneRemoved) {
+
+                    // Update seieges withstood
+                
+                    this.getCity().setSiegesWithstood(this.getCity().getSiegesWithstood() + 1);
+                    
+                    // Notify players
+                    
+                ChatSystemMsg msg = new ChatSystemMsg(null, "[Bane Channel]" + this.getCity().getGuild().getName() + " has rallied against " + this.getOwner().getGuild().getName() + ". The siege on " + this.getCity().getCityName() + " has been broken!");
+                msg.setMessageType(4);
+                msg.setChannel(engine.Enum.ChatChannelType.SYSTEM.getChannelID());
+        
+                DispatchMessage.dispatchMsgToAll(msg);
+                
+                }
+                
+                break;
+            case CAPTURE:
+
+                // Push event to warehouse
+
+                BaneRecord.updateResolution(this, Enum.RecordEventType.CAPTURE);
+
+                baneRemoved = this.remove();
+
+                if (baneRemoved) {
+
+                ChatSystemMsg msg = new ChatSystemMsg(null, "[Bane Channel]" + this.getOwner().getGuild().getName() + " have defeated " + this.getCity().getGuild().getName() + " and captured " + this.getCity().getCityName() + '!');
+                msg.setMessageType(4);
+                msg.setChannel(engine.Enum.ChatChannelType.SYSTEM.getChannelID());
+        
+                DispatchMessage.dispatchMsgToAll(msg);
+                }
+                break;
+            case DESTROY:
+
+                // Push event to warehouse
+
+                BaneRecord.updateResolution(this, Enum.RecordEventType.DESTROY);
+
+                baneRemoved = this.remove();
+
+                if (baneRemoved) {
+
+                ChatSystemMsg msg = new ChatSystemMsg(null, "[Bane Channel]" + this.getOwner().getGuild().getName() + " have defeated " + this.getCity().getGuild().getName() + " and razed " + this.getCity().getCityName() + '!');
+                msg.setMessageType(4);
+                msg.setChannel(engine.Enum.ChatChannelType.SYSTEM.getChannelID());
+                
+                DispatchMessage.dispatchMsgToAll(msg);
+                }
+                break;
+        }
+
+        Zone cityZone = this.getCity().getParent();
+
+        if (cityZone == null)
+        	return;
+
+        //UNPROTECT ALL SIEGE EQUIPMENT AFTER A BANE
+        for (Building toUnprotect: cityZone.zoneBuildingSet){
+        	if (toUnprotect.getBlueprint() != null && toUnprotect.getBlueprint().isSiegeEquip() && toUnprotect.assetIsProtected() == true)
+        	toUnprotect.setProtectionState(ProtectionState.NONE);
+        }
+
+    }
+
+    public boolean isErrant() {
+
+        boolean isErrant = true;
+
+        if (this.getOwner() == null)
+            return isErrant;
+
+       
+        if (this.getOwner().getGuild().isErrant() == true)
+            return isErrant;
+
+        if (this.getOwner().getGuild().getNation().isErrant() == true)
+            return isErrant;
+
+        // Bane passes validation
+
+        isErrant = false;
+
+        return isErrant;
+    }
+
+    /**
+     * @return the stone
+     */
+    public Building getStone() {
+        return BuildingManager.getBuilding(this.stoneUUID);
+    }
+
+    /**
+     * @return the cityUUID
+     */
+    public int getCityUUID() {
+        return cityUUID;
+    }
+
+}
diff --git a/src/engine/objects/BaseClass.java b/src/engine/objects/BaseClass.java
new file mode 100644
index 00000000..a00c208e
--- /dev/null
+++ b/src/engine/objects/BaseClass.java
@@ -0,0 +1,220 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.objects;
+
+import engine.gameManager.DbManager;
+import engine.net.ByteBufferWriter;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+
+
+public class BaseClass extends AbstractGameObject {
+
+	private final String name;
+	private final String description;
+
+	private final byte strMod;
+	private final byte dexMod;
+	private final byte conMod;
+	private final byte intMod;
+	private final byte spiMod;
+
+	private final float healthMod;
+	private final float manaMod;
+	private final float staminaMod;
+
+	private int token = 0;
+
+	private final ArrayList<SkillReq> skillsGranted;
+	private final ArrayList<PowerReq> powersGranted;
+	private ArrayList<MobBaseEffects> effectsList = new ArrayList<>();
+
+
+	/**
+	 * No Table ID Constructor
+	 */
+	public BaseClass(String name, String description, byte strMod, byte dexMod, byte conMod, byte intMod, byte spiMod,
+			ArrayList<RuneBase> allowedRunes, ArrayList<SkillReq> skillsGranted, ArrayList<PowerReq> powersGranted) {
+		super();
+		this.name = name;
+		this.description = description;
+		this.strMod = strMod;
+		this.dexMod = dexMod;
+		this.conMod = conMod;
+		this.intMod = intMod;
+		this.spiMod = spiMod;
+		this.healthMod = 1;
+		this.manaMod = 1;
+		this.staminaMod = 1;
+
+		this.skillsGranted = skillsGranted;
+		this.powersGranted = powersGranted;
+
+	}
+
+	/**
+	 * Normal Constructor
+	 */
+	public BaseClass(String name, String description, byte strMod, byte dexMod, byte conMod, byte intMod, byte spiMod,
+			ArrayList<RuneBase> allowedRunes, ArrayList<SkillReq> skillsGranted, ArrayList<PowerReq> powersGranted, int newUUID) {
+		super(newUUID);
+		this.name = name;
+		this.description = description;
+		this.strMod = strMod;
+		this.dexMod = dexMod;
+		this.conMod = conMod;
+		this.intMod = intMod;
+		this.spiMod = spiMod;
+		this.healthMod = 1;
+		this.manaMod = 1;
+		this.staminaMod = 1;
+		this.skillsGranted = skillsGranted;
+		this.powersGranted = powersGranted;
+
+	}
+
+
+	/**
+	 * ResultSet Constructor
+	 */
+	public BaseClass(ResultSet rs) throws SQLException {
+		super(rs);
+
+		this.name = rs.getString("name");
+		this.description = rs.getString("description");
+		this.strMod = rs.getByte("strMod");
+		this.dexMod = rs.getByte("dexMod");
+		this.conMod = rs.getByte("conMod");
+		this.intMod = rs.getByte("intMod");
+		this.spiMod = rs.getByte("spiMod");
+		this.token = rs.getInt("token");
+		this.healthMod = rs.getInt("healthMod");
+		this.manaMod = rs.getInt("manaMod");
+		this.staminaMod = rs.getInt("staminaMod");
+		this.skillsGranted = DbManager.SkillReqQueries.GET_REQS_FOR_RUNE(this.getObjectUUID());
+		this.powersGranted = PowerReq.getPowerReqsForRune(this.getObjectUUID());
+		this.effectsList = (DbManager.MobBaseQueries.GET_RUNEBASE_EFFECTS(this.getObjectUUID()));
+
+	}
+
+	/*
+	 * Getters
+	 */
+	public String getName() {
+		return name;
+	}
+
+	public String getDescription() {
+		return description;
+	}
+
+	public byte getStrMod() {
+		return strMod;
+	}
+
+	public byte getDexMod() {
+		return dexMod;
+	}
+
+	public byte getConMod() {
+		return conMod;
+	}
+
+	public byte getIntMod() {
+		return intMod;
+	}
+
+	public byte getSpiMod() {
+		return spiMod;
+	}
+
+	public int getToken() {
+		return this.token;
+	}
+
+	public float getHealthMod() {
+		return this.healthMod;
+	}
+
+	public float getManaMod() {
+		return this.manaMod;
+	}
+
+	public float getStaminaMod() {
+		return this.staminaMod;
+	}
+
+	public ArrayList<Integer> getRuneList() {
+		return RuneBase.AllowedBaseClassRunesMap.get(this.getObjectUUID());
+	}
+
+	public ArrayList<SkillReq> getSkillsGranted() {
+		return this.skillsGranted;
+	}
+
+	public ArrayList<PowerReq> getPowersGranted() {
+		return this.powersGranted;
+	}
+
+	public ArrayList<RuneBaseEffect> getEffectsGranted() {
+		return  RuneBaseEffect.RuneIDBaseEffectMap.get(this.getObjectUUID());
+	}
+
+	/*
+	 * Utils
+	 */
+	public boolean isAllowedRune(RuneBase rb) {
+
+		if (this.getRuneList().contains(rb.getObjectUUID()))
+			return true;
+
+		if (RuneBase.AllowedBaseClassRunesMap.containsKey(111111)){
+			if (RuneBase.AllowedBaseClassRunesMap.get(111111).contains(rb.getObjectUUID()))
+				return true;
+		}
+		return false;
+	}
+
+	public static void LoadAllBaseClasses(){
+		DbManager.BaseClassQueries.GET_ALL_BASE_CLASSES();
+	}
+
+	/*
+	 * Serializing
+	 */
+	
+	public static void serializeForClientMsg(BaseClass baseClass, ByteBufferWriter writer) {
+		serializeForClientMsg(baseClass,writer, 3);
+	}
+
+	public static void serializeForClientMsg(BaseClass baseClass,ByteBufferWriter writer, int type) {
+		writer.putInt(type); // For BaseClass
+		writer.putInt(0); // Pad
+		writer.putInt(baseClass.getObjectUUID());
+		writer.putInt(baseClass.getObjectType().ordinal()); // Is this correct?
+		writer.putInt(baseClass.getObjectUUID());
+	}
+
+	public static BaseClass getBaseClass(final int UUID) {
+		return DbManager.BaseClassQueries.GET_BASE_CLASS(UUID);
+	}
+
+	@Override
+	public void updateDatabase() {
+		; //Never update..
+	}
+
+	public ArrayList<MobBaseEffects> getEffectsList() {
+		return effectsList;
+	}
+
+}
diff --git a/src/engine/objects/Blueprint.java b/src/engine/objects/Blueprint.java
new file mode 100644
index 00000000..f3c34d47
--- /dev/null
+++ b/src/engine/objects/Blueprint.java
@@ -0,0 +1,630 @@
+package engine.objects;
+
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+
+import engine.Enum.BuildingGroup;
+import engine.gameManager.DbManager;
+import engine.math.Vector2f;
+import org.pmw.tinylog.Logger;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.HashMap;
+
+/*  @Summary - Blueprint class is used for determining
+ characteristics of instanced player owned
+ structures such as available slots, upgrade
+ cost/time and the target window symbol icon.
+ */
+public class Blueprint {
+
+	public final static Vector2f IrikieForgeExtents = new Vector2f(32, 32);
+	public final static Vector2f IrikieBarracksExtents = new Vector2f(32, 32);
+
+	private static HashMap<Integer, Blueprint> _blueprints = new HashMap<>();
+	private static HashMap<Integer, Integer> _doorNumbers = new HashMap<>();
+	public static HashMap<Integer, Blueprint> _meshLookup = new HashMap<>();
+
+	private final int blueprintUUID;
+	private final String name;
+	private final BuildingGroup buildingGroup;
+	private final int icon;
+	private final int maxRank;
+	private final int maxSlots;
+	private final int rank1UUID;
+	private final int rank3UUID;
+	private final int rank7UUID;
+	private final int destroyedUUID;
+
+	private Blueprint() {
+		this.blueprintUUID = 0;
+		this.name = "";
+		this.icon = 0;
+		this.buildingGroup = BuildingGroup.BANESTONE;
+		this.maxRank = 0;
+		this.maxSlots = 0;
+		this.rank1UUID = 0;
+		this.rank3UUID = 0;
+		this.rank7UUID = 0;
+		this.destroyedUUID = 0;
+	}
+
+	public Blueprint(ResultSet rs) throws SQLException {
+
+		this.blueprintUUID = rs.getInt("Rank0UUID");
+		this.name = rs.getString("MeshName");
+		this.icon = rs.getInt("Icon");
+		this.buildingGroup = BuildingGroup.valueOf(rs.getString("BuildingGroup"));
+		this.maxRank = rs.getInt("MaxRank");
+		this.maxSlots = rs.getInt("MaxSlots");
+		this.rank1UUID = rs.getInt("Rank1UUID");
+		this.rank3UUID = rs.getInt("Rank3UUID");
+		this.rank7UUID = rs.getInt("Rank7UUID");
+		this.destroyedUUID = rs.getInt("DestroyedUUID");
+
+	}
+
+	// Accessors
+
+	public static Blueprint getBlueprint(int blueprintUUID) {
+
+		return _blueprints.get(blueprintUUID);
+
+	}
+
+	public static BuildingGroup getBuildingGroup(int blueprintUUID) {
+
+		Blueprint blueprint;
+
+		blueprint = _blueprints.get(blueprintUUID);
+
+        return blueprint.buildingGroup;
+    }
+
+	public static int getMaxShrines(int treeRank) {
+
+		// Returns the number of allowed spires/shrines
+		// for a given rank.
+
+		int maxShrines;
+
+		switch (treeRank) {
+			case 0:
+			case 1:
+			case 2:
+				maxShrines = 0;
+				break;
+			case 3:
+			case 4:
+				maxShrines = 1;
+				break;
+			case 5:
+			case 6:
+				maxShrines = 2;
+				break;
+			case 7:
+			case 8:
+				maxShrines = 3;
+				break;
+			default:
+				maxShrines = 0;
+
+		}
+
+		return maxShrines;
+	}
+
+	public static void loadAllBlueprints() {
+
+		_blueprints = DbManager.BlueprintQueries.LOAD_ALL_BLUEPRINTS();
+
+	}
+
+	// Method returns a blueprint based on a blueprintUUID
+
+	public static void loadAllDoorNumbers() {
+
+		_doorNumbers = DbManager.BlueprintQueries.LOAD_ALL_DOOR_NUMBERS();
+
+	}
+
+	public static int getDoorNumberbyMesh(int doorMeshUUID) {
+
+		if (_doorNumbers.containsKey(doorMeshUUID))
+			return _doorNumbers.get(doorMeshUUID);
+
+		return 0;
+	}
+
+	public static boolean isMeshWallPiece(int meshUUID) {
+
+		Blueprint buildingBlueprint = Blueprint.getBlueprint(meshUUID);
+
+		if (buildingBlueprint == null)
+			return false;
+
+        switch (buildingBlueprint.buildingGroup) {
+		case WALLSTRAIGHT:
+		case ARTYTOWER:
+		case WALLCORNER:
+		case SMALLGATE:
+		case WALLSTAIRS:
+			return true;
+		default:
+			break;
+		}
+		return false;
+
+	}
+
+	// Method calculates available vendor slots
+	// based upon the building's current rank
+
+	public static int getNpcMaintCost(int rank) {
+		int maintCost = Integer.MAX_VALUE;
+
+		maintCost = (9730 * rank) + 1890;
+
+		return maintCost;
+	}
+
+	public int getMaxRank() {
+		return maxRank;
+	}
+
+	public int getMaxSlots() {
+        if (this.buildingGroup != null && this.buildingGroup.equals(BuildingGroup.BARRACK))
+			return 1;
+		return maxSlots;
+	}
+
+	// Method returns a mesh UUID for this blueprint
+	// based upon a given rank.
+
+	public BuildingGroup getBuildingGroup() {
+		return this.buildingGroup;
+	}
+
+	// Method returns a cost to upgrade a building to a given rank
+	// based upon this blueprint's maintenance group
+
+	public int getMaxHealth(int currentRank) {
+
+		int maxHealth;
+
+		// Return 0 health for a destroyed building
+		// or 1 for a destroyed mine (cleint looting restriction)
+
+		if (currentRank == -1) {
+
+            return this.buildingGroup == BuildingGroup.MINE ? 1 : 0;
+		}
+
+		// Return 15k for a constructing mesh
+
+		if (currentRank == 0)
+			return 15000;
+
+		switch (this.buildingGroup) {
+
+		case TOL:
+			maxHealth = (70000 * currentRank) + 10000;
+			break;
+		case BARRACK:
+			maxHealth = (35000 * currentRank) + 5000;
+			break;
+		case BANESTONE:
+			maxHealth = (170000 * currentRank) - 120000;
+			break;
+		case CHURCH:
+			maxHealth = (28000 * currentRank) + 4000;
+			break;
+		case MAGICSHOP:
+		case FORGE:
+		case INN:
+		case TAILOR:
+			maxHealth = (17500 * currentRank) + 2500;
+			break;
+		case VILLA:
+		case ESTATE:
+		case FORTRESS:
+			maxHealth = 300000;
+			break;
+		case CITADEL:
+			maxHealth = 500000;
+			break;
+		case SPIRE:
+			maxHealth = (37000 * currentRank) - 9000;
+			break;
+		case GENERICNOUPGRADE:
+		case SHACK:
+		case SIEGETENT:
+			maxHealth = 40000;
+			break;
+		case BULWARK:
+			if (currentRank == 1)
+				maxHealth = 110000;
+			else
+				maxHealth = 40000;
+			break;
+		case WALLSTRAIGHT:
+		case WALLSTRAIGHTTOWER:
+		case WALLSTAIRS:
+			maxHealth = 1000000;
+			break;
+		case WALLCORNER:
+		case ARTYTOWER:
+			maxHealth = 900000;
+			break;
+		case SMALLGATE:
+			maxHealth = 1100000;
+			break;
+		case AMAZONHALL:
+		case CATHEDRAL:
+		case GREATHALL:
+		case KEEP:
+		case THIEFHALL:
+		case TEMPLEHALL:
+		case WIZARDHALL:
+		case ELVENHALL:
+		case ELVENSANCTUM:
+		case IREKEIHALL:
+		case FORESTHALL:
+			maxHealth = (28000 * currentRank) + 4000;
+			break;
+		case MINE:
+			maxHealth = 125000;
+			break;
+		case RUNEGATE:
+			maxHealth = 100000;
+			break;
+		case SHRINE:
+			maxHealth = 100000;
+			break;
+		case WAREHOUSE:
+			maxHealth = 40000;
+			break;
+
+		default:
+			maxHealth = 40000;
+			break;
+
+		}
+		return maxHealth;
+	}
+
+	// Returns number of vendor slots available
+	// for the building's current rank.
+
+	public int getSlotsForRank(int currentRank) {
+
+		int availableSlots;
+
+		// Early exit for buildings not yet constructed
+
+		if (currentRank == 0)
+			return 0;
+
+		// Early exit for buildings with single or no slots
+
+		if (this.maxSlots <= 1)
+			return maxSlots;
+
+		if (this.maxRank == 1 && currentRank == 1)
+			return getMaxSlots();
+
+		switch (currentRank) {
+
+		case 1:
+		case 2:
+			availableSlots = 1;
+			break;
+		case 3:
+			case 4:
+			case 5:
+			case 6:
+			availableSlots = 2;
+			break;
+		case 7:
+			availableSlots = 3;
+			break;
+		case 8:
+			availableSlots = 1;
+			break;
+		default:
+			availableSlots = 0;
+			break;
+		}
+
+		return availableSlots;
+	}
+
+	// Returns the half extents of this blueprint's
+	// bounding box, based upon it's buildinggroup
+
+	public int getIcon() {
+		return this.icon;
+	}
+
+	public String getName() {
+		return this.name;
+	}
+
+	public int getMeshForRank(int targetRank) {
+
+		int targetMesh = this.blueprintUUID;
+
+		// The Blueprint UUID is the 'constructing' mesh so
+		// we return that value if the rank passed is 0.
+
+        if ((maxRank == 1) && (this.rank1UUID == 0)) {
+            return blueprintUUID;
+        }
+
+		// Set the return value to the proper mesh UID for rank
+
+		switch (targetRank) {
+
+		case -1:
+			targetMesh = this.destroyedUUID; // -1 Rank is a destroyed mesh
+			break;
+		case 0:
+			targetMesh = this.blueprintUUID; // Rank 0 is the 'constructing' mesh
+			break;
+		case 1:
+		case 2:
+			targetMesh = this.rank1UUID;
+			break;
+		case 3:
+		case 4:
+		case 5:
+		case 6:
+			targetMesh = this.rank3UUID;
+			break;
+		case 7:
+		case 8:
+			targetMesh = this.rank7UUID;
+			break;
+		default:
+			break;
+		}
+
+		return targetMesh;
+	}
+
+	public int getRankCost(int targetRank) {
+
+		// Set a MAXINT rankcost in case something goes wrong
+
+		int rankCost = Integer.MAX_VALUE;
+
+		// Sanity chack for retrieving a rankcost outside proper range
+
+        if ((targetRank > maxRank) || (targetRank < 0)) {
+			Logger.error( "Attempt to retrieve rankcost for rank of" + targetRank);
+			return rankCost;
+		}
+
+		// Select linear equation for rank cost based upon the
+		// buildings current Maintenance BuildingGroup.
+
+		switch (this.buildingGroup) {
+
+		case GENERICNOUPGRADE:
+		case WALLSTRAIGHT:
+		case WALLSTAIRS:
+		case WALLCORNER:
+		case SMALLGATE:
+		case ARTYTOWER:
+		case SIEGETENT:
+		case BULWARK:
+		case BANESTONE:
+		case SHACK:
+			break; // This set cannot be upgraded.  Returns max integer.
+
+		case TOL:
+			rankCost = (880000 * targetRank) - 440000;
+			break;
+		case BARRACK:
+		case VILLA:
+		case ESTATE:
+		case FORTRESS:
+		case CITADEL:
+			rankCost = (451000 * targetRank) - 308000;
+			break;
+		case CHURCH:
+			rankCost = (682000 * targetRank) - 110000;
+			break;
+		case FORGE:
+		case INN:
+		case TAILOR:
+		case MAGICSHOP:
+			rankCost = (440000 * targetRank) - 550000;
+			break;
+		case SPIRE:
+			rankCost = (176000 * targetRank) - 88000;
+			break;
+		case AMAZONHALL:
+		case CATHEDRAL:
+		case GREATHALL:
+		case KEEP:
+		case THIEFHALL:
+		case TEMPLEHALL:
+		case WIZARDHALL:
+		case ELVENHALL:
+		case ELVENSANCTUM:
+		case IREKEIHALL:
+		case FORESTHALL:
+			rankCost = (682000 * targetRank) - 110000;
+			break;
+		default:
+            Logger.error("Attempt to retrieve rankcost without MaintGroup for " + this.buildingGroup.name());
+			break;
+		}
+
+		return rankCost;
+	}
+
+	public int getRankTime(int targetRank) {
+
+		// Set a very long rankTime in case something goes wrong
+
+		int rankTime = (Integer.MAX_VALUE / 2);
+
+		// Set all initial construction to a default of 4 hours.
+
+		if (targetRank == 1)
+			return 4;
+
+		// Sanity chack for retrieving a ranktime outside proper range
+
+        if ((targetRank > maxRank) || (targetRank < 1)) {
+			Logger.error( "Attempt to retrieve ranktime for rank of" + targetRank);
+			return rankTime;
+		}
+
+		// Select equation for rank time based upon the
+		// buildings current Maintenance BuildingGroup.  These values
+		// are expressed in hours
+
+		switch (this.buildingGroup) {
+
+		case GENERICNOUPGRADE:
+			break; // Cannot be upgraded
+		case VILLA:
+		case ESTATE:
+		case FORTRESS:
+		case CITADEL:
+			rankTime = (7 * targetRank) - 7;
+			break;
+		case TOL:
+			rankTime = (7 * targetRank) - 7;
+			break;
+		case BARRACK:
+			rankTime = (7 * targetRank) - 7;
+			break;
+		case CHURCH:
+			rankTime = (7 * targetRank) - 7;
+			break;
+		case FORGE:
+		case INN:
+		case TAILOR:
+		case MAGICSHOP:
+			rankTime = (7 * targetRank) - 7;
+			break;
+		case SPIRE:
+			rankTime = (4 * targetRank) + 4;
+			break;
+		case AMAZONHALL:
+		case CATHEDRAL:
+		case GREATHALL:
+		case KEEP:
+		case THIEFHALL:
+		case TEMPLEHALL:
+		case WIZARDHALL:
+		case ELVENHALL:
+		case ELVENSANCTUM:
+		case IREKEIHALL:
+		case FORESTHALL:
+			rankTime = (7 * targetRank) - 7;
+			break;
+		default:
+			Logger.error("Attempt to retrieve ranktime without MaintGroup");
+			break;
+		}
+
+		return rankTime;
+	}
+
+	public Vector2f getExtents() {
+
+        if (blueprintUUID == 1302600)
+			return Blueprint.IrikieForgeExtents;
+		else if (blueprintUUID == 1300600)
+			return Blueprint.IrikieBarracksExtents;
+
+		return this.buildingGroup.getExtents();
+
+	}
+
+	public boolean isWallPiece() {
+
+        switch (this.buildingGroup) {
+		case WALLSTRAIGHT:
+		case WALLSTAIRS:
+		case ARTYTOWER:
+		case WALLCORNER:
+		case SMALLGATE:
+			return true;
+		default:
+			break;
+		}
+		return false;
+	}
+
+	public boolean isSiegeEquip() {
+
+        switch (this.buildingGroup) {
+		case BULWARK:
+		case SIEGETENT:
+			return true;
+		default:
+			break;
+		}
+		return false;
+
+	}
+
+	public int getBlueprintUUID() {
+		return blueprintUUID;
+	}
+
+
+	@Override
+	public boolean equals(Object object) {
+
+		if ((object instanceof Blueprint) == false)
+			return false;
+
+		Blueprint blueprint = (Blueprint) object;
+
+        return this.blueprintUUID == blueprint.blueprintUUID;
+	}
+
+	@Override
+	public int hashCode() {
+
+		return this.blueprintUUID ;
+	}
+
+	public int getMaintCost(int rank) {
+
+		int maintCost = Integer.MAX_VALUE;
+
+        switch (this.buildingGroup) {
+		case TOL:
+		case BARRACK:
+			maintCost = (61500 * rank) + 19500;
+			break;
+		case SPIRE:
+			maintCost = (4800 * rank) + 1200;
+			break;
+		default:
+            if (maxRank == 1)
+				maintCost = 22500;
+			else
+				maintCost = (15900 * rank) + 3300;
+			break;
+		}
+
+		return maintCost;
+	}
+}
diff --git a/src/engine/objects/Boon.java b/src/engine/objects/Boon.java
new file mode 100644
index 00000000..3c3bcb6d
--- /dev/null
+++ b/src/engine/objects/Boon.java
@@ -0,0 +1,64 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.objects;
+
+import engine.Enum.ShrineType;
+import engine.gameManager.DbManager;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.HashMap;
+
+
+
+public class Boon  {
+
+	private ShrineType shrineType;
+	private int amount;
+	private int itemBaseID;
+	public static HashMap<Integer,ArrayList<Boon>> GetBoonsForItemBase = new HashMap<>();
+
+
+	/**
+	 * ResultSet Constructor
+	 */
+	public Boon(ResultSet rs) throws SQLException {
+
+		this.shrineType = ShrineType.valueOf(rs.getString("shrineType"));
+		this.itemBaseID = rs.getInt("itemBaseID");
+		this.amount = rs.getInt("amount");
+	}
+
+	public int getAmount() {
+		return this.amount;
+	}
+
+	
+
+	public int getItemBaseID() {
+		return itemBaseID;
+	}
+
+
+	public ShrineType getShrineType() {
+		return shrineType;
+	}
+	
+	
+	public static void HandleBoonListsForItemBase(int itemBaseID){
+		ArrayList<Boon> boons = null;
+		boons = DbManager.BoonQueries.GET_BOON_AMOUNTS_FOR_ITEMBASEUUID(itemBaseID);
+		if (boons != null)
+			GetBoonsForItemBase.put(itemBaseID, boons);
+	}
+
+	
+}
diff --git a/src/engine/objects/Building.java b/src/engine/objects/Building.java
new file mode 100644
index 00000000..d9fca2d5
--- /dev/null
+++ b/src/engine/objects/Building.java
@@ -0,0 +1,1761 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+
+package engine.objects;
+
+import engine.Enum;
+import engine.Enum.*;
+import engine.InterestManagement.HeightMap;
+import engine.InterestManagement.RealmMap;
+import engine.InterestManagement.WorldGrid;
+import engine.db.archive.CityRecord;
+import engine.db.archive.DataWarehouse;
+import engine.db.archive.MineRecord;
+import engine.gameManager.BuildingManager;
+import engine.gameManager.DbManager;
+import engine.gameManager.ZoneManager;
+import engine.job.JobContainer;
+import engine.job.JobScheduler;
+import engine.jobs.DoorCloseJob;
+import engine.jobs.SiegeSpireWithdrawlJob;
+import engine.math.Bounds;
+import engine.math.Vector3f;
+import engine.math.Vector3fImmutable;
+import engine.net.ByteBufferWriter;
+import engine.net.DispatchMessage;
+import engine.net.client.msg.ApplyBuildingEffectMsg;
+import engine.net.client.msg.UpdateObjectMsg;
+import engine.server.MBServerStatics;
+import org.pmw.tinylog.Logger;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+
+public class Building extends AbstractWorldObject {
+
+	// Used for thread safety
+
+	private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
+
+	/*  The Blueprint class has methods able to derive
+	 *  all defining characteristics of this building,
+	 */
+	private int blueprintUUID = 0;
+	public int meshUUID;
+	private float w = 1.0f;
+	private Vector3f meshScale = new Vector3f(1.0f, 1.0f, 1.0f);
+	private int doorState = 0;
+	private int ownerUUID = 0;  //NPC or Character--check ownerIsNPC flag
+	private int _strongboxValue = 0;
+	private int maxGold;
+	private int effectFlags = 0;
+	private String name = "";
+	private int rank;
+	private boolean ownerIsNPC = true;
+	private boolean spireIsActive = false;
+	public Zone parentZone;
+	public boolean reverseKOS;
+	public int reserve = 0;
+
+	// Variables NOT to be stored in db
+
+	protected Resists resists;
+	public float statLat;
+	public float statLon;
+	public float statAlt;
+	private ConcurrentHashMap<String, JobContainer> timers = null;
+	private ConcurrentHashMap<String, Long> timestamps = null;
+	private final ConcurrentHashMap<AbstractCharacter, Integer> hirelings = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW);
+	private final HashMap<Integer, DoorCloseJob> doorJobs = new HashMap<>();
+	private ConcurrentHashMap<Integer,BuildingFriends> friends = new ConcurrentHashMap<>();
+	private ConcurrentHashMap<Integer,Condemned> condemned = new ConcurrentHashMap<>();
+	public LocalDateTime upgradeDateTime = null;
+	public LocalDateTime taxDateTime = null;
+	private ProtectionState protectionState = ProtectionState.NONE;
+
+	public ArrayList<Vector3fImmutable> patrolPoints = new ArrayList<>();
+	public ArrayList<Vector3fImmutable> sentryPoints = new ArrayList<>();
+	public TaxType taxType = TaxType.NONE;
+	public int taxAmount;
+	public boolean enforceKOS = false;
+
+	public int parentBuildingID;
+	public boolean isFurniture = false;
+
+	public int floor;
+	public int level;
+	public HashMap<Integer,Integer> fidelityNpcs = new HashMap<>();
+	public AtomicBoolean isDeranking = new AtomicBoolean(false);
+	private ArrayList<Building> children = null;
+	public LocalDateTime maintDateTime;
+
+	/**
+	 * ResultSet Constructor
+	 */
+
+	public Building(ResultSet rs) throws SQLException {
+		super(rs);
+
+		float scale;
+		Blueprint blueprint = null;
+
+		try {
+			this.meshUUID = rs.getInt("meshUUID");
+			this.setObjectTypeMask(MBServerStatics.MASK_BUILDING);
+			this.blueprintUUID = rs.getInt("blueprintUUID");
+			this.gridObjectType = GridObjectType.STATIC;
+			this.parentZone = DbManager.ZoneQueries.GET_BY_UID(rs.getLong("parent"));
+			this.name = rs.getString("name");
+			this.ownerUUID = rs.getInt("ownerUUID");
+
+			// Orphaned Object Sanity Check
+			//This was causing ABANDONED Tols.
+			//        if (objectType == DbObjectType.INVALID)
+			//            this.ownerUUID = 0;
+
+			this.doorState = rs.getInt("doorState");
+			this.setHealth(rs.getInt("currentHP"));
+			this.w = rs.getFloat("w");
+			this.setRot(new Vector3f(0f, rs.getFloat("rotY"), 0f));
+			this.reverseKOS = rs.getByte("reverseKOS") == 1 ? true : false;
+
+			scale = rs.getFloat("scale");
+			this.meshScale = new Vector3f(scale, scale, scale);
+
+			this.rank = rs.getInt("rank");
+			this.parentBuildingID = rs.getInt("parentBuildingID");
+			
+			//create a new list if the building is a parent and not a child.
+
+			if (this.parentBuildingID == 0)
+				this.children = new ArrayList<>();
+
+			this.floor = rs.getInt("floor");
+			this.level = rs.getInt("level");
+			this.isFurniture = (rs.getBoolean("isFurniture"));
+
+			// Lookup building blueprint
+
+			if (this.blueprintUUID == 0)
+				blueprint = Blueprint._meshLookup.get(meshUUID);
+			else
+				blueprint = this.getBlueprint();
+
+			// Log error if something went horrible wrong
+
+			if ((this.blueprintUUID != 0) && (blueprint == null))
+				Logger.error( "Invalid blueprint for object: " + this.getObjectUUID());
+
+			// Note: We handle R8 tree edge case for mesh and health
+			// after city is loaded to avoid recursive result set call
+			// in City resulting in a stack ovreflow.
+
+			if (blueprint != null) {
+
+				// Only switch mesh for player dropped structures
+
+				if (this.blueprintUUID != 0)
+					this.meshUUID = blueprint.getMeshForRank(rank);
+
+				this.healthMax = blueprint.getMaxHealth(this.rank);
+
+				// If this object has no blueprint but is a blueprint
+                // mesh then set it's current health to max health
+
+				if (this.blueprintUUID == 0)
+                    this.setHealth(healthMax);
+
+				if (blueprint.getBuildingGroup().equals(BuildingGroup.BARRACK))
+					this.patrolPoints = DbManager.BuildingQueries.LOAD_PATROL_POINTS(this);
+
+			} else{
+			    this.healthMax = 100000;  // Structures with no blueprint mesh
+                this.setHealth(healthMax);
+            }
+
+			// Null out blueprint if not needed (npc building)
+
+			if (blueprintUUID == 0)
+				blueprint = null;
+
+			resists = new Resists("Building");
+			this.statLat = rs.getFloat("locationX");
+			this.statAlt = rs.getFloat("locationY");
+			this.statLon = rs.getFloat("locationZ");
+
+			if (this.parentZone != null){
+				if (this.parentBuildingID != 0){
+					Building parentBuilding = BuildingManager.getBuilding(this.parentBuildingID);
+					if (parentBuilding != null){
+						this.setLoc(new Vector3fImmutable(this.statLat + this.parentZone.absX + parentBuilding.statLat, this.statAlt + this.parentZone.absY + parentBuilding.statAlt, this.statLon + this.parentZone.absZ + parentBuilding.statLon));
+					}else{
+						this.setLoc(new Vector3fImmutable(this.statLat + this.parentZone.absX, this.statAlt + this.parentZone.absY, this.statLon + this.parentZone.absZ));
+
+					}
+				} else {
+
+					// Altitude of this building is derived from the heightmap engine.
+
+					Vector3fImmutable tempLoc = new Vector3fImmutable(this.statLat + this.parentZone.absX, 0, this.statLon + this.parentZone.absZ);
+					tempLoc = new Vector3fImmutable(tempLoc.x, HeightMap.getWorldHeight(tempLoc), tempLoc.z);
+					this.setLoc(tempLoc);
+				}
+			}
+
+			this._strongboxValue = rs.getInt("currentGold");
+			this.maxGold = 15000000; // *** Refactor to blueprint method
+			this.reserve = rs.getInt("reserve");
+
+			// Does building have a protection contract?
+			this.taxType = TaxType.valueOf(rs.getString("taxType"));
+			this.taxAmount = rs.getInt("taxAmount");
+			this.protectionState = ProtectionState.valueOf(rs.getString("protectionState"));
+
+			java.sql.Timestamp maintTimeStamp = rs.getTimestamp("maintDate");
+
+			if (maintTimeStamp != null)
+				this.maintDateTime = LocalDateTime.ofInstant(maintTimeStamp.toInstant(), ZoneId.systemDefault());
+
+			java.sql.Timestamp taxTimeStamp = rs.getTimestamp("taxDate");
+
+			if (taxTimeStamp != null)
+				this.taxDateTime = LocalDateTime.ofInstant(taxTimeStamp.toInstant(), ZoneId.systemDefault());
+
+			java.sql.Timestamp upgradeTimeStamp = rs.getTimestamp("upgradeDate");
+
+			if (upgradeTimeStamp != null)
+				this.upgradeDateTime = LocalDateTime.ofInstant(upgradeTimeStamp.toInstant(), ZoneId.systemDefault());
+
+		} catch (Exception e) {
+
+			Logger.error( "Failed for object " + this.blueprintUUID + ' ' + this.getObjectUUID() + e.toString());
+		}
+	}
+
+	/*
+	 * Getters
+	 */
+
+	public final boolean isRanking() {
+
+		return this.upgradeDateTime != null;
+	}
+
+	public final int getRank() {
+		return rank;
+	}
+
+	public final int getOwnerUUID() {
+		return ownerUUID;
+	}
+
+	public final boolean isOwnerIsNPC() {
+		return ownerIsNPC;
+	}
+
+	public final City getCity() {
+
+		if (this.parentZone == null)
+			return null;
+
+		if (this.getBlueprint() != null && this.getBlueprint().isSiegeEquip() && this.protectionState.equals(ProtectionState.PROTECTED)){
+			if (this.getGuild() != null){
+				if (this.getGuild().getOwnedCity() != null){
+					if (this.getLoc().isInsideCircle(this.getGuild().getOwnedCity().getLoc(), Enum.CityBoundsType.SIEGE.extents))
+						return this.getGuild().getOwnedCity();
+				}else{
+					Bane bane = Bane.getBaneByAttackerGuild(this.getGuild());
+					
+					if (bane != null){
+						if (bane.getCity() != null){
+							if (this.getLoc().isInsideCircle(bane.getCity().getLoc(), Enum.CityBoundsType.SIEGE.extents))
+								return bane.getCity();
+						}
+					}
+				}
+			}
+		}
+		if (this.parentZone.isPlayerCity() == false)
+			return null;
+
+		return City.getCity(this.parentZone.getPlayerCityUUID());
+
+	}
+
+	public final String getCityName() {
+
+		City city = getCity();
+
+		if (city != null)
+			return city.getName();
+
+		return "";
+	}
+
+	public final Blueprint getBlueprint() {
+
+		if (this.blueprintUUID == 0)
+			return null;
+
+		return Blueprint.getBlueprint(this.blueprintUUID);
+
+	}
+
+	public final int getBlueprintUUID() {
+
+		return this.blueprintUUID;
+	}
+
+	public final void setCurrentHitPoints(Float CurrentHitPoints) {
+		this.addDatabaseJob("health", MBServerStatics.ONE_MINUTE);
+		this.setHealth(CurrentHitPoints);
+	}
+
+	public final LocalDateTime getUpgradeDateTime() {
+		lock.readLock().lock();
+		try {
+			return upgradeDateTime;
+		} finally {
+			lock.readLock().unlock();
+		}
+	}
+
+	public final float modifyHealth(final float value, final AbstractCharacter attacker) {
+
+		if (this.rank == -1)
+			return 0f;
+
+		boolean worked = false;
+		Float oldHealth=0f, newHealth=0f;
+		while (!worked) {
+			if (this.rank == -1)
+				return 0f;
+			oldHealth = this.health.get();
+			newHealth = oldHealth + value;
+			if (newHealth > this.healthMax)
+				newHealth = healthMax;
+			worked = this.health.compareAndSet(oldHealth, newHealth);
+		}
+
+		if (newHealth < 0) {
+			if (this.isDeranking.compareAndSet(false, true)) {
+				this.destroyOrDerank(attacker);
+			}
+
+			return newHealth - oldHealth;
+		} else
+			this.addDatabaseJob("health", MBServerStatics.ONE_MINUTE);
+
+		if (value < 0)
+			Mine.SendMineAttackMessage(this);
+
+		return newHealth - oldHealth;
+
+
+	}
+
+	//This method is to handle when a building is damaged below 0 health.
+	//Either destroy or derank it.
+
+	public final void destroyOrDerank(AbstractCharacter attacker) {
+
+		Blueprint blueprint;
+		City city;
+
+		// Sanity check: Early exit if a non
+		// blueprinted object is attempting to
+		// derank.
+
+		if (this.blueprintUUID == 0)
+			return;
+
+		blueprint = this.getBlueprint();
+		city = this.getCity();
+
+		// Special handling of destroyed Banes
+
+		if (blueprint.getBuildingGroup() == BuildingGroup.BANESTONE) {
+			city.getBane().endBane(SiegeResult.DEFEND);
+			return;
+		}
+
+		// Special handling of warehouses
+
+		if (blueprint.getBuildingGroup() == BuildingGroup.WAREHOUSE)
+			if (city != null)
+				city.setWarehouseBuildingID(0);
+
+		// Special handling of destroyed Spires
+
+		if ((blueprint.getBuildingGroup() == BuildingGroup.SPIRE) && this.rank == 1)
+			this.disableSpire(true);
+
+		// Special handling of destroyed Mines
+
+		if (blueprint.getBuildingGroup() == BuildingGroup.MINE
+				&& this.rank == 1) {
+
+			Mine mine = Mine.getMineFromTower(this.getObjectUUID());
+
+			if (mine != null) {
+
+				// Warehouse mine destruction event
+
+				MineRecord mineRecord = MineRecord.borrow(mine, attacker, RecordEventType.DESTROY);
+				DataWarehouse.pushToWarehouse(mineRecord);
+
+				this.setRank(-1);
+				this.setCurrentHitPoints((float) 1);
+				this.healthMax = (float) 1;
+				this.meshUUID = this.getBlueprint().getMeshForRank(this.rank);
+				mine.handleDestroyMine();
+				this.getBounds().setBounds(this);
+				this.refresh(true);
+				return;
+			}
+		}
+
+		// Special handling of deranking Trees
+
+		if (blueprint.getBuildingGroup() == BuildingGroup.TOL) {
+			derankTreeOfLife();
+			return;
+		}
+
+		// If codepath reaches here then it's a regular
+		//  structure not requiring special handling.
+		//  Time to either derank or destroy the building.
+
+		if ((this.rank - 1) < 1)
+			this.setRank(-1);
+		else
+			this.setRank(this.rank - 1);
+
+	}
+
+	private void derankTreeOfLife() {
+
+		City city;
+		Bane bane;
+		Realm cityRealm;ArrayList<Building> spireBuildings = new ArrayList<>();
+		ArrayList<Building> shrineBuildings = new ArrayList<>();
+		ArrayList<Building> barracksBuildings = new ArrayList<>();
+		Building spireBuilding;
+		Building shrineBuilding;
+		SiegeResult siegeResult;
+		AbstractCharacter newOwner;
+
+		city = this.getCity();
+
+		if (city == null) {
+			Logger.error("No city for tree of uuid" + this.getObjectUUID());
+			return;
+		}
+
+		bane = city.getBane();
+
+		// We need to collect the spires and shrines on the citygrid in case
+		// they will be deleted as excess as the tree deranks.
+
+		for (Building building : city.getParent().zoneBuildingSet) {
+
+			//dont add -1 rank buildings.
+			if (building.rank <= 0)
+				continue;
+			if (building.getBlueprint() != null && building.getBlueprint().getBuildingGroup() == BuildingGroup.SPIRE)
+				spireBuildings.add(building);
+
+			if (building.getBlueprint() != null && building.getBlueprint().getBuildingGroup() == BuildingGroup.SHRINE)
+				shrineBuildings.add(building);
+
+			if (building.getBlueprint() != null && building.getBlueprint().getBuildingGroup() == BuildingGroup.BARRACK)
+				barracksBuildings.add(building);
+		}
+
+		// A tree can only hold so many spires.  As it deranks we need to delete
+		// the excess
+
+		if (spireBuildings.size() > Blueprint.getMaxShrines(this.rank - 1)) {
+
+			spireBuilding = spireBuildings.get(0);
+
+			// Disable and delete a random spire
+
+			if (spireBuilding != null) {
+				spireBuilding.disableSpire(true);
+				spireBuilding.setRank(-1);
+			}
+		}
+
+		if (shrineBuildings.size() > Blueprint.getMaxShrines(this.rank - 1)) {
+
+			shrineBuilding = shrineBuildings.get(0);
+
+			// Delete a random shrine
+
+			if (shrineBuilding != null)
+				shrineBuilding.setRank(-1);
+		}
+
+		if (barracksBuildings.size() > this.rank - 1) {
+
+			Building barracksBuilding = barracksBuildings.get(0);
+
+			// Delete a random barrack
+
+			if (barracksBuilding != null)
+				barracksBuilding.setRank(-1);
+		}
+
+		// If the tree is R8 and deranking, we need to update it's
+		// mesh along with buildings losing their health bonus
+
+		if (this.rank == 8) {
+
+			cityRealm = city.getRealm();
+
+			if (cityRealm != null)
+				cityRealm.abandonRealm();
+
+			for (Building cityBuilding : this.parentZone.zoneBuildingSet) {
+
+				if ((cityBuilding.getBlueprint() != null && cityBuilding.getBlueprint().getBuildingGroup() != BuildingGroup.TOL)
+						&& (cityBuilding.getBlueprint().getBuildingGroup() != BuildingGroup.BANESTONE)) {
+					cityBuilding.healthMax = cityBuilding.getBlueprint().getMaxHealth(cityBuilding.rank);
+				}
+
+				if (cityBuilding.health.get() > cityBuilding.healthMax)
+					cityBuilding.setHealth(cityBuilding.healthMax);
+			}
+		}
+
+		// Tree is simply deranking.
+		// Let's do so and early exit
+
+		if (this.rank > 1) {
+			this.setRank(rank - 1);
+			City.lastCityUpdate = System.currentTimeMillis();
+			return;
+		}
+
+		// Handling of exploding TOL's
+
+		// Must remove a bane before considering destruction of a TOL
+
+		if (bane != null) {
+
+			// Cache the new owner
+
+			newOwner = Guild.GetGL(bane.getOwner().getGuild());
+
+			this.isDeranking.compareAndSet(false, true);
+
+			if ((bane.getOwner().getGuild().getGuildState() == GuildState.Sovereign) ||
+					(bane.getOwner().getGuild().getGuildState() == GuildState.Protectorate) ||
+					(bane.getOwner().getGuild().getGuildState() == GuildState.Province) ||
+					(bane.getOwner().getGuild().getGuildState() == GuildState.Nation))
+				siegeResult = SiegeResult.DESTROY;
+			else
+				siegeResult = SiegeResult.CAPTURE;
+
+			// Remove realm if city had one
+
+			Realm realm = RealmMap.getRealmAtLocation(city.getLoc());
+
+			if (realm != null)
+				if (realm.isRealmFullAfterBane())
+					siegeResult = SiegeResult.DESTROY;
+
+			city.getBane().endBane(siegeResult);
+
+			// If it's a capture bane transfer the tree and exit
+
+			if (siegeResult.equals(SiegeResult.CAPTURE)) {
+				city.transfer(newOwner);
+				CityRecord cityRecord = CityRecord.borrow(city, RecordEventType.CAPTURE);
+				DataWarehouse.pushToWarehouse(cityRecord);
+				return;
+			}
+		} // end removal of bane
+
+		//  if codepath reaches here then we can now destroy the tree and the city
+
+		CityRecord cityRecord = CityRecord.borrow(city, RecordEventType.DESTROY);
+		DataWarehouse.pushToWarehouse(cityRecord);
+
+		city.destroy();
+
+	}
+
+	public float getCurrentHitpoints(){
+		return this.health.get();
+	}
+
+	// Return the maint cost in gold associated with this structure
+
+	public int getMaintCost() {
+
+		int maintCost =0;
+
+		// Add cost for building structure
+
+		maintCost += this.getBlueprint().getMaintCost(rank);
+
+		// Add costs associated with hirelings
+
+		for (AbstractCharacter npc : this.hirelings.keySet()) {
+
+			if (npc.getObjectType() != GameObjectType.NPC)
+				continue;
+
+
+
+			maintCost += Blueprint.getNpcMaintCost(npc.getRank());
+		}
+
+		return maintCost;
+	}
+
+
+	public final void submitOpenDoorJob(int doorID) {
+
+		//cancel any outstanding door close jobs for this door
+
+		if (this.doorJobs.containsKey(doorID)) {
+			this.doorJobs.get(doorID).cancelJob();
+			this.doorJobs.remove(doorID);
+		}
+
+		//add new door close job
+
+		DoorCloseJob dcj = new DoorCloseJob(this, doorID);
+		this.doorJobs.put(doorID, dcj);
+		JobScheduler.getInstance().scheduleJob(dcj, MBServerStatics.DOOR_CLOSE_TIMER);
+	}
+
+	public final float getMaxHitPoints() {
+		return this.healthMax;
+	}
+
+	public final void setMaxHitPoints(float maxHealth) {
+		this.healthMax = maxHealth;
+	}
+
+	public final void setName(String value) {
+
+		if (DbManager.BuildingQueries.CHANGE_NAME(this, value) == false)
+			return;
+
+		this.name = value;
+		this.updateName();
+	}
+
+	public final void setw(float value) {
+		this.w = value;
+	}
+
+	public final float getw() {
+		return this.w;
+	}
+
+	public final void setMeshScale(Vector3f value) {
+		this.meshScale = value;
+	}
+
+	public final Vector3f getMeshScale() {
+		return this.meshScale;
+	}
+
+	public final int getMeshUUID() {
+		return this.meshUUID;
+	}
+
+	public final Resists getResists() {
+		return this.resists;
+	}
+
+	public final Zone getParentZone() {
+		return this.parentZone;
+	}
+
+	public final int getParentZoneID() {
+
+		if (this.parentZone == null)
+			return 0;
+
+		return this.parentZone.getObjectUUID();
+	}
+
+	public final void setParentZone(Zone zone) {
+
+		//update ZoneManager's zone building list
+		if (zone != null)
+			if (this.parentZone != null) {
+
+				this.parentZone.zoneBuildingSet.remove(this);
+				zone.zoneBuildingSet.add(this);
+
+			} else
+				zone.zoneBuildingSet.add(this);
+		else if (this.parentZone != null)
+			this.parentZone.zoneBuildingSet.remove(this);
+
+		if (this.parentZone == null) {
+			this.parentZone = zone;
+			this.setLoc(new Vector3fImmutable(this.statLat + zone.absX, this.statAlt + zone.absY, this.statLon + zone.absZ));
+		} else
+			this.parentZone = zone;
+	}
+
+	//Sets the relative position to a parent zone
+
+	public final void setRelPos(Zone zone, float locX, float locY, float locZ) {
+
+		//update ZoneManager's zone building list
+
+		if (zone != null)
+			if (this.parentZone != null) {
+				if (zone.getObjectUUID() != this.parentZone.getObjectUUID()) {
+					this.parentZone.zoneBuildingSet.remove(this);
+					zone.zoneBuildingSet.add(this);
+				}
+			} else
+				zone.zoneBuildingSet.add(this);
+		else if (this.parentZone != null)
+			this.parentZone.zoneBuildingSet.remove(this);
+
+		this.statLat = locX;
+		this.statAlt = locY;
+		this.statLon = locZ;
+		this.parentZone = zone;
+	}
+
+	public float getStatLat() {
+		return statLat;
+	}
+
+	public float getStatLon() {
+		return statLon;
+	}
+
+	public float getStatAlt() {
+		return statAlt;
+	}
+
+	public Guild getGuild() {
+
+		AbstractCharacter buildingOwner;
+
+		buildingOwner = this.getOwner();
+
+		if (buildingOwner != null)
+			return buildingOwner.getGuild();
+		else
+			return Guild.getErrantGuild();
+	}
+
+	public int getEffectFlags() {
+		return this.effectFlags;
+	}
+
+	public void addEffectBit(int bit) {
+		this.effectFlags |= bit;
+	}
+
+	public void removeAllVisualEffects() {
+		this.effectFlags = 0;
+		ApplyBuildingEffectMsg applyBuildingEffectMsg = new ApplyBuildingEffectMsg(3276859, 1, this.getObjectType().ordinal(), this.getObjectUUID(), 0);
+		DispatchMessage.sendToAllInRange(this, applyBuildingEffectMsg);
+	}
+
+	public void removeEffectBit(int bit) {
+		this.effectFlags &= (~bit);
+
+	}
+
+	@Override
+	public String getName() {
+		return this.name;
+	}
+
+	/*
+	 * Utils
+	 */
+
+	public final AbstractCharacter getOwner() {
+
+		if (this.ownerUUID == 0)
+			return null;
+		if (this.ownerIsNPC)
+			return NPC.getFromCache(this.ownerUUID);
+
+		return PlayerCharacter.getFromCache(this.ownerUUID);
+
+	}
+
+	public final String getOwnerName() {
+		AbstractCharacter owner = this.getOwner();
+		if (owner != null)
+			return owner.getName();
+		return "";
+	}
+
+	public final String getGuildName() {
+		Guild g = getGuild();
+		if (g != null)
+			return g.getName();
+		return "None";
+	}
+
+
+	/*
+	 * Serializing
+	 */
+	
+	public static void _serializeForClientMsg(Building building, ByteBufferWriter writer) {
+		writer.putInt(building.getObjectType().ordinal());
+		writer.putInt(building.getObjectUUID());
+		writer.putInt(0); // pad
+
+		writer.putInt(building.meshUUID);
+
+		writer.putInt(0); // pad
+
+		if (building.parentBuildingID != 0){
+
+			writer.putFloat(building.statLat);
+			writer.putFloat(building.statAlt);
+			writer.putFloat(building.statLon);
+
+		}else{
+			writer.putFloat(building.getLoc().getX());
+			writer.putFloat(building.getLoc().getY()); // Y location
+			writer.putFloat(building.getLoc().getZ());
+		}
+
+		writer.putFloat(building.w);
+		writer.putFloat(0f);
+		writer.putFloat(building.getRot().y);
+
+		writer.putFloat(0f);
+		writer.putFloat(building.meshScale.getX());
+		writer.putFloat(building.meshScale.getY());
+		writer.putFloat(building.meshScale.getZ());
+
+		if (building.parentBuildingID != 0){
+			writer.putInt(GameObjectType.Building.ordinal());
+			writer.putInt(building.parentBuildingID);
+			writer.putInt(building.floor);
+			writer.putInt(building.level);
+
+		}else{
+			writer.putInt(0); // Pad //Parent
+			writer.putInt(0); // Pad
+			writer.putInt(-1); // Static
+			writer.putInt(-1); // Static
+		}
+
+		writer.put((byte)0);  // 0
+		writer.putFloat(3);  // 3
+		writer.putInt(GameObjectType.Building.ordinal());
+		writer.putInt(building.getObjectUUID());
+
+		if (building.ownerIsNPC)
+			writer.putInt(GameObjectType.NPC.ordinal());
+		else
+			writer.putInt(GameObjectType.PlayerCharacter.ordinal());
+
+		writer.putInt(building.ownerUUID);
+
+		writer.put((byte) 1); // End Datablock
+		writer.putFloat(building.health.get());
+		writer.putFloat(building.healthMax);
+
+		if (building.blueprintUUID == 0)
+			writer.putInt(0);
+		else
+			writer.putInt(building.getBlueprint().getIcon());
+
+		writer.putInt(building.effectFlags);
+
+		writer.put((byte) 1); // End Datablock
+		Guild g = building.getGuild();
+		Guild nation = null;
+
+		if (g == null) {
+
+			for (int i = 0; i < 3; i++) {
+				writer.putInt(16);
+			}
+			for (int i = 0; i < 2; i++) {
+				writer.putInt(0);
+			}
+		} else {
+			GuildTag._serializeForDisplay(g.getGuildTag(),writer);
+			nation = g.getNation();
+		}
+		writer.put((byte) 1); // End Datablock?
+		if (nation == null) {
+			for (int i = 0; i < 3; i++) {
+				writer.putInt(16);
+			}
+			for (int i = 0; i < 2; i++) {
+				writer.putInt(0);
+			}
+		} else
+			GuildTag._serializeForDisplay(nation.getGuildTag(),writer);
+		writer.putString(building.name);
+		writer.put((byte) 0); // End datablock
+	}
+
+	/*
+	 * Database
+	 */
+
+	@Override
+	public void updateDatabase() {
+
+		// *** Refactor : Log error here to see if it's ever called
+	}
+
+	public final LocalDateTime getDateToUpgrade() {
+		return upgradeDateTime;
+	}
+
+	public final boolean setStrongboxValue(int newValue) {
+
+		boolean success = true;
+
+		try {
+			DbManager.BuildingQueries.SET_PROPERTY(this, "currentGold", newValue);
+			this._strongboxValue = newValue;
+		} catch (Exception e) {
+			success = false;
+			Logger.error( "Error writing to database");
+		}
+
+		return success;
+	}
+
+	public final int getStrongboxValue() {
+		return _strongboxValue;
+	}
+
+	public final void setMeshUUID(int value) {
+		this.meshUUID = value;
+	}
+
+	public final void setRank(int newRank) {
+
+		int newMeshUUID;
+		boolean success;
+
+
+		// If this building has no blueprint then set rank and exit immediatly.
+
+		if (this.blueprintUUID == 0 || this.getBlueprint() != null && this.getBlueprint().getBuildingGroup().equals(BuildingGroup.MINE)) {
+			this.rank = newRank;
+			DbManager.BuildingQueries.CHANGE_RANK(this.getObjectUUID(), newRank);
+			return;
+		}
+
+		// Delete any upgrade jobs before doing anything else.  It won't quite work
+		// if in a few lines we happen to delete this building.
+
+		JobContainer jc = this.getTimers().get("UPGRADE");
+
+		if (jc != null) {
+			if (!JobScheduler.getInstance().cancelScheduledJob(jc))
+				Logger.error( "failed to cancel existing upgrade job.");
+		}
+
+		// Attempt write to database, or delete the building
+		// if we are destroying it.
+
+		if (newRank == -1)
+			success = DbManager.BuildingQueries.DELETE_FROM_DATABASE(this);
+		else
+			success = DbManager.BuildingQueries.updateBuildingRank(this, newRank);
+
+		if (success == false) {
+			Logger.error("Error writing to database UUID: " + this.getObjectUUID());
+			return;
+		}
+
+		this.isDeranking.compareAndSet(false, true);
+
+		// Change the building's rank
+
+		this.rank = newRank;
+
+		// New rank means new mesh
+
+		newMeshUUID = this.getBlueprint().getMeshForRank(this.rank);
+		this.meshUUID = newMeshUUID;
+
+		// New rank mean new max hitpoints.
+
+		this.healthMax = this.getBlueprint().getMaxHealth(this.rank);
+		this.setCurrentHitPoints(this.healthMax);
+
+		if (this.getUpgradeDateTime() != null)
+			BuildingManager.setUpgradeDateTime(this, null, 0);
+
+		// If we destroyed this building make sure to turn off
+		// protection
+
+		if (this.rank == -1)
+			this.protectionState = ProtectionState.NONE;
+
+		if ((this.getBlueprint().getBuildingGroup() == BuildingGroup.TOL)
+				&& (this.rank == 8))
+			this.meshUUID = Realm.getRealmMesh(this.getCity());;
+
+		// update object to clients
+
+		this.refresh(true);
+		if (this.getBounds() != null)
+			this.getBounds().setBounds(this);
+
+		// Cleanup hirelings resulting from rank change
+
+		BuildingManager.cleanupHirelings(this);
+
+		this.isDeranking.compareAndSet(true, false);
+	}
+
+	public final void refresh(boolean newMesh) {
+
+		if (newMesh)
+			WorldGrid.updateObject(this);
+		else {
+			UpdateObjectMsg uom = new UpdateObjectMsg(this, 3);
+			DispatchMessage.sendToAllInRange(this, uom);
+		}
+	}
+
+	public final void updateName() {
+
+		UpdateObjectMsg uom = new UpdateObjectMsg(this, 2);
+		DispatchMessage.sendToAllInRange(this, uom);
+
+	}
+
+	// *** Refactor: Can't we just use setRank() for this?
+
+	public final void rebuildMine(){
+		this.setRank(1);
+		this.meshUUID = this.getBlueprint().getMeshForRank(this.rank);
+		// New rank mean new max hitpoints.
+		this.healthMax = this.getBlueprint().getMaxHealth(this.rank);
+		this.setCurrentHitPoints(this.healthMax);
+		this.getBounds().setBounds(this);
+	}
+
+	public final void refreshGuild() {
+
+		UpdateObjectMsg uom = new UpdateObjectMsg(this, 5);
+		DispatchMessage.sendToAllInRange(this, uom);
+
+	}
+
+	public int getMaxGold() {
+		return maxGold;
+	}
+
+	//This returns if a player is allowed access to control the building
+
+	@Override
+	public void runAfterLoad() {
+
+		try {
+
+			this.parentZone.zoneBuildingSet.add(this);
+
+			// Submit upgrade job if building is currently set to rank.
+
+		
+
+			try {
+				DbObjectType objectType = DbManager.BuildingQueries.GET_UID_ENUM(this.ownerUUID);
+				this.ownerIsNPC = (objectType == DbObjectType.NPC);
+			} catch (Exception e) {
+				this.ownerIsNPC = false;
+				Logger.error("Failed to find Object Type for owner " + this.ownerUUID+ " Location " + this.getLoc().toString());
+			}
+
+			try{
+				DbManager.BuildingQueries.LOAD_ALL_FRIENDS_FOR_BUILDING(this);
+				DbManager.BuildingQueries.LOAD_ALL_CONDEMNED_FOR_BUILDING(this);
+			}catch(Exception e){
+				Logger.error( this.getObjectUUID() + " failed to load friends/condemned." + e.getMessage());
+			}
+
+			//LOad Owners in Cache so we do not have to continuely look in the db for owner.
+
+			if (this.ownerIsNPC){
+				if (NPC.getNPC(this.ownerUUID) == null)
+					Logger.info( "Building UID " + this.getObjectUUID() + " Failed to Load NPC Owner with ID " + this.ownerUUID+ " Location " + this.getLoc().toString());
+
+			}else if (this.ownerUUID != 0){
+				if (PlayerCharacter.getPlayerCharacter(this.ownerUUID) == null){
+					Logger.info( "Building UID " + this.getObjectUUID() + " Failed to Load Player Owner with ID " + this.ownerUUID + " Location " + this.getLoc().toString());
+				}
+			}
+
+			// Apply health bonus and special mesh for realm if applicable
+			if ((this.getCity() != null) && this.getCity().getTOL() != null && (this.getCity().getTOL().rank == 8)) {
+
+				// Update mesh accordingly
+				if (this.getBlueprint() != null && this.getBlueprint().getBuildingGroup() == BuildingGroup.TOL)
+					this.meshUUID = Realm.getRealmMesh(this.getCity());
+
+				// Apply realm capital health bonus.
+				// Do not apply bonus to banestones or TOL's.  *** Refactor:
+				// Possibly only protected buildings?  Needs some thought.
+
+				float missingHealth = 0;
+
+				if (this.health.get() != 0)
+					missingHealth = this.healthMax-this.health.get();
+
+				if ((this.getBlueprint() != null && this.getBlueprint().getBuildingGroup() != BuildingGroup.TOL)
+						&& (this.getBlueprint().getBuildingGroup() != BuildingGroup.BANESTONE)){
+					this.healthMax += (this.healthMax * Realm.getRealmHealthMod(this.getCity()));
+
+					if (this.health.get() != 0)
+						this.health.set(this.healthMax - missingHealth);
+
+					if (this.health.get() > this.healthMax)
+						this.health.set(this.healthMax);
+				}
+			}
+
+			// Set bounds for this building
+
+			Bounds buildingBounds = Bounds.borrow();
+			buildingBounds.setBounds(this);
+			this.setBounds(buildingBounds);
+			
+			//create a new list for children if the building is not a child. children list default is null.
+			//TODO Remove Furniture/Child buildings from building class and move them into a seperate class.
+			if (this.parentBuildingID == 0)
+				this.children = new ArrayList<>();
+
+			if (this.parentBuildingID != 0){
+				Building parent = BuildingManager.getBuildingFromCache(this.parentBuildingID);
+				
+				if (parent != null){
+					parent.children.add(this);
+					//add furniture to region cache. floor and level are reversed in database, //TODO Fix
+					Regions region = BuildingManager.GetRegion(parent, this.level,this.floor, this.getLoc().x, this.getLoc().z);
+				if (region != null)
+					Regions.FurnitureRegionMap.put(this.getObjectUUID(), region);
+				}
+					
+			}
+			
+			if (this.upgradeDateTime != null)
+				BuildingManager.submitUpgradeJob(this);
+
+			// Run Once move buildings
+			// 64 / -64 to align with pads
+
+			// Don't move furniture
+/*
+			if (parentBuildingID != 0)
+				return;
+
+			// Don't move buildings not on a city zone
+			// or buildings that are in npc owned city
+
+			City city = getCity();
+
+			if (city == null)
+				return;
+
+			if (city.getIsNpcOwned() == 1)
+				return;
+
+			PullCmd.MoveBuilding(this, null, getLoc().add(new Vector3fImmutable(0, 0, 0)), getParentZone());
+*/
+
+		}catch (Exception e){
+			e.printStackTrace();
+		}
+	}
+
+	public synchronized  boolean setOwner(AbstractCharacter newOwner) {
+
+		int  newOwnerID;
+		if (newOwner == null)
+			newOwnerID = 0;
+		else
+			newOwnerID = newOwner.getObjectUUID();
+
+		// ***BONUS CODE BELOW!
+		/*
+        if (newOwner == null) {
+            this.ownerIsNPC = false;
+            this.ownerUUID = 0;
+        } else if (newOwner instanceof PlayerCharacter) {
+            this.ownerIsNPC = false;
+            this.ownerUUID = newOwner.getObjectUUID();
+        } else {
+            this.ownerIsNPC = true;
+            this.ownerUUID = newOwner.getObjectUUID();
+        }
+		 */
+
+		try {
+			// Save new owner to database
+
+			if (!DbManager.BuildingQueries.updateBuildingOwner(this, newOwnerID))
+				return false;
+
+			if (newOwner == null) {
+				this.ownerIsNPC = false;
+				this.ownerUUID = 0; }
+			else {
+				this.ownerUUID = newOwner.getObjectUUID();
+				this.ownerIsNPC = (newOwner.getObjectType() == GameObjectType.NPC);
+			}
+
+
+			// Set new guild for hirelings and refresh all clients
+
+			this.refreshGuild();
+			BuildingManager.refreshHirelings(this);
+
+		} catch (Exception e) {
+			Logger.error( "Error updating owner! UUID: " + this.getObjectUUID());
+			return false;
+		}
+
+		return true;
+
+	}
+
+	//This turns on and off low damage effect for building
+
+	public void toggleDamageLow(boolean on) {
+		if (on)
+			addEffectBit(2);
+		else
+			removeEffectBit(2);
+	}
+
+	//This turns on and off medium damage effect for building
+
+	public void toggleDamageMedium(boolean on) {
+		if (on)
+			addEffectBit(4);
+		else
+			removeEffectBit(4);
+	}
+
+	//This turns on and off high damage effect for building
+
+	public void toggleDamageHigh(boolean on) {
+		if (on)
+			addEffectBit(8);
+		else
+			removeEffectBit(8);
+	}
+
+	//This clears all damage effects on a building
+	public void clearDamageEffect() {
+		toggleDamageLow(false);
+		toggleDamageMedium(false);
+		toggleDamageHigh(false);
+	}
+
+	public Vector3fImmutable getStuckLocation() {
+
+		BuildingModelBase bmb = BuildingModelBase.getModelBase(this.meshUUID);
+		Vector3fImmutable convertLoc = null;
+
+
+		if (bmb != null) {
+			BuildingLocation bl = bmb.getStuckLocation();
+
+			if (bl != null){
+
+				Vector3fImmutable buildingWorldLoc = ZoneManager.convertLocalToWorld(this, bl.getLoc());
+				return buildingWorldLoc;
+			}
+
+
+		}
+
+		return null;
+	}
+
+	public boolean isDoorOpen(int doorNumber) {
+
+		if (this.doorState == 0)
+			return false;
+
+		return (this.doorState & (1 << doorNumber + 16)) != 0;
+
+	}
+
+	public boolean isDoorLocked(int doorNumber) {
+
+		if (this.doorState == 0)
+			return false;
+
+		return (this.doorState & (1 << doorNumber)) != 0;
+
+	}
+
+	public boolean setDoorState(int doorNumber, DoorState doorState) {
+
+		boolean updateRecord;
+
+		updateRecord = false;
+
+		// Can't have an invalid door number
+		// Log error?
+		if (doorNumber < 1 || doorNumber > 16)
+			return false;
+
+		switch (doorState) {
+
+		case OPEN:
+			this.doorState |= (1 << (doorNumber + 16));
+			break;
+		case CLOSED:
+			this.doorState &= ~(1 << (doorNumber + 16));
+			break;
+		case UNLOCKED:
+			this.doorState &= ~(1 << doorNumber);
+			updateRecord = true;
+			break;
+		case LOCKED:
+			this.doorState |= (1 << doorNumber);
+			updateRecord = true;
+			break;
+		}
+
+		// Save to database ?
+		if (updateRecord == true)
+			return DbManager.BuildingQueries.UPDATE_DOOR_LOCK(this.getObjectUUID(), this.doorState);
+		else
+			return true;
+	}
+
+	public int getDoorstate(){
+		return this.doorState;
+	}
+
+	public void updateEffects() {
+
+		ApplyBuildingEffectMsg applyBuildingEffectMsg = new ApplyBuildingEffectMsg(0x00720063, 1, this.getObjectType().ordinal(), this.getObjectUUID(), this.effectFlags);
+		DispatchMessage.sendToAllInRange(this, applyBuildingEffectMsg);
+
+	}
+
+	public final void enableSpire() {
+
+		SpireType spireType;
+
+		if (this.getCity() == null)
+			return;
+
+		// Blueprint sanity check
+
+		if (this.blueprintUUID == 0)
+			return;
+
+		spireType = SpireType.getByBlueprintUUID(this.blueprintUUID);
+
+		SiegeSpireWithdrawlJob spireJob = new SiegeSpireWithdrawlJob(this);
+		JobContainer jc = JobScheduler.getInstance().scheduleJob(spireJob, 300000);
+		this.getTimers().put("SpireWithdrawl", jc);
+
+		this.getCity().addCityEffect(spireType.getEffectBase(), rank);
+		addEffectBit(spireType.getEffectFlag());
+		this.spireIsActive = true;
+		this.updateEffects();
+
+
+	}
+
+	public final void disableSpire(boolean refreshEffect) {
+
+		SpireType spireType;
+
+		if (this.getCity() == null)
+			return;
+
+		// Blueprint sanity check
+
+		if (this.blueprintUUID == 0)
+			return;
+
+		spireType = SpireType.getByBlueprintUUID(this.blueprintUUID);
+
+		this.getCity().removeCityEffect(spireType.getEffectBase(), rank, refreshEffect);
+
+		JobContainer toRemove = this.getTimers().get("SpireWithdrawl");
+
+		if (toRemove != null) {
+			toRemove.cancelJob();
+			this.getTimers().remove("SpireWithdrawl");
+		}
+
+		this.spireIsActive = false;
+		this.removeEffectBit(spireType.getEffectFlag());
+		this.updateEffects();
+	}
+
+	public ConcurrentHashMap<AbstractCharacter, Integer> getHirelings() {
+		return hirelings;
+	}
+
+	public final boolean isSpireIsActive() {
+		return spireIsActive;
+	}
+
+	public final boolean isVulnerable() {
+
+		// NPC owned buildings are never vulnerable
+
+		if (ownerIsNPC)
+			return false;
+
+		// Buildings on an npc citygrid are never vulnerable
+
+		if (this.getCity() != null) {
+			if (this.getCity().getParent().isNPCCity() == true)
+				return false;
+		}
+
+		// Destroyed buildings are never vulnerable
+
+		if (rank < 0)
+			return false;
+
+		// Any structure without a blueprint was not placed by a
+		// player and we can assume to be invulnerable regardless
+		// of a protection contract or not.
+
+		if (this.getBlueprint() == null)
+			return false;
+
+		// Runegates are never vulerable.
+
+		if (this.getBlueprint().getBuildingGroup() == BuildingGroup.RUNEGATE)
+			return false;
+
+		// Shrines are never vulerable.  They blow up as a
+		// tree deranks.
+
+		if (this.getBlueprint().getBuildingGroup() == BuildingGroup.SHRINE)
+			return false;
+
+		// Mines are vulnerable only if they are active
+
+		if (this.getBlueprint().getBuildingGroup() == BuildingGroup.MINE) {
+
+			// Cannot access mine
+
+			if (Mine.getMineFromTower(this.getObjectUUID()) == null)
+				return false;
+
+			return Mine.getMineFromTower(this.getObjectUUID()).getIsActive() == true;
+		}
+
+		// Errant banestones are vulnerable by default
+
+		if ((this.getBlueprint().getBuildingGroup() == BuildingGroup.BANESTONE) &&
+				this.getCity().getBane().isErrant() == true)
+			return true;
+
+		// There is an active protection contract.  Is there also
+		// an active bane?  If so, it's meaningless.
+
+		if (this.assetIsProtected() == true) {
+
+			// Building protection is meaningless without a city
+
+			if (this.getCity() == null)
+				return true;
+
+			// All buildings are vulnerable during an active bane
+
+			return (this.getCity().protectionEnforced == false);
+
+		}
+
+		// No protection contract?  Oh well, you're vunerable!
+
+		return true;
+	}
+
+	public final void setSpireIsActive(boolean spireIsActive) {
+		this.spireIsActive = spireIsActive;
+	}
+
+	public final ConcurrentHashMap<String, JobContainer> getTimers() {
+		if (this.timers == null)
+			this.timers = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW);
+		return this.timers;
+	}
+
+	public final ConcurrentHashMap<String, Long> getTimestamps() {
+		if (this.timestamps == null)
+			this.timestamps = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW);
+		return this.timestamps;
+	}
+
+	public final long getTimeStamp(final String name) {
+		if (this.getTimestamps().containsKey(name))
+			return this.timestamps.get(name);
+		return 0L;
+	}
+
+	public final void setTimeStamp(final String name, final long value) {
+		this.getTimestamps().put(name, value);
+	}
+
+	public  ConcurrentHashMap<Integer,BuildingFriends> getFriends() {
+		return this.friends;
+	}
+
+	public final void claim(AbstractCharacter sourcePlayer) {
+
+		// Clear any existing friend or condemn entries
+
+		this.friends.clear();
+		DbManager.BuildingQueries.CLEAR_FRIENDS_LIST(this.getObjectUUID());
+
+		condemned.clear();
+		DbManager.BuildingQueries.CLEAR_CONDEMNED_LIST(this.getObjectUUID());
+
+		// Transfer the building asset ownership
+
+		this.setOwner(sourcePlayer);
+
+	}
+
+	/**
+	 * @return the protectionState
+	 */
+	public ProtectionState getProtectionState() {
+		return protectionState;
+	}
+
+	/**
+	 * @param protectionState the protectionState to set
+	 */
+	public void setProtectionState(ProtectionState protectionState) {
+
+		// Early exit if protection state is already set to input value
+
+		if (this.protectionState.equals(protectionState))
+			return;
+
+		// if building is destroyed, just set the protection state.  There isn't a DB
+		// record to write anything to.
+
+		if (rank == -1) {
+			this.protectionState = protectionState;
+			return;
+		}
+
+		if (DbManager.BuildingQueries.UPDATE_PROTECTIONSTATE(this.getObjectUUID(), protectionState) == true) {
+			this.protectionState = protectionState;
+			return;
+		}
+
+		Logger.error("Protection update failed for UUID: " + this.getObjectUUID() + "\n" +
+				this.getBlueprint().getName() + " From " + this.protectionState.name() + " To: " + protectionState.name());
+
+	}
+
+	public ConcurrentHashMap<Integer,Condemned> getCondemned() {
+		return condemned;
+	}
+
+	public boolean setReverseKOS(boolean reverseKOS) {
+		if (!DbManager.BuildingQueries.updateReverseKOS(this, reverseKOS))
+			return false;
+		this.reverseKOS = reverseKOS;
+		return true;
+	}
+
+	public boolean assetIsProtected() {
+
+		boolean outValue = false;
+
+		if (protectionState.equals(ProtectionState.PROTECTED))
+			outValue = true;
+
+		if (protectionState.equals(ProtectionState.CONTRACT))
+			outValue = true;
+
+		return outValue;
+	}
+
+	public synchronized boolean transferGold(int amount,boolean tax){
+
+		if (amount < 0)
+			if (!this.hasFunds(-amount))
+				return false;
+
+		if (_strongboxValue + amount < 0)
+			return false;
+
+		if (_strongboxValue + amount > maxGold)
+			return false;
+
+		//Deduct Profit taxes.
+		if (tax)
+			if (taxType == TaxType.PROFIT && protectionState == ProtectionState.CONTRACT && amount > 0)
+				amount = this.payProfitTaxes(amount);
+
+
+		if (amount != 0)
+			return this.setStrongboxValue(_strongboxValue + amount);
+		return true;
+	}
+
+	public synchronized int payProfitTaxes(int amount){
+
+		if (this.getCity() == null)
+			return amount;
+		if (this.getCity().getWarehouse() == null)
+			return amount;
+
+		if (this.getCity().getWarehouse().getResources().get(ItemBase.getGoldItemBase()) >= Warehouse.getMaxResources().get(ItemBase.getGoldItemBase().getUUID()))
+			return amount;
+
+		int profitAmount = (int) (amount * (taxAmount *.01f));
+
+		if (this.getCity().getWarehouse().getResources().get(ItemBase.getGoldItemBase()) + profitAmount <= Warehouse.getMaxResources().get(ItemBase.getGoldItemBase().getUUID())){
+			this.getCity().getWarehouse().depositProfitTax(ItemBase.getGoldItemBase(), profitAmount,this);
+			return amount - profitAmount;
+		}
+		//overDrafting
+		int warehouseDeposit =  Warehouse.getMaxResources().get(ItemBase.getGoldItemBase().getUUID()) - this.getCity().getWarehouse().getResources().get(ItemBase.getGoldItemBase());
+		this.getCity().getWarehouse().depositProfitTax(ItemBase.getGoldItemBase(), warehouseDeposit,this);
+		return amount - warehouseDeposit;
+	}
+
+	public synchronized boolean setReserve(int amount, PlayerCharacter player){
+
+		if (!BuildingManager.playerCanManageNotFriends(player, this))
+			return false;
+
+		if (amount < 0)
+			return false;
+
+		if (!DbManager.BuildingQueries.SET_RESERVE(this, amount))
+			return false;
+
+		this.reserve = amount;
+
+		return true;
+	}
+
+	public synchronized boolean hasFunds(int amount){
+		return amount <= (this._strongboxValue - reserve);
+	}
+
+	public ArrayList<Vector3fImmutable> getPatrolPoints() {
+		return patrolPoints;
+	}
+
+	public void setPatrolPoints(ArrayList<Vector3fImmutable> patrolPoints) {
+		this.patrolPoints = patrolPoints;
+	}
+
+	public ArrayList<Vector3fImmutable> getSentryPoints() {
+		return sentryPoints;
+	}
+
+	public void setSentryPoints(ArrayList<Vector3fImmutable> sentryPoints) {
+		this.sentryPoints = sentryPoints;
+	}
+
+	public synchronized boolean addProtectionTax(Building building, PlayerCharacter pc, final TaxType taxType, int amount, boolean enforceKOS){
+		if (building == null)
+			return false;
+
+		if (this.getBlueprint() == null)
+			return false;
+
+		if (this.getBlueprint().getBuildingGroup() != BuildingGroup.TOL)
+			return false;
+
+		if (building.assetIsProtected())
+			return false;
+
+		if (!DbManager.BuildingQueries.addTaxes(building, taxType, amount, enforceKOS))
+			return false;
+
+		building.taxType = taxType;
+		building.taxAmount = amount;
+		building.enforceKOS = enforceKOS;
+
+		return true;
+
+	}
+
+	public synchronized boolean declineTaxOffer(){
+		return true;
+	}
+
+	public synchronized boolean acceptTaxOffer(){
+		return true;
+	}
+
+	public synchronized boolean acceptTaxes(){
+
+		if (!DbManager.BuildingQueries.acceptTaxes(this))
+			return false;
+
+		this.setProtectionState(Enum.ProtectionState.CONTRACT);
+		this.taxDateTime = LocalDateTime.now().plusDays(7);
+
+		return true;
+	}
+
+	public synchronized boolean removeTaxes(){
+
+		if (!DbManager.BuildingQueries.removeTaxes(this))
+			return false;
+
+		this.taxType = TaxType.NONE;
+		this.taxAmount = 0;
+		this.taxDateTime = null;
+		this.enforceKOS = false;
+
+		return true;
+	}
+
+	public boolean isTaxed(){
+		if (this.taxType == TaxType.NONE)
+			return false;
+		if (this.taxAmount == 0)
+			return false;
+		return this.taxDateTime != null;
+	}
+}
diff --git a/src/engine/objects/BuildingFriends.java b/src/engine/objects/BuildingFriends.java
new file mode 100644
index 00000000..f6518759
--- /dev/null
+++ b/src/engine/objects/BuildingFriends.java
@@ -0,0 +1,51 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.objects;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+public class BuildingFriends  {
+
+	private int playerUID;
+	private int buildingUID;
+	private int guildUID;
+	private int friendType;
+
+	/**
+	 * ResultSet Constructor
+	 */
+
+	public BuildingFriends(ResultSet rs) throws SQLException {
+		this.playerUID = rs.getInt("playerUID");
+		this.buildingUID = rs.getInt("buildingUID");
+		this.guildUID = rs.getInt("guildUID");
+		this.friendType = rs.getInt("friendType");
+	}
+
+	public BuildingFriends(int playerUID, int buildingUID, int guildUID, int friendType) {
+		super();
+		this.playerUID = playerUID;
+		this.buildingUID = buildingUID;
+		this.guildUID = guildUID;
+		this.friendType = friendType;
+	}
+
+	public int getPlayerUID() {
+		return playerUID;
+	}
+	public int getGuildUID() {
+		return guildUID;
+	}
+	public int getFriendType() {
+		return friendType;
+	}
+
+}
diff --git a/src/engine/objects/BuildingLocation.java b/src/engine/objects/BuildingLocation.java
new file mode 100644
index 00000000..e703a65b
--- /dev/null
+++ b/src/engine/objects/BuildingLocation.java
@@ -0,0 +1,143 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.objects;
+
+import engine.gameManager.DbManager;
+import engine.math.Vector3fImmutable;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.concurrent.ConcurrentHashMap;
+
+
+public class BuildingLocation extends AbstractGameObject {
+
+	private final int buildingUUID;
+	private final int type;
+	private final int slot;
+	private final int unknown;
+	private final Vector3fImmutable loc;
+	private final Vector3fImmutable rot;
+	private final float w;
+
+
+	/**
+	 * ResultSet Constructor
+	 */
+	public BuildingLocation(ResultSet rs) throws SQLException {
+		super(rs);
+		this.buildingUUID = rs.getInt("BuildingID");
+		this.type = rs.getInt("type");
+		this.slot = rs.getInt("slot");
+		this.unknown = rs.getInt("unknown");
+		this.loc = new Vector3fImmutable(rs.getFloat("locX"), rs.getFloat("locY"), rs.getFloat("locZ"));
+		this.rot = new Vector3fImmutable(rs.getFloat("rotX"), rs.getFloat("rotY"), rs.getFloat("rotZ"));
+		this.w = rs.getFloat("w");
+	}
+
+	/*
+	 * Getters
+	 */
+
+	public int getBuildingUUID() {
+		return this.buildingUUID;
+	}
+
+	public Vector3fImmutable rotatedLoc() {
+		Vector3fImmutable convertLoc = null;
+
+
+		double rotY = 2.0 * Math.asin(this.rot.y);
+
+
+		// handle building rotation
+
+		convertLoc = new Vector3fImmutable(
+				(float) ((loc.z * Math.sin(rotY)) + (loc.x * Math.cos(rotY))),
+				loc.y,
+				(float) ((loc.z * Math.cos(rotY)) - (loc.x * Math.sin(rotY))));
+
+		return convertLoc;
+
+	}
+
+	public int getType() {
+		return this.type;
+	}
+
+	public int getSlot() {
+		return this.slot;
+	}
+
+	public int getUnknown() {
+		return this.unknown;
+	}
+
+	public float getLocX() {
+		return this.loc.x;
+	}
+
+	public float getLocY() {
+		return this.loc.y;
+	}
+
+	public float getLocZ() {
+		return this.loc.z;
+	}
+
+	public float getRotX() {
+		return this.rot.x;
+	}
+
+	public float getRotY() {
+		return this.rot.y;
+	}
+
+	public float getRotZ() {
+		return this.rot.z;
+	}
+
+	public float getW() {
+		return this.w;
+	}
+
+	public Vector3fImmutable getLoc() {
+		return this.loc;
+	}
+
+	public Vector3fImmutable getRot() {
+		return this.rot;
+	}
+
+	
+	@Override
+	public void updateDatabase() {
+	}
+
+
+	public static void loadAllLocations() {
+		ArrayList<BuildingLocation> bls = DbManager.BuildingLocationQueries.LOAD_ALL_BUILDING_LOCATIONS();
+		ConcurrentHashMap<Integer, BuildingModelBase> mbs = BuildingModelBase.getModelBases();
+		for (BuildingLocation bl : bls) {
+			int modelID = bl.buildingUUID;
+			BuildingModelBase mb = null;
+			if (!mbs.containsKey(modelID)) {
+				mb = new BuildingModelBase(modelID);
+				mbs.put(modelID, mb);
+			} else
+				mb = mbs.get(modelID);
+			mb.addLocation(bl);
+
+			if (bl.type == 6)
+				mb.addSlotLocation(bl);
+		}
+	}
+}
diff --git a/src/engine/objects/BuildingModelBase.java b/src/engine/objects/BuildingModelBase.java
new file mode 100644
index 00000000..ac3e2ced
--- /dev/null
+++ b/src/engine/objects/BuildingModelBase.java
@@ -0,0 +1,87 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.objects;
+
+import engine.server.MBServerStatics;
+
+import java.util.ArrayList;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class BuildingModelBase extends AbstractGameObject {
+
+	private ArrayList<BuildingLocation> locations = new ArrayList<>();
+	private static ConcurrentHashMap<Integer, BuildingModelBase> modelBases = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW);
+	private final int buildingBaseID;
+
+	private ArrayList<BuildingLocation> slotLocations = new ArrayList<>();
+
+	public BuildingModelBase(int buildingBaseID) {
+		super();
+		this.buildingBaseID = buildingBaseID;
+	}
+
+	public void addLocation(BuildingLocation bl) {
+		this.locations.add(bl);
+	}
+
+	public void addSlotLocation(BuildingLocation bl) {
+		this.slotLocations.add(bl);
+	}
+
+	public ArrayList<BuildingLocation> getLocations() {
+		return this.locations;
+	}
+
+	public BuildingLocation getNPCLocation(int slot) {
+		for (BuildingLocation bl : this.locations) {
+			if (bl.getType() == 6 && bl.getSlot() == slot)
+				return bl;
+		}
+		return null; //not found
+	}
+
+	public BuildingLocation getStuckLocation() {
+
+		for (BuildingLocation bl : this.locations) {
+			if (bl.getType() == 8)
+				return bl;
+		}
+		return null; //not found
+	}
+
+	public BuildingLocation getSlotLocation(int slot) {
+
+		try{
+			return this.slotLocations.get(slot - 1);
+		}catch(Exception e){
+			return null;
+		}
+	}
+
+
+	@Override
+	public void updateDatabase() {
+	}
+
+	public int getBuildingBaseID() {
+		return this.buildingBaseID;
+	}
+
+	public static ConcurrentHashMap<Integer, BuildingModelBase> getModelBases() {
+		return BuildingModelBase.modelBases;
+	}
+
+	public static BuildingModelBase getModelBase(int ID) {
+		if (!BuildingModelBase.modelBases.containsKey(ID))
+			BuildingModelBase.modelBases.put(ID, new BuildingModelBase(ID));
+		return BuildingModelBase.modelBases.get(ID);
+	}
+
+}
diff --git a/src/engine/objects/BuildingRegions.java b/src/engine/objects/BuildingRegions.java
new file mode 100644
index 00000000..0cd8fddd
--- /dev/null
+++ b/src/engine/objects/BuildingRegions.java
@@ -0,0 +1,388 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.objects;
+
+import engine.gameManager.DbManager;
+import engine.math.Vector3f;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.HashMap;
+
+public class BuildingRegions  {
+
+
+	private int buildingID;
+	private int level;
+	private int numVertex	;
+	private float vertex1X	;
+	private float vertex1Y	;
+	private float vertex1Z	;
+	private float vertex2X	;
+	private float vertex2Y	;
+	private float vertex2Z	;
+	private float vertex3X	;
+	private float vertex3Y	;
+	private float vertex3Z	;
+	private float vertex4X	;
+	private float vertex4Y	;
+	private float vertex4Z	;
+	private byte ground1;
+	private byte ground2;
+	private byte ground3;
+	private byte ground4;
+
+	private short contentBehavior;
+	private boolean outside;
+	private float centerX;
+	private float centerZ;
+	private int room = 0;
+	public static HashMap<Integer,ArrayList<BuildingRegions>> _staticRegions = new HashMap<>();
+	private final boolean exitRegion;
+	private final boolean stairs;
+	
+	private ArrayList<Vector3f> regionPoints = new ArrayList<>();
+	public Vector3f center;
+
+	/**
+	 * ResultSet Constructor
+	 */
+
+	public BuildingRegions(ResultSet rs) throws SQLException {
+
+		buildingID = rs.getInt("buildingID");
+		level = rs.getInt("level");
+		room = rs.getInt("room");
+		numVertex = rs.getInt("numVertex");
+		vertex1X = rs.getFloat("vertex1X");
+		vertex1Y = rs.getFloat("vertex1Y");
+		vertex1Z = rs.getFloat("vertex1Z");
+		vertex2X = rs.getFloat("vertex2X");
+		vertex2Y = rs.getFloat("vertex2Y");
+		vertex2Z = rs.getFloat("vertex2Z");
+		vertex3X = rs.getFloat("vertex3X");
+		vertex3Y = rs.getFloat("vertex3Y");
+		vertex3Z = rs.getFloat("vertex3Z");
+		vertex4X = rs.getFloat("vertex4X");
+		vertex4Y = rs.getFloat("vertex4Y");
+		vertex4Z = rs.getFloat("vertex4Z");
+		
+		regionPoints.add(new Vector3f(vertex1X,vertex1Y,vertex1Z));
+		regionPoints.add(new Vector3f(vertex2X,vertex2Y,vertex2Z));
+		regionPoints.add(new Vector3f(vertex3X,vertex3Y,vertex3Z));
+		
+		
+		if(numVertex ==4)
+		regionPoints.add(new Vector3f(vertex4X,vertex4Y,vertex4Z));
+	
+		
+		this.contentBehavior = (rs.getShort("unknown_Order1"));
+		short state = rs.getShort("unknown_Order2");
+		
+		if (state == 2)
+			this.outside = (true);
+		else
+			this.outside = (false);
+		
+		this.exitRegion = rs.getBoolean("colOrder1");
+		this.stairs = rs.getBoolean("colOrder2");
+
+		
+		
+		ground1 = rs.getByte("colOrder1");
+		ground2 = rs.getByte("colOrder2");
+		ground3 = rs.getByte("colOrder3");
+		ground4 = rs.getByte("colOrder4");
+		
+		float centerY = rs.getFloat("unknown_VectorY");
+		centerX = rs.getFloat("unknown_VectorX");
+		centerZ = rs.getFloat("unknown_VectorZ");
+		
+		this.center = new Vector3f(centerX,centerY,centerZ);
+		
+
+	}
+
+
+
+	public int getBuildingID() {
+		return buildingID;
+	}
+
+
+
+	public void setBuildingID(int buildingID) {
+		this.buildingID = buildingID;
+	}
+
+
+
+	public int getLevel() {
+		return level;
+	}
+
+
+
+	public void setLevel(int level) {
+		this.level = level;
+	}
+
+
+
+	public int getNumVertex() {
+		return numVertex;
+	}
+
+
+
+	public void setNumVertex(int numVertex) {
+		this.numVertex = numVertex;
+	}
+
+
+
+	public float getVertex1X() {
+		return vertex1X;
+	}
+
+
+
+	public void setVertex1X(float vertex1x) {
+		vertex1X = vertex1x;
+	}
+
+
+
+	public float getVertex1Y() {
+		return vertex1Y;
+	}
+
+
+
+	public void setVertex1Y(float vertex1y) {
+		vertex1Y = vertex1y;
+	}
+
+
+
+	public float getVertex1Z() {
+		return vertex1Z;
+	}
+
+
+
+	public void setVertex1Z(float vertex1z) {
+		vertex1Z = vertex1z;
+	}
+
+
+
+	public float getVertex2X() {
+		return vertex2X;
+	}
+
+
+
+	public void setVertex2X(float vertex2x) {
+		vertex2X = vertex2x;
+	}
+
+
+
+	public float getVertex2Y() {
+		return vertex2Y;
+	}
+
+
+
+	public void setVertex2Y(float vertex2y) {
+		vertex2Y = vertex2y;
+	}
+
+
+
+	public float getVertex2Z() {
+		return vertex2Z;
+	}
+
+
+
+	public void setVertex2Z(float vertex2z) {
+		vertex2Z = vertex2z;
+	}
+
+
+
+	public float getVertex3X() {
+		return vertex3X;
+	}
+
+
+
+	public void setVertex3X(float vertex3x) {
+		vertex3X = vertex3x;
+	}
+
+
+
+	public float getVertex3Y() {
+		return vertex3Y;
+	}
+
+
+
+	public void setVertex3Y(float vertex3y) {
+		vertex3Y = vertex3y;
+	}
+
+
+
+	public float getVertex3Z() {
+		return vertex3Z;
+	}
+
+
+
+	public void setVertex3Z(float vertex3z) {
+		vertex3Z = vertex3z;
+	}
+
+
+
+	public float getVertex4X() {
+		return vertex4X;
+	}
+
+
+
+	public void setVertex4X(float vertex4x) {
+		vertex4X = vertex4x;
+	}
+
+
+
+	public float getVertex4Y() {
+		return vertex4Y;
+	}
+
+
+
+	public void setVertex4Y(float vertex4y) {
+		vertex4Y = vertex4y;
+	}
+
+
+
+	public float getVertex4Z() {
+		return vertex4Z;
+	}
+
+
+
+	public void setVertex4Z(float vertex4z) {
+		vertex4Z = vertex4z;
+	}
+
+
+
+	public static HashMap<Integer, ArrayList<BuildingRegions>> get_staticRegions() {
+		return _staticRegions;
+	}
+
+
+
+	public static void set_staticRegions(HashMap<Integer, ArrayList<BuildingRegions>> _staticRegions) {
+		BuildingRegions._staticRegions = _staticRegions;
+	}
+
+
+
+	public static void loadAllStaticColliders(){
+		_staticRegions = DbManager.BuildingQueries.LOAD_BUILDING_REGIONS();
+	}
+
+	public static ArrayList<BuildingRegions> GetStaticCollidersForMeshID(int meshID) {
+		return _staticRegions.get(meshID);
+	}
+
+	public boolean isGroundLevel(){
+		if (this.level > 0)
+			return false;
+
+		if (this.ground1 == 0)
+			return true;
+		if (this.ground2 == 0)
+			return true;
+		if (this.ground3 == 0)
+			return true;
+        return this.ground4 == 0;
+
+    }
+
+
+
+	public float getCenterX() {
+		return centerX;
+	}
+
+
+
+	public void setCenterX(float centerX) {
+		this.centerX = centerX;
+	}
+
+
+
+	public float getCenterY() {
+		return centerZ;
+	}
+
+
+
+	public void setCenterY(float centerY) {
+		this.centerZ = centerY;
+	}
+
+
+
+	public boolean isOutside() {
+		return outside;
+	}
+
+	public short getContentBehavior() {
+		return contentBehavior;
+	}
+
+
+
+	public int getRoom() {
+		return room;
+	}
+
+
+
+	public ArrayList<Vector3f> getRegionPoints() {
+		return regionPoints;
+	}
+
+
+
+	public boolean isExitRegion() {
+		return exitRegion;
+	}
+
+
+
+	public boolean isStairs() {
+		return stairs;
+	}
+
+}
diff --git a/src/engine/objects/CharacterItemManager.java b/src/engine/objects/CharacterItemManager.java
new file mode 100644
index 00000000..bc51e3f8
--- /dev/null
+++ b/src/engine/objects/CharacterItemManager.java
@@ -0,0 +1,2630 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.objects;
+
+import engine.Enum;
+import engine.Enum.GameObjectType;
+import engine.Enum.ItemType;
+import engine.gameManager.BuildingManager;
+import engine.gameManager.ChatManager;
+import engine.gameManager.ConfigManager;
+import engine.gameManager.DbManager;
+import engine.math.Vector3fImmutable;
+import engine.net.Dispatch;
+import engine.net.DispatchMessage;
+import engine.net.client.ClientConnection;
+import engine.net.client.ClientMessagePump;
+import engine.net.client.msg.*;
+import engine.server.MBServerStatics;
+import org.pmw.tinylog.Logger;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ThreadLocalRandom;
+
+import static engine.math.FastMath.sqr;
+import static engine.net.client.msg.ErrorPopupMsg.sendErrorPopup;
+
+
+
+public class CharacterItemManager {
+
+	private final AbstractCharacter absCharacter;
+	private Account account;
+
+	// Mapping of all the items associated with this Manager
+	private final ConcurrentHashMap<Integer, Integer> itemIDtoType = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW);
+
+	// Mapping of all items equipped in this Manager
+	// Key = Item Slot
+	private final ConcurrentHashMap<Integer, Item> equipped = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW);
+
+	private final HashSet<Item> inventory = new HashSet<>();
+	private final HashSet<Item> bank = new HashSet<>();
+	private final HashSet<Item> vault = new HashSet<>();
+
+	private Item goldInventory;
+	private Item goldBank;
+	public Item goldVault;
+
+	private boolean bankOpened;
+	private boolean vaultOpened;
+
+	private short bankWeight;
+	private short inventoryWeight;
+	private short equipWeight;
+	private short vaultWeight;
+
+	private ClientConnection tradingWith;
+	private byte tradeCommitted;
+	private boolean tradeSuccess;
+	private HashSet<Integer> trading;
+	private int goldTradingAmount;
+	private int tradeID = 0;
+	private final HashSet<Integer> equipOrder = new HashSet<>();
+
+	/*
+	 * Item Manager Version data
+	 */
+	private byte equipVer = (byte) 0;
+	private static final byte inventoryVer = (byte) 0;
+	private static final byte bankVer = (byte) 0;
+	private static final byte vaultVer = (byte) 0;
+
+	public CharacterItemManager(AbstractCharacter ac) {
+		super();
+		this.absCharacter = ac;
+	}
+
+	public void load() {
+		loadForGeneric();
+
+		if (this.absCharacter .getObjectType().equals(GameObjectType.PlayerCharacter))
+			loadForPlayerCharacter();
+		else if (this.absCharacter.getObjectType().equals(GameObjectType.NPC))
+			loadForNPC();
+
+	}
+
+	public void loadGoldItems() {
+
+		if (ConfigManager.serverType.equals(Enum.ServerType.LOGINSERVER)) {
+			//other server, just make generic
+			this.goldInventory = new MobLoot(this.absCharacter, 0);
+			this.goldBank = new MobLoot(this.absCharacter, 0);
+			this.goldVault = new MobLoot(this.absCharacter, 0);
+			return;
+		}
+
+		//create inventory gold if needed
+		if (this.goldInventory == null)
+			if (this.absCharacter != null && (this.absCharacter.getObjectType().equals(GameObjectType.PlayerCharacter) || this.absCharacter.getObjectType().equals(GameObjectType.NPC)))
+				this.goldInventory = Item.newGoldItem(this.absCharacter, ItemBase.getItemBase(7), Enum.ItemContainerType.INVENTORY);
+			else
+				this.goldInventory = new MobLoot(this.absCharacter, 0);
+
+		//create bank gold if needed
+		if (this.goldBank == null)
+			if (this.absCharacter != null && this.absCharacter.getObjectType().equals(GameObjectType.PlayerCharacter))
+				this.goldBank = Item.newGoldItem(this.absCharacter, ItemBase.getItemBase(7), Enum.ItemContainerType.BANK);
+			else
+				this.goldBank = new MobLoot(this.absCharacter, 0);
+
+		//create vault gold if needed
+		if (this.goldVault == null)
+			if (this.absCharacter != null && this.absCharacter.getObjectType().equals(GameObjectType.PlayerCharacter)){
+				this.goldVault = this.account.vaultGold;
+			}
+
+			else
+				this.goldVault = new MobLoot(this.absCharacter, 0);
+
+		this.itemIDtoType.put(this.goldInventory.getObjectUUID(), this.goldInventory.getObjectType().ordinal());
+		this.itemIDtoType.put(this.goldBank.getObjectUUID(), this.goldBank.getObjectType().ordinal());
+		this.itemIDtoType.put(this.goldVault.getObjectUUID(), this.goldVault.getObjectType().ordinal());
+
+	}
+
+	private void loadForPlayerCharacter() {
+		ArrayList<Item> al = null;
+
+		// TODO Verify this is an actual account.
+		this.account = ((PlayerCharacter) this.absCharacter).getAccount();
+
+		// Get Items for player and vault
+		if (ConfigManager.serverType.equals(Enum.ServerType.LOGINSERVER)) //login, only need equipped items
+			al = DbManager.ItemQueries.GET_EQUIPPED_ITEMS(this.absCharacter.getObjectUUID());
+		else
+			al = DbManager.ItemQueries.GET_ITEMS_FOR_PC(this.absCharacter.getObjectUUID());
+
+		for (Item i : al) {
+
+			i.validateItemContainer();
+			this.itemIDtoType.put(i.getObjectUUID(), i.getObjectType().ordinal());
+
+			switch (i.containerType) {
+				case EQUIPPED:
+					if (this.equipped.containsValue(i) == false) {
+						this.equipped.put((int) i.getEquipSlot(), i);
+						addEquipOrder((int) i.getEquipSlot());
+					}
+					break;
+				case BANK:
+					if (i.getItemBase().getType().equals(ItemType.GOLD))
+						this.goldBank = i;
+					else if (this.bank.contains(i) == false)
+						this.bank.add(i);
+					break;
+				case INVENTORY:
+					if (i.getItemBase().getType().equals(ItemType.GOLD))
+						this.goldInventory = i;
+					else if (this.inventory.contains(i) == false)
+						this.inventory.add(i);
+						break;
+				case VAULT:
+					if (i.getItemBase().getType().equals(ItemType.GOLD))
+						this.goldVault = i;
+					else if (this.vault.contains(i) == false)
+						this.vault.add(i);
+					break;
+					default:
+						i.junk();
+						break;
+			}
+
+		}
+
+		this.goldVault = this.account.vaultGold;
+
+		//check all gold is created
+		//loadGoldItems();
+		calculateWeights();
+	}
+
+	private void loadForNPC() {
+		ArrayList<Item> al = null;
+
+		// Get all items related to this NPC:
+		al = DbManager.ItemQueries.GET_ITEMS_FOR_NPC(this.absCharacter.getObjectUUID());
+
+		for (Item i : al) {
+			i.validateItemContainer();
+			this.itemIDtoType.put(i.getObjectUUID(), i.getObjectType().ordinal());
+
+			switch (i.containerType) {
+				case EQUIPPED:
+				if (this.equipped.containsValue(i) == false)
+					this.equipped.put((int) i.getEquipSlot(), i);
+				break;
+				case BANK:
+					if (i.getItemBase().getType().equals(ItemType.GOLD))
+						this.goldBank = i;
+					else if (this.bank.contains(i) == false)
+						this.bank.add(i);
+					break;
+				case INVENTORY:
+					if (i.getItemBase().getType().equals(ItemType.GOLD))
+						this.goldInventory = i;
+					else if (this.inventory.contains(i) == false)
+						this.inventory.add(i);
+					break;
+					default:
+						i.junk();
+						break;
+			}
+		}
+
+		//check all gold is created
+		//loadGoldItems();
+	}
+
+	private void loadForGeneric() {
+		this.bankWeight = 0;
+		this.inventoryWeight = 0;
+		this.equipWeight = 0;
+		this.vaultWeight = 0;
+
+		//check all gold is created
+		//loadGoldItems();
+		// Always initialize with bank and vault closed
+		bankOpened = false;
+		vaultOpened = false;
+	}
+
+	//Positve Amount = TO BUILDING; Negative Amount = FROM BUILDING. flip signs for Player inventory.
+	public synchronized boolean transferGoldToFromBuilding(int amount, AbstractWorldObject object){
+        if (this.absCharacter.getObjectType() != GameObjectType.PlayerCharacter)
+			return false;
+
+        PlayerCharacter player = (PlayerCharacter) this.absCharacter;
+
+		switch (object.getObjectType()){
+		case Building:
+			Building building = (Building)object;
+
+			if (!this.getGoldInventory().validForInventory(player.getClientConnection(), player, this))
+				return false;
+
+			if (amount <0 && amount > building.getStrongboxValue())
+				return false;
+
+			// Not enough gold in inventory to transfer to tree
+
+			if ((amount > 0) &&
+					(this.getGoldInventory().getNumOfItems() - amount < 0)) {
+				sendErrorPopup(player, 28);
+				return false;
+			}
+
+			if (this.getGoldInventory().getNumOfItems() - amount > MBServerStatics.PLAYER_GOLD_LIMIT){
+				ErrorPopupMsg.sendErrorPopup(player, 202);
+				return false;
+			}
+			
+			
+
+			// Not enough gold to transfer to inventory from tree
+
+			if ((amount < 0) &&
+					(building.getStrongboxValue() + amount < 0)) {
+				sendErrorPopup(player, 127);
+				return false;
+			}
+
+			if (amount < 0)
+				if (!building.hasFunds(-amount))
+					return false;
+
+			//Verify player can access building to transfer goldItem
+
+			if (!BuildingManager.playerCanManage(player, building))
+				return false;
+
+			if (building.getStrongboxValue() + amount > building.getMaxGold()){
+				ErrorPopupMsg.sendErrorPopup(player, 201);
+				return false;
+			}
+			
+			if (this.getOwner().getCharItemManager().getGoldTrading() > 0){
+				if (this.getOwner().getObjectType().equals(GameObjectType.PlayerCharacter))
+				ErrorPopupMsg.sendErrorPopup((PlayerCharacter)this.getOwner(), 195);
+				return false;
+			}
+
+			if (!this.modifyInventoryGold(-amount)){
+
+				Logger.error(player.getName() + " transfer amount = " + amount +" ; Gold Inventory = " + this.getGoldInventory().getNumOfItems());
+
+				//  ChatManager.chatSystemError(player, "You do not have this Gold.");
+				return false;
+			}
+
+			if (!building.transferGold(amount,false)){
+
+				Logger.error(player.getName() + " transfer amount = " + amount +" ; Gold Inventory = " + this.getGoldInventory().getNumOfItems() + "; Building Strongbox = " + building.getStrongboxValue());
+
+				//ChatManager.chatSystemError(player, "Something went terribly wrong. Contact CCR.");
+				return false;
+			}
+
+			break;
+		case Warehouse:
+
+			Warehouse warehouse = (Warehouse)object;
+
+			if (amount < 0){
+                if (!warehouse.deposit((PlayerCharacter) this.absCharacter, this.getGoldInventory(), amount*-1, true,true)){
+
+                    ErrorPopupMsg.sendErrorPopup((PlayerCharacter) this.absCharacter, 203);
+					return false;
+				}
+			}else{
+                if (!warehouse.withdraw((PlayerCharacter) this.absCharacter, this.getGoldInventory().getItemBase(), amount*-1, true,true)){
+
+                    ErrorPopupMsg.sendErrorPopup((PlayerCharacter) this.absCharacter, 203);
+					return false;
+				}
+
+			}
+
+			break;
+
+		}
+		return true;
+	}
+
+	/*
+	 * Item Controls
+	 */
+	public synchronized boolean modifyInventoryGold(int modifyValue) {
+
+		Item goldItem;
+		PlayerCharacter player;
+		boolean success = false;
+
+		goldItem = getGoldInventory();
+
+		if (goldItem == null) {
+			Logger.error("ModifyInventoryGold", "Could not create gold item");
+			return success;
+		}
+
+		if (this.getGoldInventory().getNumOfItems() + modifyValue > MBServerStatics.PLAYER_GOLD_LIMIT){
+			return false;
+		}
+
+		if (this.getGoldInventory().getNumOfItems() + modifyValue < 0)
+			return false;
+
+		// No database update for npc's gold values so we use the player object
+		// for flow control later on.
+
+		if (this.absCharacter.getObjectType() == GameObjectType.PlayerCharacter)
+			player = (PlayerCharacter) this.absCharacter;
+		else
+			player = null;
+
+		// If this is an update for a player character update the database
+
+		if (player != null)
+			try {
+				if (!DbManager.ItemQueries.UPDATE_GOLD(this.getGoldInventory(), this.goldInventory.getNumOfItems() + modifyValue)){
+					return false;
+				}
+
+
+				success = true;
+			} catch (Exception e) {
+				Logger.error("ModifyInventoryGold", "Error writing to database");
+			}
+
+		// Update in-game gold values for character
+		goldItem.setNumOfItems(goldItem.getNumOfItems() + modifyValue);
+		UpdateGoldMsg ugm = new UpdateGoldMsg(this.absCharacter);
+		ugm.configure();
+
+		Dispatch dispatch = Dispatch.borrow(player, ugm);
+		DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+
+		return success;
+	}
+	
+	 public synchronized boolean tradeRequest(TradeRequestMsg msg) {
+
+	        PlayerCharacter source = (PlayerCharacter) this.getOwner();
+	        PlayerCharacter target = PlayerCharacter.getFromCache(msg.getPlayerID());
+	        Dispatch dispatch;
+
+	        if (!canTrade(source, target)) {
+	            ChatManager.chatSystemError(source, "Can't currently trade with target player");
+	            return false;
+	        }
+
+	        // TODO uncomment this block after we determine when we
+	        // setBankOpen(false) and setVaultOpen(false)
+	        CharacterItemManager cim1 = source.getCharItemManager();
+	        CharacterItemManager cim2 = target.getCharItemManager();
+
+	        if (cim1 == null)
+	            return false;
+
+	        if (cim2 == null)
+	            return false;
+
+	        dispatch = Dispatch.borrow(target, msg);
+	        DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+	        return true;
+
+	    }
+	
+	 public synchronized boolean invalidTradeRequest(InvalidTradeRequestMsg msg) {
+	        PlayerCharacter requester = PlayerCharacter.getFromCache(msg.getRequesterID());
+	        Dispatch dispatch;
+
+	        dispatch = Dispatch.borrow(requester, msg);
+	        DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+	        return true;
+
+	    }
+	 
+	 public synchronized boolean canTrade(PlayerCharacter playerA, PlayerCharacter playerB) {
+
+	        if (playerA == null || playerB == null)
+	            return false;
+
+	        //make sure both are alive
+	        if (!playerA.isAlive() || !playerB.isAlive())
+	            return false;
+
+	        //distance check
+	        Vector3fImmutable aLoc = playerA.getLoc();
+	        Vector3fImmutable bLoc = playerB.getLoc();
+
+	        if (aLoc.distanceSquared2D(bLoc) > sqr(MBServerStatics.TRADE_RANGE))
+	            return false;
+
+	        //visibility check
+	        if (!playerA.canSee(playerB) || !playerB.canSee(playerA))
+	            return false;
+
+	        if (playerA.lastBuildingAccessed != 0) {
+	            ManageCityAssetsMsg mca = new ManageCityAssetsMsg();
+				mca.actionType = 4;
+				mca.setTargetType(Enum.GameObjectType.Building.ordinal());
+	            mca.setTargetID(playerA.lastBuildingAccessed);
+	            Dispatch dispatch = Dispatch.borrow(playerA, mca);
+	            DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+	            playerA.lastBuildingAccessed = 0;
+	        }
+
+	        return true;
+	    }
+	
+	 public synchronized boolean acceptTradeRequest(AcceptTradeRequestMsg msg) {
+
+	        PlayerCharacter source = (PlayerCharacter)this.getOwner();
+	        PlayerCharacter target = PlayerCharacter.getFromCache(msg.getTargetID());
+
+	        Dispatch dispatch;
+
+	        if (source == null || !source.isAlive())
+	            return false;
+
+	        if (target == null || !target.isAlive())
+	            return false;
+
+	        if (this.tradingWith != null)
+	        	return false;
+	        
+	        if (!canTrade(source, target))
+	            return false;
+
+	        // verify characterTarget is in range
+	        if (source.getLoc().distanceSquared2D(target.getLoc()) > sqr(MBServerStatics.TRADE_RANGE))
+	            return false;
+
+	        // TODO uncomment this block after we determine when we
+	        // setBankOpen(false) and setVaultOpen(false)
+	        /*
+	         * CharacterItemManager cim1 = source.getCharItemManager();
+	         * CharacterItemManager cim2 = characterTarget.getCharItemManager(); if (cim1 ==
+	         * null) return false; if (cim2 == null) return false; if (cim1.isBankOpen())
+	         * return false; if (cim2.isVaultOpen()) return false;
+	         */
+	        ClientConnection sourceConn = source.getClientConnection();
+	        ClientConnection targetConn = target.getClientConnection();
+
+	        if (sourceConn == null)
+	            return false;
+
+	        if (targetConn == null)
+	            return false;
+
+	       
+	        CharacterItemManager toTradeWith = target.getCharItemManager();
+
+	        if (toTradeWith == null)
+	            return false;
+
+	        Account sourceAccount = source.getAccount();
+	        Account targetAccount = target.getAccount();
+
+	        UpdateVaultMsg uvmSource = new UpdateVaultMsg(sourceAccount);
+	        UpdateVaultMsg uvmTarget = new UpdateVaultMsg(targetAccount);
+
+	        dispatch = Dispatch.borrow(source, uvmSource);
+	        DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.PRIMARY);
+
+	        dispatch = Dispatch.borrow(target, uvmTarget);
+	        DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.PRIMARY);
+
+	        this.setVaultOpen(false);
+	        toTradeWith.setVaultOpen(false);
+	        this.setBankOpen(false);
+	        toTradeWith.setBankOpen(false);
+
+	        OpenTradeWindowMsg otwm = new OpenTradeWindowMsg(msg.getUnknown01(), source, target);
+
+	        // Only start trade if both players aren't already trading with
+	        // someone
+	        
+	        if (this.getTradingWith() != null || toTradeWith.getTradingWith() != null)
+	        	return false;
+	        	
+	            this.initializeTrade();
+	            toTradeWith.initializeTrade();
+	            this.setTradingWith(targetConn);
+	            toTradeWith.setTradingWith(sourceConn);
+	            this.tradeID = msg.getUnknown01();
+	            toTradeWith.tradeID = msg.getUnknown01();
+
+	            dispatch = Dispatch.borrow(source, otwm);
+	            DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.PRIMARY);
+
+	            dispatch = Dispatch.borrow(target, otwm);
+	            DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.PRIMARY);
+
+			return true;
+	    }
+	
+	  public synchronized boolean addItemToTradeWindow(AddItemToTradeWindowMsg msg) {
+	        PlayerCharacter source =(PlayerCharacter)this.getOwner();
+	        Dispatch dispatch;
+
+	        if (source == null || !source.isAlive())
+	            return false;
+
+	       
+
+
+	        ClientConnection ccOther = this.getTradingWith();
+
+	        if (ccOther == null)
+	            return false;
+
+	        PlayerCharacter other = ccOther.getPlayerCharacter();
+
+	        if (other == null || !other.isAlive())
+	            return false;
+
+	        CharacterItemManager tradingWith = other.getCharItemManager();
+
+	        if (tradingWith == null)
+	            return false;
+
+	        if (!canTrade(source, other))
+	            return false;
+
+	        Item i = Item.getFromCache(msg.getItemID());
+
+	        if (i == null)
+	            return false;
+
+	        if (!this.doesCharOwnThisItem(i.getObjectUUID()))
+	            return false;
+
+	        //can't add item to trade window twice
+	        if (this.tradingContains(i))
+	            return false;
+
+	        //dupe check
+	        if (!i.validForInventory(source.getClientConnection(), source, this))
+	            return false;
+
+	        if (!tradingWith.hasRoomTrade(i.getItemBase().getWeight())) {
+	            dispatch = Dispatch.borrow(source, msg);
+	            DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.PRIMARY);
+	            return false;
+	        }
+
+	        UpdateTradeWindowMsg utwm = new UpdateTradeWindowMsg(source, other);
+
+	        this.setTradeCommitted((byte) 0);
+	        tradingWith.setTradeCommitted((byte) 0);
+
+	        this.addItemToTrade(i);
+	
+	        dispatch = Dispatch.borrow(other, msg);
+	        DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.PRIMARY);
+
+	        modifyCommitToTrade();
+
+	        dispatch = Dispatch.borrow(other, utwm);
+	        DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.PRIMARY);
+			return true;
+	    }
+	
+	public synchronized boolean addGoldToTradeWindow(AddGoldToTradeWindowMsg msg) {
+
+        PlayerCharacter source = (PlayerCharacter) this.getOwner();
+        Dispatch dispatch;
+
+        if (source == null || !source.isAlive())
+            return false;
+
+       
+       
+
+        ClientConnection ccOther = this.getTradingWith();
+
+        if (ccOther == null)
+            return false;
+
+        PlayerCharacter other = ccOther.getPlayerCharacter();
+
+        if (other == null || !other.isAlive())
+            return false;
+
+        CharacterItemManager tradingWith = other.getCharItemManager();
+
+        if (tradingWith == null)
+            return false;
+
+        UpdateTradeWindowMsg utwm = new UpdateTradeWindowMsg(other, source);
+        UpdateTradeWindowMsg utwmOther = new UpdateTradeWindowMsg(source, other);
+
+        if (!canTrade(source, other))
+            return false;
+
+        this.setTradeCommitted((byte) 0);
+        tradingWith.setTradeCommitted((byte) 0);
+
+        int amt = msg.getAmount();
+
+        if (amt <= 0){
+            Logger.info( source.getFirstName() + " added negative gold to trade window. Dupe attempt FAILED!");
+            return false;
+        }
+        
+        if (amt > MBServerStatics.PLAYER_GOLD_LIMIT)
+        	return false;
+        
+        if (this.getGoldInventory().getNumOfItems() - amt < 0)
+        	return false;
+
+        this.addGoldToTrade(amt);
+
+        // BONUS CODE BELOW:  Thanks some unknown retard!
+        //		sourceItemMan.updateInventory(sourceItemMan.getInventory(), true);
+
+        UpdateGoldMsg ugm = new UpdateGoldMsg(source);
+        ugm.configure();
+
+        modifyCommitToTrade();
+
+        dispatch = Dispatch.borrow(source, utwm);
+        DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+
+        dispatch = Dispatch.borrow(source, ugm);
+        DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+
+        dispatch = Dispatch.borrow(other, utwmOther);
+        DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+		return true;
+
+    }
+	
+	
+	public synchronized boolean uncommitToTrade(UncommitToTradeMsg msg) {
+
+        PlayerCharacter source = (PlayerCharacter) this.getOwner();
+
+        if (source == null || !source.isAlive())
+            return false;
+
+        CharacterItemManager sourceItemMan = source.getCharItemManager();
+
+        if (sourceItemMan == null)
+            return false;
+
+        sourceItemMan.setTradeCommitted((byte) 0);
+
+        ClientConnection ccOther = sourceItemMan.getTradingWith();
+
+        if (ccOther == null)
+            return false;
+
+        PlayerCharacter other = ccOther.getPlayerCharacter();
+
+        if (other == null)
+            return false;
+
+        if (!canTrade(source, other))
+            return false;
+
+        return modifyCommitToTrade();
+    }
+	
+	public synchronized boolean commitToTrade(CommitToTradeMsg msg) {
+
+        PlayerCharacter source = (PlayerCharacter)this.getOwner();
+
+        if (source == null || !source.isAlive())
+            return false;
+
+
+        
+
+        this.setTradeCommitted((byte) 1);
+
+        ClientConnection ccOther = this.getTradingWith();
+
+        if (ccOther == null)
+            return false;
+
+        PlayerCharacter other = ccOther.getPlayerCharacter();
+
+        if (other == null || !other.isAlive())
+            return false;
+
+        CharacterItemManager tradingWith = other.getCharItemManager();
+
+        if (tradingWith == null)
+            return false;
+
+        if (!canTrade(source, other))
+            return false;
+
+        modifyCommitToTrade();
+
+        if (this.getTradeCommitted() == (byte) 1 && tradingWith.getTradeCommitted() == (byte) 1) {
+        	int tradeID = this.tradeID;
+            CloseTradeWindowMsg ctwm1 = new CloseTradeWindowMsg(source, tradeID);
+            CloseTradeWindowMsg ctwm2 = new CloseTradeWindowMsg(other, tradeID);
+            this.commitTrade();
+            this.closeTradeWindow(ctwm1, false);
+            other.getCharItemManager().closeTradeWindow(ctwm2, false);
+        }
+		return true;
+    }
+	
+	  private synchronized boolean modifyCommitToTrade() {
+	        CharacterItemManager man1 = this;
+	        
+	        if (this.getTradingWith() == null)
+	        	return false;
+	        
+	        if (this.getTradingWith().getPlayerCharacter() == null)
+	        	return false;
+	        CharacterItemManager man2 = this.getTradingWith().getPlayerCharacter().getCharItemManager();
+	        Dispatch dispatch;
+
+	        if (man1 == null || man2 == null)
+	            return false;
+
+	        ModifyCommitToTradeMsg modify = new ModifyCommitToTradeMsg(this.getOwner(), man2.getOwner(), man1.getTradeCommitted(),
+	                man2.getTradeCommitted());
+
+	        dispatch = Dispatch.borrow((PlayerCharacter) this.getOwner(), modify);
+	        DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+
+	        dispatch = Dispatch.borrow((PlayerCharacter) man2.getOwner(), modify);
+	        DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+	        
+	        return true;
+
+	    }
+	
+	 public synchronized boolean closeTradeWindow(CloseTradeWindowMsg msg, boolean sourceTrade) {
+
+	     
+	        Dispatch dispatch;
+
+	        PlayerCharacter source = (PlayerCharacter) this.getOwner();
+	        if (source == null)
+	            return false;
+
+	        CharacterItemManager sourceItemMan = source.getCharItemManager();
+
+	        if (sourceItemMan == null)
+	            return false;
+	        
+	        int tradeID = this.tradeID;
+	        CloseTradeWindowMsg closeMsg = new CloseTradeWindowMsg(source,tradeID);
+
+	        dispatch = Dispatch.borrow(source, closeMsg);
+	        DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+	        
+	        if (!sourceTrade){
+	        	sourceItemMan.endTrade();
+	        	return true;
+	        }
+	        
+	        ClientConnection cc2 = sourceItemMan.getTradingWith();
+
+	        if (cc2 == null || cc2.getPlayerCharacter() == null){
+		        sourceItemMan.endTrade();
+	            return true;
+	        }
+	        
+	        sourceItemMan.endTrade();
+
+	        
+	        
+	        cc2.getPlayerCharacter().getCharItemManager().closeTradeWindow(msg, false);
+	        
+	       
+	        
+	     
+			return true;
+	    }
+
+	public Item getGoldInventory() {
+		if (this.goldInventory == null)
+			loadGoldItems();
+		return this.goldInventory;
+	}
+
+	public Item getGoldBank() {
+		if (this.goldBank == null)
+			loadGoldItems();
+		return this.goldBank;
+	}
+
+	public Item getGoldVault() {
+		if (this.goldVault == null)
+			loadGoldItems();
+		return this.goldVault;
+	}
+
+	public void addEquipOrder(int slot) {
+		synchronized (this.equipOrder) {
+			Integer iSlot = slot;
+			if (this.equipOrder.contains(iSlot))
+				this.equipOrder.remove(iSlot);
+			this.equipOrder.add(slot);
+		}
+	}
+
+	public synchronized boolean doesCharOwnThisItem(int itemID) {
+		return this.itemIDtoType.containsKey(itemID);
+	}
+
+	public synchronized boolean junk(Item i) {
+		return junk(i, true);
+	}
+
+	public synchronized boolean recycle(Item i) {
+		if (i.getObjectType() == GameObjectType.Item)
+			return junk(i, false);
+		else{
+			if(this.removeItemFromInventory(i) == false)
+				return false;
+			((MobLoot)i).recycle((NPC)this.absCharacter);
+			calculateInventoryWeight();
+			return true;
+		}
+	}
+
+	// The DeleteItemMsg takes care of updating inventory, so we don't want to do it separately
+	public synchronized boolean delete(Item i) {
+		return junk(i, false);
+	}
+
+	//cleanup an item from CharacterItemManager if it doesn't belong here
+	public synchronized boolean cleanupDupe(Item i) {
+		if (i == null)
+			return false;
+
+		if(i.getItemBase().getType().equals(ItemType.GOLD)){
+			if (this.getGoldInventory() != null){
+				if (i.getObjectUUID() == this.getGoldInventory().getObjectUUID())
+					this.goldInventory = null;
+			}else if (this.getGoldBank() != null){
+				if (i.getObjectUUID() == this.getGoldBank().getObjectUUID())
+					this.goldBank = null;
+			}
+			return true;
+		}
+
+		byte slot = i.getEquipSlot();
+
+		if (this.doesCharOwnThisItem(i.getObjectUUID()) == false)
+			return false;
+
+		// remove it from other lists:
+		this.remItemFromLists(i, slot);
+		this.itemIDtoType.remove(i.getObjectUUID());
+
+		calculateWeights();
+		return true;
+	}
+
+	public synchronized boolean consume(Item i) {
+		i.decrementChargesRemaining();
+		if (i.getChargesRemaining() > 0)
+			return true;
+		return junk(i, true);
+	}
+
+	private synchronized boolean junk(Item i, boolean updateInventory) {
+		if (i.getItemBase().getType().equals(ItemType.GOLD)) {
+			if (this.getGoldInventory().getObjectUUID() == i.getObjectUUID())
+				if (DbManager.ItemQueries.UPDATE_GOLD(i, 0)) {
+					this.getGoldInventory().setNumOfItems(0);
+					if (updateInventory)
+					updateInventory();
+					return true;
+				}else{
+					return false;
+				}
+			if (!(this.absCharacter.getObjectType().equals(GameObjectType.Mob)))
+				return false;
+		}
+
+		byte slot = i.getEquipSlot();
+
+        if (this.doesCharOwnThisItem(i.getObjectUUID()) == false && this.absCharacter.getObjectType() != GameObjectType.Mob && (i.containerType != Enum.ItemContainerType.FORGE))
+			return false;
+
+		// remove it from other lists:
+		this.remItemFromLists(i, slot);
+		this.itemIDtoType.remove(i.getObjectUUID());
+
+		i.junk();
+
+		//Why are we adding junked items?!
+
+		//		if (i.getObjectType() != GameObjectType.MobLoot)
+		//			CharacterItemManager.junkedItems.add(i);
+
+
+		calculateWeights();
+
+		if (updateInventory)
+			// Send the new inventory
+			//updateInventory(i, false); this line was causing entire inventory to disappear
+			updateInventory(this.getInventory(), true);
+
+		return true;
+	}
+
+	public synchronized boolean moveItemToInventory(Item i) {
+
+		boolean fromEquip = false;
+		synchronized (this) {
+			byte slot = i.getEquipSlot();
+
+			//Skip if NOT in vault.
+			if (i.containerType != Enum.ItemContainerType.VAULT)
+				if (this.doesCharOwnThisItem(i.getObjectUUID()) == false)
+					return false;
+
+			// Only valid from bank, equip and vault
+			if (!bankContains(i) && !equippedContains(i) && !vaultContains(i))
+				return false;
+
+			if (equippedContains(i)) {
+				fromEquip = true;
+				ItemBase ib = i.getItemBase();
+				if (ib != null && ib.getType().equals(ItemType.GOLD))
+					this.absCharacter.cancelOnUnEquip();
+			}
+
+			// check to see what type of AbstractCharacter subclass we have stored
+			if (this.absCharacter.getClass() == PlayerCharacter.class) {
+				if (!i.moveItemToInventory((PlayerCharacter) this.absCharacter))
+					return false;
+			} else if (!i.moveItemToInventory((NPC) this.absCharacter))
+				return false;
+
+			// remove it from other lists:
+			this.remItemFromLists(i, slot);
+
+			// add to Inventory
+			this.inventory.add(i);
+			i.addToCache();
+			this.itemIDtoType.put(i.getObjectUUID(), i.getObjectType().ordinal());
+
+			calculateWeights();
+		}
+
+		//Apply bonuses if from equip
+		if (fromEquip && this.absCharacter != null) {
+			this.absCharacter.applyBonuses();
+			if (this.absCharacter.getObjectType().equals(GameObjectType.PlayerCharacter))
+				this.absCharacter.incVer();
+		}
+
+		return true;
+	}
+
+	public synchronized boolean moveItemToBank(Item i) {
+		byte slot = i.getEquipSlot();
+
+		if (this.doesCharOwnThisItem(i.getObjectUUID()) == false)
+			return false;
+
+		// Item must be in inventory to move to bank
+		if (!this.inventory.contains(i))
+			return false;
+
+		// check to see what type of AbstractCharacter subclass we have stored
+		if (this.absCharacter.getClass() == PlayerCharacter.class) {
+			if (!i.moveItemToBank((PlayerCharacter) this.absCharacter))
+				return false;
+		} else if (!i.moveItemToBank((NPC) this.absCharacter))
+			return false;
+
+		// remove it from other lists:
+		this.remItemFromLists(i, slot);
+
+		// add to Bank
+		this.bank.add(i);
+		i.addToCache();
+
+		calculateWeights();
+
+		return true;
+	}
+
+	public synchronized boolean moveGoldToBank(Item from, int amt) {
+		if (from == null)
+			return false;
+		if (from.getNumOfItems() - amt < 0)
+			return false;
+		if (this.goldBank.getNumOfItems() + amt > MBServerStatics.BANK_GOLD_LIMIT){
+			if (this.absCharacter.getObjectType() == GameObjectType.PlayerCharacter){
+				PlayerCharacter pc = (PlayerCharacter)this.absCharacter;
+				if (pc.getClientConnection() != null)
+					ErrorPopupMsg.sendErrorPopup(pc, 202);
+				return false;
+			}
+		}
+
+		if (!DbManager.ItemQueries.MOVE_GOLD(from, this.getGoldBank(), amt))
+			return false;
+		from.setNumOfItems(from.getNumOfItems() - amt);
+		this.goldBank.setNumOfItems(this.goldBank.getNumOfItems() + amt);
+		return true;
+	}
+
+	public synchronized boolean moveGoldToVault(Item from, int amt) {
+		if (from == null)
+			return false;
+		if (from.getNumOfItems() - amt < 0)
+			return false;
+		if (!DbManager.ItemQueries.MOVE_GOLD(from, this.account.vaultGold, amt))
+			return false;
+		from.setNumOfItems(from.getNumOfItems() - amt);
+		this.account.vaultGold.setNumOfItems(this.goldVault.getNumOfItems() + amt);
+		return true;
+	}
+
+	public synchronized boolean moveGoldToInventory(Item from, int amt) {
+		if (from == null)
+			return false;
+		if (from.getNumOfItems() - amt < 0 || amt < 1)
+			return false;
+
+		if (this.goldInventory.getNumOfItems() + amt > MBServerStatics.PLAYER_GOLD_LIMIT){
+			if (this.absCharacter.getObjectType() == GameObjectType.PlayerCharacter){
+				PlayerCharacter pc = (PlayerCharacter)this.absCharacter;
+				if (pc.getClientConnection() != null)
+					ErrorPopupMsg.sendErrorPopup(pc, 202);
+				return false;
+			}
+		}
+
+		if (from instanceof MobLoot) {
+			if (!DbManager.ItemQueries.UPDATE_GOLD(this.getGoldInventory(),
+					this.goldInventory.getNumOfItems() + amt))
+				return false;
+		} else if (!DbManager.ItemQueries.MOVE_GOLD(from, this.goldInventory, amt))
+			return false;
+		from.setNumOfItems(from.getNumOfItems() - amt);
+		this.goldInventory.setNumOfItems(this.goldInventory.getNumOfItems() + amt);
+		return true;
+	}
+
+	//This is called by the addGold devCmd.
+	public synchronized boolean addGoldToInventory(int amt, boolean fromDevCmd) {
+
+		if (this.absCharacter == null || (!(this.absCharacter.getObjectType().equals(GameObjectType.PlayerCharacter))))
+			return false;
+
+		if (this.getGoldInventory().getNumOfItems() + amt > MBServerStatics.PLAYER_GOLD_LIMIT){
+			return false;
+		}
+
+		if (this.getGoldInventory().getNumOfItems() + amt < 0)
+			return false;
+
+
+		boolean worked = DbManager.ItemQueries.UPDATE_GOLD(this.getGoldInventory(), this.goldInventory.getNumOfItems() + amt);
+		if (worked) {
+			//log this since it's technically a dupe. Only use on test server!
+			if (fromDevCmd) {
+				String logString = this.absCharacter.getName() + " added " + amt + " gold to their inventory";
+				Logger.info(logString);
+			}
+			this.goldInventory.setNumOfItems(this.goldInventory.getNumOfItems() + amt);
+		}
+		return worked;
+	}
+
+	//Used to trainsfer gold from one inventory to another, for steal, etc.
+	public boolean transferGoldToMyInventory(AbstractCharacter tar, int amount) {
+		if (tar == null)
+			return false;
+
+		CharacterItemManager tarCim = tar.getCharItemManager();
+		if (tarCim == null)
+			return false;
+
+		if (this.getGoldInventory().getNumOfItems()  + amount < 0)
+			return false;
+
+		if (this.getGoldInventory().getNumOfItems() + amount > MBServerStatics.PLAYER_GOLD_LIMIT)
+			return false;
+
+		if (tarCim.getGoldInventory().getNumOfItems() -amount < 0)
+			return false;
+
+		if (tarCim.getGoldInventory().getNumOfItems() - amount > MBServerStatics.PLAYER_GOLD_LIMIT)
+			return false;
+
+		synchronized (this) {
+			synchronized (tarCim) {
+				if (!tarCim.addGoldToInventory(0 - amount, false)) //remove gold from target
+					return false;
+				if (!addGoldToInventory(amount, false)) //add to this inventory
+					return false;
+			}
+		}
+		return true;
+	}
+
+	public synchronized boolean moveItemToVault(Item i) {
+		byte slot = i.getEquipSlot();
+
+		//		if (this.doesCharOwnThisItem(i.getObjectUUID()) == false)
+		//			return false;
+
+		// Item must be in inventory to move to vault
+		if (!this.inventory.contains(i))
+			return false;
+
+		// check to see what type of AbstractCharacter subclass we have stored
+		if (this.absCharacter.getClass() == PlayerCharacter.class) {
+			if (!i.moveItemToVault(this.account))
+				return false;
+		} else
+			return false; // NPC's dont have vaults!
+
+		// remove it from other lists:
+		this.remItemFromLists(i, slot);
+
+		// add to Vault
+		i.addToCache();
+
+		calculateWeights();
+
+		return true;
+	}
+
+	// This removes ingame item from inventory for loot.
+	private synchronized boolean removeItemFromInventory(Item i) {
+		if (i.getItemBase().getType().equals(ItemType.GOLD)) {
+			if (i.getObjectUUID() != this.getGoldInventory().getObjectUUID())
+				return false;
+			if (!DbManager.ItemQueries.UPDATE_GOLD(this.goldInventory, 0)){
+				return false;
+			}
+
+		} else {
+			if (this.doesCharOwnThisItem(i.getObjectUUID()) == false)
+				return false;
+			if (this.inventory.contains(i)) {
+				this.inventory.remove(i);
+				this.itemIDtoType.remove(i.getObjectUUID());
+				return true;
+			}
+		}
+		// tell client we're removing item
+		updateInventory(i, false);
+		return false;
+	}
+
+	// This adds item to inventory for loot. Validity checks already handled
+	public synchronized boolean addItemToInventory(Item i) {
+		if (i.getItemBase().getType().equals(ItemType.GOLD))
+			if (this.absCharacter.getObjectType() == GameObjectType.Mob) {
+				if (this.goldInventory == null)
+					loadGoldItems();
+				this.goldInventory.setNumOfItems(this.goldInventory.getNumOfItems() + i.getNumOfItems());
+			} else {
+				int amt = i.getNumOfItems();
+				if (DbManager.ItemQueries.UPDATE_GOLD(this.goldInventory, this.goldInventory.getNumOfItems() + amt)) {
+					updateInventory();
+					return true;
+				}
+
+				return false;
+			}
+
+		this.inventory.add(i);
+		this.itemIDtoType.put(i.getObjectUUID(), i.getObjectType().ordinal());
+
+		ItemBase ib = i.getItemBase();
+		if (ib != null)
+			this.inventoryWeight += ib.getWeight();
+		return true;
+	}
+
+
+
+	//called for adding gold of a specified amount
+	public synchronized boolean addItemToInventory(Item i, int amount) {
+		if (i.getItemBase().getType().equals(ItemType.GOLD))
+            return DbManager.ItemQueries.UPDATE_GOLD(this.getGoldInventory(), this.goldInventory.getNumOfItems() + amount);
+		return false;
+	}
+
+	public boolean equipItem(Item i, byte slot) {
+
+		synchronized (this) {
+			byte curSlot = i.getEquipSlot(); // Should be 0
+
+            if (this.doesCharOwnThisItem(i.getObjectUUID()) == false && this.absCharacter.getObjectType() != GameObjectType.Mob) {
+				Logger.error("Doesnt own item");
+				return false;
+			}
+
+			// Item must be in inventory to equip
+            if (!this.inventory.contains(i) && this.absCharacter.getObjectType() != GameObjectType.Mob)
+				return false;
+
+			// make sure player can equip item
+			if (i.getItemBase() == null)
+				return false;
+            if (!i.getItemBase().canEquip(slot, this, absCharacter, i) && this.absCharacter.getObjectType() != GameObjectType.Mob)
+				return false;
+
+			// check to see if item is already there.
+			Item old = this.equipped.get((int) slot);
+			if (old != null) {
+				Logger.error( "already equipped");
+				return false;
+			}
+
+			// check to see what type of AbstractCharacter subclass we have stored
+			if (this.absCharacter.getClass() == PlayerCharacter.class) {
+				if (!i.equipItem((PlayerCharacter) this.absCharacter, slot))
+					return false;
+			} else if (this.absCharacter.getObjectType() == GameObjectType.Mob) {
+				if (!i.equipItem((Mob) this.absCharacter, slot)) {
+					Logger.error("Failed to set Equip");
+					return false;
+				}
+
+			} else if (!i.equipItem((NPC) this.absCharacter, slot))
+				return false;
+
+			// remove it from other lists:
+			this.remItemFromLists(i, slot);
+
+			// add to Equipped
+			this.equipped.put((int) slot, i);
+			i.addToCache();
+
+			addEquipOrder(i.getEquipSlot());
+
+			//calculateWeights();
+		}
+
+		//Apply Bonuses and update player
+		if (this.absCharacter != null) {
+			this.absCharacter.applyBonuses();
+			if (this.absCharacter.getObjectType().equals(GameObjectType.PlayerCharacter))
+				this.absCharacter.incVer();
+		}
+
+		return true;
+	}
+
+	//Used for buying MobEquipment from NPC
+	//Handles the gold transfer aspect
+
+	public synchronized boolean buyFromNPC(Building vendorBuilding, int cost,int buildingDeposit) {
+
+		Item gold = this.getGoldInventory();
+
+		if (cost <= 0 || (gold.getNumOfItems() - cost) < 0)
+			return false;
+		
+		
+		if (this.getOwner() != null && this.getOwner().getObjectType().equals(GameObjectType.PlayerCharacter)){
+			if (this.goldTradingAmount > 0){
+				ErrorPopupMsg.sendErrorPopup((PlayerCharacter)this.getOwner(), 195);
+				return false;
+			}
+		}
+
+		// Create gold from screatch instead of building strongbox
+		// if the NPC is not slotted.
+
+		if (vendorBuilding == null) {
+
+            return this.modifyInventoryGold(-cost);
+        }
+
+
+		if (vendorBuilding.getStrongboxValue() + cost > vendorBuilding.getMaxGold()){
+
+			if (this.absCharacter.getObjectType() == GameObjectType.PlayerCharacter){
+				PlayerCharacter pc = (PlayerCharacter)this.absCharacter;
+				if (pc.getClientConnection() != null)
+					ErrorPopupMsg.sendErrorPopup(pc, 206);
+			}
+
+			return false;
+		}
+
+
+		// Update strongbox and inventory gold
+		if (!this.modifyInventoryGold(-cost))
+			return false;
+		
+		City buildingCity = vendorBuilding.getCity();
+		
+		if (buildingCity != null){
+			buildingCity.transactionLock.writeLock().lock();
+			try{
+				if (!vendorBuilding.transferGold(buildingDeposit, true))
+			        return false;
+			}catch(Exception e){
+				Logger.error(e);
+				return false;
+			}finally{
+				buildingCity.transactionLock.writeLock().unlock();
+			}
+		}else
+		if (!vendorBuilding.transferGold(buildingDeposit, true))
+        return false;
+		
+		return true;
+    }
+
+	//Used for selling items to NPC
+	public synchronized boolean sellToNPC(Building building, int cost, Item item) {
+
+		// Create gold from screatch instead of building strongbox
+		// if the NPC is not slotted.
+
+		if (this.getGoldInventory().getNumOfItems() + cost < 0)
+			return false;
+
+		if (this.getGoldInventory().getNumOfItems() + cost > MBServerStatics.PLAYER_GOLD_LIMIT)
+			return false;
+		
+
+		if (this.getOwner().getCharItemManager().getGoldTrading() > 0){
+			if (this.getOwner().getObjectType().equals(GameObjectType.PlayerCharacter))
+			ErrorPopupMsg.sendErrorPopup((PlayerCharacter)this.getOwner(), 195);
+			return false;
+		}
+
+
+		if (building == null) {
+            return this.modifyInventoryGold(cost);
+        }
+
+		//make sure strongbox can afford gold.
+
+		if (!building.hasFunds(cost))
+			return false;
+
+		if ((building.getStrongboxValue() - cost) < 0)
+			return false;
+
+		// Update strongbox and inventory gold
+
+		if (!building.transferGold(-cost,false))
+			return false;
+
+        return this.modifyInventoryGold(cost);
+    }
+
+	/**
+	 * This sells an item to an npc
+	 *
+	 * @return True on success
+	 */
+	public synchronized boolean sellToNPC(Item itemToSell, NPC npc) {
+
+		CharacterItemManager itemMan;
+
+		if (itemToSell == null || npc == null)
+			return false;
+
+		itemMan = npc.getCharItemManager();
+
+		if (itemMan == null)
+			return false;
+
+		//test npc inventory is not full
+
+		synchronized (this) {
+			synchronized (itemMan) {
+				if (!this.doesCharOwnThisItem(itemToSell.getObjectUUID()))
+					return false;
+				// attempt to transfer item in db
+
+				boolean sdrMerchant = false;
+
+				if (npc.getContractID() >= 1900 && npc.getContractID() <= 1906)
+					sdrMerchant = true;
+
+				if (sdrMerchant){
+					this.delete(itemToSell);
+					this.updateInventory();
+
+				}else
+					if (!itemToSell.moveItemToInventory(npc))
+						return false;
+
+				// db transfer successfull, remove from this character
+				// skip this check if this is a mobLoot item (which is not in any inventory)
+				if (!sdrMerchant)
+					if (!removeItemFromInventory(itemToSell))
+						return false;
+
+				// add item to looter.
+				if(!sdrMerchant)
+					if (!itemMan.addItemToInventory(itemToSell))
+						return false;
+			}
+		}
+
+		// calculate new weights
+		calculateInventoryWeight();
+		itemMan.calculateInventoryWeight();
+		return true;
+	}
+
+	/**
+	 * This buys an item from an npc
+	 * Handles transfer of item.
+	 *
+	 * @return True on success
+	 */
+	public synchronized boolean buyFromNPC(Item purchasedItem, NPC npc) {
+
+		CharacterItemManager itemMan;
+		ItemBase itemBase;
+
+		if (purchasedItem == null || npc == null)
+			return false;
+
+		itemMan = npc.getCharItemManager();
+
+		if (itemMan == null)
+			return false;
+
+
+
+		synchronized (this) {
+			synchronized (itemMan) {
+				itemBase = purchasedItem.getItemBase();
+
+				if (itemBase == null)
+					return false;
+
+				//test inventory is not full
+
+				if (!hasRoomInventory(itemBase.getWeight()))
+					return false;
+
+				if (!itemMan.inventory.contains(purchasedItem))
+					return false;
+				// attempt to transfer item in db
+
+				if (purchasedItem.getObjectType() == GameObjectType.MobLoot){
+
+					Item newItem = ((MobLoot) purchasedItem).promoteToItem((PlayerCharacter)this.absCharacter);
+					if (newItem == null)
+						return false;
+
+					if (!itemMan.removeItemFromInventory(purchasedItem))
+						return false;
+
+					if (!addItemToInventory(newItem))
+						return false;
+					//Item was created and still a mobloot item, remove from npc production list in db.
+
+					DbManager.NPCQueries.REMOVE_FROM_PRODUCTION_LIST(purchasedItem.getObjectUUID(),npc.getObjectUUID());
+
+
+				}else{
+					if (!purchasedItem.moveItemToInventory((PlayerCharacter) this.absCharacter))
+						return false;
+
+					if (purchasedItem.getValue() != purchasedItem.getMagicValue()){
+						DbManager.ItemQueries.UPDATE_VALUE(purchasedItem,0);
+						purchasedItem.setValue(0);
+					}
+
+					// db transfer successfull, remove from this character
+					// skip this check if this is a mobLoot item (which is not in any inventory)
+					if (!itemMan.removeItemFromInventory(purchasedItem))
+						return false;
+
+					// add item to looter.
+
+					if (!addItemToInventory(purchasedItem))
+						return false;
+				}
+
+			}
+		}
+
+		// calculate new weights
+		calculateInventoryWeight();
+		itemMan.calculateInventoryWeight();
+		return true;
+	}
+
+	/**
+	 * Loot an item from an AbstractCharacter. Call this function on
+	 * the CharacterItemManager of the current item owner, not the looter.
+	 * This method will verify that the looter can receive the item
+	 * (e.g. inventory isn't full).
+	 *
+	 * @param i Item being looted
+	 * @param looter Player looting the item
+	 * @param origin ClientConnection
+	 * @return True on success
+	 */
+	public synchronized Item lootItemFromMe(Item i, PlayerCharacter looter, ClientConnection origin) {
+		return lootItemFromMe(i, looter, origin, false, -1);
+	}
+
+	//This function is used for both looting and stealing
+	public synchronized Item lootItemFromMe(Item lootItem, PlayerCharacter lootingPlayer, ClientConnection origin, boolean fromSteal, int amount) {
+
+		//TODO this function should have more logging
+		// make sure lootingPlayer exists
+		if (lootingPlayer == null)
+			return null;
+
+		// get looters item manager
+		CharacterItemManager looterItems = lootingPlayer.getCharItemManager();
+
+		if (looterItems == null)
+			return null;
+
+		if (fromSteal) {
+			if (!this.absCharacter.isAlive())
+				return null;
+		} else if (!this.absCharacter.canBeLooted())
+			return null;
+
+		MobLoot mobLoot = null;
+		if (lootItem instanceof MobLoot) {
+			mobLoot = (MobLoot) lootItem;
+			if (mobLoot.isDeleted())
+				return null;
+		}
+
+		//Lock both ItemManagers; lower ID first
+		CharacterItemManager lockFirst;
+		CharacterItemManager lockSecond;
+		if (this.absCharacter.getObjectUUID()
+				< looterItems.absCharacter.getObjectUUID()) {
+			lockFirst = this;
+			lockSecond = looterItems;
+		} else {
+			lockFirst = looterItems;
+			lockSecond = this;
+		}
+
+		synchronized (lockFirst) {
+			synchronized (lockSecond) {
+				// make sure current player has item in inventory
+				if (lootItem.getItemBase().getType().equals(ItemType.GOLD) && lootItem.getObjectUUID() != this.getGoldInventory().getObjectUUID() && !(this.absCharacter.getObjectType().equals(GameObjectType.Mob)))
+					return null;
+				else if (!this.inventory.contains(lootItem) && !this.getEquippedList().contains(lootItem) && !lootItem.getItemBase().getType().equals(ItemType.GOLD))
+					return null;
+
+				// get weight of item
+				ItemBase ib = lootItem.getItemBase();
+				if (ib == null)
+					return null;
+				short weight = ib.getWeight();
+
+				// make sure lootingPlayer has room for item
+				if (!lootItem.getItemBase().getType().equals(ItemType.GOLD) && !looterItems.hasRoomInventory(weight))
+					return null;
+
+				if (lootItem.getItemBase().getType().equals(ItemType.GOLD))
+					if (amount != -1) { //from steal
+						int total = lootItem.getNumOfItems();
+						amount = (amount > total) ? total : amount;
+						if (!looterItems.moveGoldToInventory(lootItem, amount))
+							return null;
+						if (mobLoot != null && amount == total)
+							this.delete(mobLoot);
+					} else { //from loot
+						if (!looterItems.moveGoldToInventory(lootItem, lootItem.getNumOfItems()))
+							return null;
+						if (mobLoot != null) // delete mobloot after it has been looted
+							this.delete(mobLoot);
+					}
+				else {  //not Gold item
+					boolean created = false;
+					if (mobLoot != null) {
+                        lootItem = mobLoot.promoteToItem(lootingPlayer);
+
+						// delete mobloot after it has been looted
+						this.delete(mobLoot);
+						if (lootItem == null)
+							return null;
+
+						created = true;
+					}
+
+					// attempt to transfer item in db
+
+					if (!lootItem.moveItemToInventory(lootingPlayer))
+						return null;
+
+					// db transfer successfull, remove from this character
+					// skip this check if this is a mobLoot item (which is not in any inventory)
+					if (mobLoot == null)
+						if (!removeItemFromInventory(lootItem))
+							return null;
+
+					// add item to lootingPlayer.
+					if (!looterItems.addItemToInventory(lootItem))
+						return null;
+				}
+			}
+		}
+
+		// calculate new weights
+		calculateInventoryWeight();
+		looterItems.calculateInventoryWeight();
+
+		return lootItem;
+	}
+
+	private synchronized void remItemFromLists(Item i, byte slot) {
+
+		this.equipped.remove((int) slot);
+		this.vault.remove(i);
+		this.bank.remove(i);
+		this.inventory.remove(i);
+	}
+
+	/*
+	 * Delegates
+	 */
+	public synchronized boolean bankContains(Item i) {
+		if (i.getItemBase().getType().equals(ItemType.GOLD))
+			return (this.getGoldBank() != null && this.goldBank.getObjectUUID() == i.getObjectUUID());
+		return bank.contains(i);
+	}
+
+
+	public synchronized boolean inventoryContains(Item i) {
+		if (i.getItemBase().getType().equals(ItemType.GOLD))
+			return (this.getGoldInventory() != null && this.goldInventory.getObjectUUID() == i.getObjectUUID());
+		return inventory.contains(i);
+	}
+
+	public synchronized boolean forgeContains(Item i,NPC vendor) {
+		if (i.getItemBase().getType().equals(ItemType.GOLD))
+			return (this.getGoldInventory() != null && this.goldInventory.getObjectUUID() == i.getObjectUUID());
+		return vendor.getRolling().contains(i);
+	}
+
+	
+
+	public synchronized boolean vaultContains(Item i) {
+		if (i.getItemBase().getType().equals(ItemType.GOLD))
+			return (this.getGoldVault() != null && this.goldVault.getObjectUUID() == i.getObjectUUID());
+		return this.account.getVault().contains(i);
+	}
+
+	public synchronized boolean vaultContainsType(ItemBase ib) {
+		if (ib.getUUID() == 7)
+			return (this.getGoldVault() != null);
+		for (Item i : vault) {
+			if (i.getItemBase().getUUID() == ib.getUUID())
+				return true;
+		}
+		return false;
+	}
+
+	//for calling from devCmd fill vault. Already synchronized
+	public boolean vaultContainsTypeA(ItemBase ib) {
+		if (ib.getUUID() == 7)
+			return (this.getGoldVault() != null);
+		for (Item i : vault) {
+			if (i.getItemBase().getUUID() == ib.getUUID())
+				return true;
+		}
+		return false;
+	}
+
+	
+	public synchronized boolean equippedContains(Item i) {
+		return equipped.containsValue(i);
+	}
+
+	public synchronized Item getItemFromEquipped(int slot) {
+		return equipped.get(slot);
+	}
+
+	public synchronized Item getItemByUUID(int objectUUID) {
+		if (this.itemIDtoType.containsKey(objectUUID)){
+
+			Integer integer = this.itemIDtoType.get(objectUUID);
+			if (integer == GameObjectType.Item.ordinal()) {
+				return Item.getFromCache(objectUUID);
+			} else if (integer == GameObjectType.MobLoot.ordinal()) {
+				return MobLoot.getFromCache(objectUUID);
+			}
+
+		}
+
+		if (this.getGoldInventory() != null && this.goldInventory.getObjectUUID() == objectUUID)
+			return this.goldInventory;
+		if (this.getGoldBank() != null && this.goldBank.getObjectUUID() == objectUUID)
+			return this.goldBank;
+		if (this.getGoldVault() != null && this.goldVault.getObjectUUID() == objectUUID)
+			return this.goldVault;
+		return null;
+	}
+
+	public boolean tradingContains(Item i) {
+		if (this.trading == null || i == null)
+			return false;
+		return this.trading.contains(i.getObjectUUID());
+	}
+
+	public boolean isBankOpen() {
+		return this.bankOpened;
+	}
+
+	public synchronized void setBankOpen(boolean bankOpened) {
+		this.bankOpened = bankOpened;
+	}
+
+	public boolean isVaultOpen() {
+		return this.vaultOpened;
+	}
+
+	public synchronized void setVaultOpen(boolean vaultOpened) {
+		this.vaultOpened = vaultOpened;
+	}
+
+	public ClientConnection getTradingWith() {
+		return tradingWith;
+	}
+
+	public synchronized void setTradingWith(ClientConnection tradingWith) {
+		this.tradingWith = tradingWith;
+	}
+
+	public synchronized void clearTradingWith() {
+		this.tradingWith = null;
+	}
+
+	public int getGoldTrading() {
+		return goldTradingAmount;
+	}
+
+	public synchronized void setTradeCommitted(byte tradeCommitted) {
+		this.tradeCommitted = tradeCommitted;
+	}
+
+	public byte getTradeCommitted() {
+		return tradeCommitted;
+	}
+
+	public HashSet<Integer> getTrading() {
+		return trading;
+	}
+
+
+	public synchronized void addItemToTrade(Item i) {
+		this.trading.add(i.getObjectUUID());
+	}
+
+
+	public synchronized void setTradeSuccess(boolean tradeSuccess) {
+		this.tradeSuccess = tradeSuccess;
+	}
+
+	public boolean getTradeSuccess() {
+		return tradeSuccess;
+	}
+
+	
+	public synchronized boolean RemoveEquipmentFromLackOfSkill(PlayerCharacter pc, boolean initialized) {
+		
+
+		if (pc == null)
+			return false;
+		
+		if (this.equipped == null)
+			return false;
+
+		
+		for (int slot : this.equipped.keySet()) {
+
+			if (slot == MBServerStatics.SLOT_HAIRSTYLE || slot == MBServerStatics.SLOT_BEARDSTYLE)
+				continue;
+
+			Item item = this.equipped.get(slot);
+
+			if (item == null){
+				this.equipped.remove(slot);
+				pc.applyBonuses();
+				continue;
+			}
+				
+			if (!item.getItemBase().validForSkills(pc.getSkills())){
+				this.forceToInventory(slot, item, pc, initialized);
+				pc.applyBonuses();
+			}
+		}
+
+		return true;
+	}
+
+	/*
+	 * List Copiers
+	 */
+	/**
+	 * Note that this method returns a <b>copy</b> of the internally stored
+	 * list.
+	 *
+	 * @return the equipped
+	 */
+	public ConcurrentHashMap<Integer, Item> getEquipped() {
+		synchronized (this.equipped) {
+			return new ConcurrentHashMap<>(this.equipped);
+		}
+	}
+
+	public ArrayList<Item> getEquippedList() {
+		ArrayList<Item> ret = new ArrayList<>();
+		synchronized (this.equipOrder) {
+			synchronized (this.equipped) {
+				for (int slot : this.equipOrder) {
+					if (this.equipped.containsKey(slot))
+						ret.add(this.equipped.get(slot));
+				}
+				if (ret.size() != this.equipped.size())
+					//missed adding some items, figure out what.
+					for (int slot : this.equipped.keySet()) {
+						if (!(this.equipOrder.contains(slot))) {
+							this.equipOrder.add(slot);
+							ret.add(this.equipped.get(slot));
+						}
+					}
+			}
+		}
+		return ret;
+	}
+
+	public Item getEquipped(int slot) {
+		synchronized (this.equipped) {
+			return this.equipped.get(slot);
+		}
+	}
+
+	/**
+	 * Note that this method returns a <b>copy</b> of the internally stored
+	 * list.
+	 *
+	 * @return the inventory
+	 */
+	public ArrayList<Item> getInventory() {
+		return getInventory(false);
+	}
+
+	public ArrayList<Item> getInventory(boolean sendGold) {
+		synchronized (this.inventory) {
+			ArrayList<Item> ret = new ArrayList<>(this.inventory);
+			if (sendGold && this.getGoldInventory() != null && this.goldInventory.getNumOfItems() > 0)
+				ret.add(this.goldInventory);
+			return ret;
+		}
+	}
+
+	public int getInventoryCount() {
+		synchronized (this.inventory) {
+			return this.inventory.size();
+		}
+	}
+
+	/**
+	 * Clears ownership of items. Called when player dies, but before
+	 * respawning.
+	 *
+	 * @return the inventory
+	 */
+	public synchronized void orphanInventory() {
+		PlayerCharacter pc = null;
+		if (this.absCharacter != null && this.absCharacter.getObjectType().equals(GameObjectType.PlayerCharacter))
+			pc = (PlayerCharacter) this.absCharacter;
+		synchronized (this.inventory) {
+			//dupe check, validate player properly owns all items
+			if (pc != null) {
+				Iterator<Item> iter = this.inventory.iterator();
+				while (iter.hasNext()) {
+					Item item = iter.next();
+					//this call may remove the item from this.inventory
+					if (!item.validForInventory(pc.getClientConnection(), pc, this)) {
+					}
+				}
+			}
+
+			if (this.inventory.size() > 0)
+				DbManager.ItemQueries.ORPHAN_INVENTORY(this.inventory);
+			//make a copy of gold inventory for looting
+			//so we don't remove the goldInventory
+			if (this.getGoldInventory().getNumOfItems() > 0) {
+				int amt = this.goldInventory.getNumOfItems();
+				if (DbManager.ItemQueries.UPDATE_GOLD(this.goldInventory, 0)) {
+					this.goldInventory.setNumOfItems(0);
+					MobLoot gold = new MobLoot(this.absCharacter, amt);
+					this.inventory.add(gold);
+				}
+			}
+		}
+	}
+
+	/**
+	 * This transfers the entire inventory to another list For populating
+	 * corpse' inventory when player dies
+	 *
+	 * @return the inventory
+	 */
+	public synchronized void transferEntireInventory(
+			ArrayList<Item> newInventory, Corpse corpse, boolean enterWorld) {
+
+		PlayerCharacter pc = null;
+		if (this.absCharacter != null && this.absCharacter.getObjectType().equals(GameObjectType.PlayerCharacter))
+			pc = (PlayerCharacter) this.absCharacter;
+
+		if (this.getGoldInventory().getNumOfItems() > 0) {
+			int amt = this.goldInventory.getNumOfItems();
+			if (DbManager.ItemQueries.UPDATE_GOLD(this.goldInventory, 0)) {
+				this.goldInventory.setNumOfItems(0);
+				MobLoot gold = new MobLoot(this.absCharacter, amt);
+				newInventory.add(gold);
+			}
+		}
+
+		for (Item item : this.inventory) {
+			if (item != null)
+				if (item instanceof MobLoot) {
+
+					//MobLoot
+					item.zeroItem();
+					item.containerType = Enum.ItemContainerType.INVENTORY;
+
+					if (item.getItemBase().getType().equals(ItemType.GOLD))
+						//only add gold item once
+						if (!corpse.hasGold())
+							corpse.setHasGold(true);
+					newInventory.add(item);
+				} else //item
+					if (item.getItemBase().getType().equals(ItemType.GOLD)) {
+						int amt = item.getNumOfItems();
+						item.setNumOfItems(0);
+						MobLoot ml = new MobLoot(this.absCharacter, amt);
+						ml.zeroItem();
+						ml.containerType = Enum.ItemContainerType.INVENTORY;
+						if (!corpse.hasGold()) {
+							corpse.setHasGold(true);
+							newInventory.add(ml);
+						}
+					} else {
+						boolean transferred = item.moveItemToInventory(corpse);
+						if (!transferred)
+							Logger.error(
+									"CharItemManager.transferEntireInvetory",
+									"DB Error, Failed to transfer item "
+											+ item.getObjectUUID() + " to new owner "
+											+ corpse.getObjectUUID());
+						newInventory.add(item);
+
+					}
+		}
+
+		// tell client we're clearing inventory
+
+
+		// clear the inventory.
+		this.inventory.clear();
+
+		//re-calculate inventory weight
+		calculateInventoryWeight();
+		if (!enterWorld)
+			updateInventory(this.getInventory(), false);
+	}
+	
+	public synchronized void purgeInventory() {
+		
+		if (!this.absCharacter.getObjectType().equals(GameObjectType.PlayerCharacter))
+			return;
+		
+		if (this.goldInventory != null)
+		if (this.getGoldInventory().getNumOfItems() > 0) {
+			if (DbManager.ItemQueries.UPDATE_GOLD(this.goldInventory, 0)) {
+				this.goldInventory.setNumOfItems(0);
+			}
+		}
+
+		if (this.inventory.size() > 0)
+			DbManager.ItemQueries.ORPHAN_INVENTORY(this.inventory);
+
+		// clear the inventory.
+		this.inventory.clear();
+		//re-calculate inventory weight
+		calculateInventoryWeight();
+	}
+
+	/**
+	 * Note that this method returns a <b>copy</b> of the internally stored
+	 * list.
+	 *
+	 * @return the bank
+	 */
+	public ArrayList<Item> getBank() {
+		synchronized (this.bank) {
+			ArrayList<Item> ret = new ArrayList<>(this.bank);
+			if (this.getGoldBank() != null && this.goldBank.getNumOfItems() > 0)
+				ret.add(this.goldBank);
+			return ret;
+		}
+	}
+
+	/**
+	 * Note that this method returns a <b>copy</b> of the internally stored
+	 * list.
+	 *
+	 * @return the vault
+	 */
+	public ArrayList<Item> getVault() {
+		synchronized (this.vault) {
+			ArrayList<Item> ret = new ArrayList<>(this.vault);
+			if (this.getGoldVault() != null && this.goldVault.getNumOfItems() > 0)
+				ret.add(this.goldVault);
+			return ret;
+		}
+	}
+
+	public boolean hasRoomInventory(short weight) {
+		if (this.absCharacter == null)
+			return false;
+		if (this.absCharacter.getObjectType() == GameObjectType.PlayerCharacter) {
+			PlayerCharacter pc = (PlayerCharacter) this.absCharacter;
+			int newWeight = this.getCarriedWeight() + weight;
+			return newWeight <= (int) pc.statStrBase * 3;
+        } else if (this.absCharacter.getObjectType() == GameObjectType.NPC){
+			int newWeight = this.getCarriedWeight() + weight;
+            return newWeight <= 1900 + (this.absCharacter.getLevel() * 3);
+        }else
+			return true; // npc's need checked
+	}
+
+	public boolean hasRoomTrade(short itemWeight) {
+
+		PlayerCharacter playerCharacter;
+		PlayerCharacter tradeCharacter;
+
+		int tradeWeight;
+
+		if (this.absCharacter == null)
+			return false;
+
+		if (this.absCharacter.getObjectType().equals(GameObjectType.PlayerCharacter) == false)
+			return false;
+
+		   playerCharacter = (PlayerCharacter) this.absCharacter;
+
+		if ((this.tradingWith == null) ||
+    		(this.tradingWith.isConnected() == false))
+		    return false;
+
+		   tradeCharacter = this.tradingWith.getPlayerCharacter();
+
+		   tradeWeight = this.getCarriedWeight() + itemWeight;
+		   tradeWeight = tradeWeight + tradeCharacter.getCharItemManager().getTradingWeight();
+		   tradeWeight = tradeWeight - this.getTradingWeight();
+
+		   return tradeWeight <= (int) playerCharacter.statStrBase * 3;
+	}
+
+	public boolean hasRoomBank(short weight) {
+		if (this.absCharacter == null)
+			return false;
+        return weight <= this.absCharacter.getBankCapacityRemaining();
+    }
+
+	public boolean hasRoomVault(short weight) {
+		if (this.absCharacter == null)
+			return false;
+        return weight <= this.absCharacter.getVaultCapacityRemaining();
+    }
+
+	public int getCarriedWeight() {
+		return getInventoryWeight() + getEquipWeight();
+	}
+
+	public int getInventoryWeight() {
+		return this.inventoryWeight;
+	}
+
+	public int getBankWeight() {
+		return this.bankWeight;
+	}
+
+	public int getEquipWeight() {
+		return this.equipWeight;
+	}
+
+	public int getVaultWeight() {
+		return this.vaultWeight;
+	}
+
+	public int getTradingForWeight() {
+		return calculateTradingForWeight();
+	}
+
+	public int getTradingWeight() {
+
+		int weight = 0;
+		Item item;
+
+		for (int i : this.trading) {
+			item = Item.getFromCache(i);
+
+			if (item == null)
+				continue;
+
+			ItemBase ib = item.getItemBase();
+			weight += ib.getWeight();
+		}
+		return weight;
+	}
+
+	public AbstractCharacter getOwner() {
+		return this.absCharacter;
+	}
+
+	public void calculateWeights() {
+		calculateBankWeight();
+		calculateInventoryWeight();
+		calculateEquipWeight();
+		calculateVaultWeight();
+	}
+
+	public void calculateBankWeight() {
+		this.bankWeight = 0;
+		for (Item i : this.bank) {
+			ItemBase ib = i.getItemBase();
+			if (ib != null)
+				this.bankWeight += ib.getWeight();
+		}
+	}
+
+	public void calculateEquipWeight() {
+		this.equipWeight = 0;
+		Collection<Item> c = this.equipped.values();
+		Iterator<Item> it = c.iterator();
+		while (it.hasNext()) {
+			Item i = it.next();
+			ItemBase ib = i.getItemBase();
+			if (ib != null)
+				this.equipWeight += ib.getWeight();
+		}
+	}
+
+	public void calculateInventoryWeight() {
+		this.inventoryWeight = 0;
+		for (Item i : this.inventory) {
+			ItemBase ib = i.getItemBase();
+			if (ib != null)
+				this.inventoryWeight += ib.getWeight();
+		}
+	}
+
+	public void calculateVaultWeight() {
+		this.vaultWeight = 0;
+		for (Item i : this.vault) {
+			ItemBase ib = i.getItemBase();
+			if (ib != null)
+				this.vaultWeight += ib.getWeight();
+		}
+	}
+
+	private int calculateTradingForWeight() {
+		int tradingForWeight = 0;
+		
+		return tradingForWeight;
+	}
+
+
+	public void updateInventory(Item item, boolean add) {
+		ArrayList<Item> list = new ArrayList<>();
+		list.add(item);
+		updateInventory(list, add);
+	}
+
+	private void updateInventory(ArrayList<Item> inventory, boolean add) {
+
+		if (this.absCharacter == null)
+			return;
+
+		if (this.absCharacter.getObjectType().equals(GameObjectType.PlayerCharacter) == false)
+			return;
+
+		PlayerCharacter pc = (PlayerCharacter) this.absCharacter;
+
+		UpdateInventoryMsg updateInventoryMsg = new UpdateInventoryMsg(inventory, this.getBank(), this.getGoldInventory(), add);
+		Dispatch dispatch = Dispatch.borrow(pc, updateInventoryMsg);
+		DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+
+	}
+
+	public void forceToInventory(int slot, Item item, PlayerCharacter pc, boolean initialized) {
+		if (item == null || pc == null)
+			return;
+
+		if (!item.moveItemToInventory(pc)) {
+			//TODO well why did this fail? clean it up
+		}
+
+		// remove it from other lists:
+		this.remItemFromLists(item, (byte) slot);
+
+		// add to Inventory
+		this.inventory.add(item);
+		item.addToCache();
+
+		calculateWeights();
+
+		//Update players with unequipped item
+		if (initialized) {
+			TransferItemFromEquipToInventoryMsg back = new TransferItemFromEquipToInventoryMsg(pc, slot);
+			DispatchMessage.dispatchMsgToInterestArea(pc, back,  engine.Enum.DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, true, false);
+		}
+
+	}
+
+	/**
+	 * Update the player's inventory window by resending the entire contents.
+	 */
+	public void updateInventory() {
+		this.updateInventory(this.getInventory(), true);
+	}
+
+	public synchronized void initializeTrade() {
+		this.trading = new HashSet<>();
+	}
+
+	public synchronized boolean commitTrade() {
+		int goldFrom1 = 0;
+		int goldFrom2 = 0;
+
+		if (this.getTradingWith() == null || this.getTradingWith().isConnected() == false
+				|| this.getTradingWith().getPlayerCharacter() == null){
+			this.endTrade();
+			return false;
+		}
+			
+		
+		CharacterItemManager tradingWith = this.getTradingWith().getPlayerCharacter().getCharItemManager();
+		
+		if (tradingWith == null)
+			return false;
+		
+		if (this.goldTradingAmount != 0) {
+			
+			if (tradingWith.goldInventory == null){
+				Logger.error("Null Gold for player " + this.getOwner().getObjectUUID());
+				return false;
+			}
+			goldFrom1 = this.goldTradingAmount;
+		}
+		if (tradingWith.goldTradingAmount != 0) {
+			
+			if (this.getGoldInventory() == null){
+				Logger.error("Null Gold for player " + this.getOwner().getObjectUUID());
+				return false;
+			}
+			goldFrom2 = tradingWith.goldTradingAmount;
+		}
+		
+
+		if (this.getGoldInventory().getNumOfItems() + goldFrom2 > 10000000){
+			PlayerCharacter pc = (PlayerCharacter)this.absCharacter;
+			if (pc.getClientConnection() != null)
+				ErrorPopupMsg.sendErrorPopup(pc, 202);
+			return false;
+		}
+		
+		
+		if (tradingWith.getGoldInventory().getNumOfItems() + goldFrom1 > 10000000){
+			PlayerCharacter pc = (PlayerCharacter)tradingWith.absCharacter;
+			if (pc.getClientConnection() != null)
+				ErrorPopupMsg.sendErrorPopup(pc, 202);
+			return false;
+		}
+
+		if (this.trading.size() > 0 || tradingWith.trading.size() > 0 || goldFrom1 > 0 || goldFrom2 > 0) {
+			if (!DbManager.ItemQueries.DO_TRADE(this.trading, tradingWith.trading, this, tradingWith,
+					this.goldInventory, tradingWith.goldInventory, goldFrom1, goldFrom2))
+				return false;
+		} else
+			return true;
+
+		for (int i : this.trading) {
+			Item item = Item.getFromCache(i);
+			if (item == null)
+				continue;
+			this.trade(item);
+			tradingWith.tradeForItem(item);
+		}
+		for (int i : tradingWith.trading) {
+			Item item = Item.getFromCache(i);
+			if (item == null)
+				continue;
+			tradingWith.trade(item);
+			this.tradeForItem(item);
+		}
+		
+		//subtract gold your trading from your inventory.
+		if (this.goldTradingAmount > 0)
+			this.getGoldInventory().setNumOfItems(this.getGoldInventory().getNumOfItems() - this.goldTradingAmount);
+		//subtract gold your trading from your inventory.
+		if (tradingWith.goldTradingAmount > 0)
+			tradingWith.getGoldInventory().setNumOfItems(tradingWith.getGoldInventory().getNumOfItems() - tradingWith.goldTradingAmount);
+		
+		if (tradingWith.goldTradingAmount > 0)
+			this.getGoldInventory().setNumOfItems(this.goldInventory.getNumOfItems()
+					+ tradingWith.goldTradingAmount);
+		if (this.goldTradingAmount > 0)
+			tradingWith.getGoldInventory().setNumOfItems(tradingWith.goldInventory.getNumOfItems()
+					+ this.goldTradingAmount);
+
+		this.tradeSuccess = true;
+		tradingWith.tradeSuccess = true;
+		
+		return true;
+		
+	}
+
+	public synchronized void endTrade() {
+		updateInventory(this.getInventory(), true);
+		this.tradeCommitted = (byte) 0;
+		this.tradeSuccess = false;
+		this.tradingWith = null;
+		this.trading = null;
+		this.goldTradingAmount = 0;
+		this.tradeID = 0;
+	}
+
+	public synchronized void endTrade(boolean fromDeath) {
+		this.tradeCommitted = (byte) 0;
+		this.tradeSuccess = false;
+		this.tradingWith = null;
+		this.trading = null;
+		this.goldTradingAmount = 0;
+	}
+
+	// Remove item from your possession
+	private synchronized boolean trade(Item i) {
+		if (this.doesCharOwnThisItem(i.getObjectUUID()) == false)
+			return false;
+
+		// Only valid from inventory
+		if (!inventoryContains(i))
+			return false;
+
+		// remove from Inventory
+		this.inventory.remove(i);
+		this.itemIDtoType.remove(i.getObjectUUID());
+		i.setOwnerID(0);
+
+		calculateWeights();
+
+		return true;
+	}
+
+	//Damage an equipped item a specified amount
+	public void damageItem(Item item, int amount) {
+		if (item == null || amount < 1 || amount > 5)
+			return;
+
+		//verify the item is equipped by this player
+		int slot = item.getEquipSlot();
+		if (!this.equipped.containsKey(slot))
+			return;
+		Item verify = this.equipped.get(slot);
+		if (verify == null || item.getObjectUUID() != verify.getObjectUUID())
+			return;
+
+		//don't damage noob gear, hair or beards.
+		if (item.getDurabilityMax() == 0)
+			return;
+
+		if (!item.isCanDestroy())
+			return;
+
+		int dur = (int) item.getDurabilityCurrent();
+		if (dur - amount <= 0) {
+			//destroy the item
+			junk(item);
+
+			//TODO remove item from the client
+			//This may not be correct
+			dur = 0;
+		} else {
+			dur -= amount;
+			if (!DbManager.ItemQueries.SET_DURABILITY(item, dur))
+				return;
+			item.setDurabilityCurrent((short) dur);
+
+		}
+
+		if (this.absCharacter.getObjectType().equals(GameObjectType.PlayerCharacter) == false)
+			return;
+
+		//send damage item msg to client
+		PlayerCharacter pc = (PlayerCharacter) this.absCharacter;
+
+		ItemHealthUpdateMsg itemHealthUpdateMsg = new ItemHealthUpdateMsg(slot, (float) dur);
+		Dispatch dispatch = Dispatch.borrow(pc, itemHealthUpdateMsg);
+		DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+
+	}
+
+	//Damage a random piece of armor a specified amount
+	public void damageRandomArmor(int amount) {
+		ArrayList<Item> armor = new ArrayList<>();
+		if (this.equipped.containsKey(MBServerStatics.SLOT_OFFHAND)) {
+			Item item = this.equipped.get(MBServerStatics.SLOT_OFFHAND);
+			ItemBase ib = item.getItemBase();
+			if (ib.isShield())
+				armor.add(item);
+		}
+		if (this.equipped.containsKey(MBServerStatics.SLOT_HELMET))
+			armor.add(this.equipped.get(MBServerStatics.SLOT_HELMET));
+		if (this.equipped.containsKey(MBServerStatics.SLOT_CHEST))
+			armor.add(this.equipped.get(MBServerStatics.SLOT_CHEST));
+		if (this.equipped.containsKey(MBServerStatics.SLOT_ARMS))
+			armor.add(this.equipped.get(MBServerStatics.SLOT_ARMS));
+		if (this.equipped.containsKey(MBServerStatics.SLOT_GLOVES))
+			armor.add(this.equipped.get(MBServerStatics.SLOT_GLOVES));
+		if (this.equipped.containsKey(MBServerStatics.SLOT_GLOVES))
+			armor.add(this.equipped.get(MBServerStatics.SLOT_GLOVES));
+		if (this.equipped.containsKey(MBServerStatics.SLOT_LEGGINGS))
+			armor.add(this.equipped.get(MBServerStatics.SLOT_LEGGINGS));
+		if (this.equipped.containsKey(MBServerStatics.SLOT_FEET))
+			armor.add(this.equipped.get(MBServerStatics.SLOT_FEET));
+
+		if (armor.isEmpty())
+			return; //nothing to damage
+
+		int roll = ThreadLocalRandom.current().nextInt(armor.size());
+		damageItem(armor.get(roll), amount);
+	}
+
+	//Damage all equipped gear a random amount between 1 and 5
+	public void damageAllGear() {
+		for (Item gear : this.equipped.values()) {
+			damageItem(gear, (ThreadLocalRandom.current().nextInt(5) + 1));
+		}
+	}
+
+	// Add item to your possession
+	public synchronized boolean tradeForItem(Item i) {
+		// add to Inventory
+		this.inventory.add(i);
+		this.itemIDtoType.put(i.getObjectUUID(), i.getObjectType().ordinal());
+        i.setOwnerID(this.absCharacter.getObjectUUID());
+
+		calculateWeights();
+
+		return true;
+	}
+
+	public synchronized boolean addGoldToTrade(int amount) {
+		
+		if (this.goldTradingAmount + amount > MBServerStatics.PLAYER_GOLD_LIMIT)
+			return false;
+		
+		this.goldTradingAmount += amount;
+		
+		return true;
+	}
+
+	/**
+	 * Completely empties inventory, deleting any items. Use with caution!
+	 */
+	public synchronized void clearInventory() {
+		this.getGoldInventory().setNumOfItems(0);
+		Iterator<Item> ii = this.inventory.iterator();
+		while (ii.hasNext()) {
+			Item itm = ii.next();
+			ii.remove();
+			this.delete(itm);
+		}
+	}
+
+	public synchronized void clearEquip() {
+
+		ArrayList<Item> equipCopy = new ArrayList<>(this.getEquippedList());
+		Iterator<Item> ii = equipCopy.iterator();
+		while (ii.hasNext()) {
+			Item itm = ii.next();
+			this.getEquippedList().remove(itm);
+			this.delete(itm);
+		}
+	}
+
+	public byte getEquipVer() {
+		return this.equipVer;
+	}
+
+	public static byte getInventoryVer() {
+		return inventoryVer;
+	}
+
+	public static byte getBankVer() {
+		return bankVer;
+	}
+
+	public static byte getVaultVer() {
+		return vaultVer;
+	}
+
+	public void incEquipVer() {
+		this.equipVer++;
+	}
+
+	public void incInventoryVer() {
+		this.equipVer++;
+	}
+
+	public void incBankVer() {
+		this.equipVer++;
+	}
+
+	public void incVaultVer() {
+		this.equipVer++;
+	}
+
+	public static void takeFromNPC(NPC npc, PlayerCharacter pc, Item take, ClientMessagePump clientMessagePump) {
+		ItemBase ib = take.getItemBase();
+		if (ib == null)
+			return;
+		CharacterItemManager itemMan = pc.getCharItemManager();
+		if (itemMan == null)
+			return;
+		CharacterItemManager npcCim = npc.getCharItemManager();
+		if (npcCim == null)
+			return;
+		if (!npcCim.inventoryContains(take)) {
+			return;
+		}
+
+		if (!itemMan.hasRoomInventory(ib.getWeight()))
+			return;
+		if (take != null) {
+			itemMan.buyFromNPC(take, npc);
+			itemMan.updateInventory();
+		}
+	}
+
+	public int getTradeID() {
+		return tradeID;
+	}
+	
+	public synchronized boolean closeTradeWindow(){
+		if (this.getTradingWith() != null || this.getTradeID() != 0)
+			this.closeTradeWindow(new CloseTradeWindowMsg(this.getOwner(), this.getTradeID()), true);
+		return true;
+
+	}
+
+}
diff --git a/src/engine/objects/CharacterPower.java b/src/engine/objects/CharacterPower.java
new file mode 100644
index 00000000..7ef1a3d7
--- /dev/null
+++ b/src/engine/objects/CharacterPower.java
@@ -0,0 +1,628 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.objects;
+
+import engine.gameManager.DbManager;
+import engine.gameManager.PowersManager;
+import engine.net.ByteBufferWriter;
+import engine.net.client.msg.ErrorPopupMsg;
+import engine.powers.PowersBase;
+import engine.server.MBServerStatics;
+import org.pmw.tinylog.Logger;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicInteger;
+
+
+public class CharacterPower extends AbstractGameObject {
+
+	private final PowersBase power;
+	private AtomicInteger trains = new AtomicInteger();
+	private short grantedTrains;
+	private int ownerUID;
+	private boolean trained = false;
+	private int requiredLevel = 0;
+
+
+
+
+	/**
+	 * No Table ID Constructor
+	 */
+	public CharacterPower(PowersBase power, PlayerCharacter pc) {
+		super();
+		this.power = power;
+		this.trains.set(0);
+        this.grantedTrains = this.grantedTrains;
+		this.ownerUID = pc.getObjectUUID();
+
+	}
+
+	/**
+	 * Normal Constructor
+	 */
+	public CharacterPower(PowersBase power, PlayerCharacter pc, int newUUID) {
+		super(newUUID);
+		this.power = power;
+		this.trains.set(0);
+        this.grantedTrains = this.grantedTrains;
+		this.ownerUID = pc.getObjectUUID();
+
+
+	}
+
+
+	/**
+	 * ResultSet Constructor
+	 */
+	public CharacterPower(ResultSet rs, PlayerCharacter pc) throws SQLException {
+		super(rs);
+		int powersBaseToken = rs.getInt("PowersBaseToken");
+		this.power = PowersManager.getPowerByToken(powersBaseToken);
+		
+		if (this.power != null && this.power.isWeaponPower())
+			this.trains.set(0);
+		else
+		this.trains.set(rs.getInt("trains"));
+        this.grantedTrains = this.grantedTrains;
+		this.ownerUID = pc.getObjectUUID();
+
+	}
+
+	public CharacterPower(ResultSet rs) throws SQLException {
+		super(rs);
+		int powersBaseToken = rs.getInt("PowersBaseToken");
+		this.power = PowersManager.getPowerByToken(powersBaseToken);
+		this.trains.set(rs.getInt("trains"));
+        this.grantedTrains = this.grantedTrains;
+		this.ownerUID = rs.getInt("CharacterID");
+
+		//		this.owner = DbManager.PlayerCharacterQueries.GET_PLAYER_CHARACTER(rs.getInt("CharacterID"));
+	}
+
+	private short getGrantedTrains(PlayerCharacter pc) {
+		if (this.power != null && pc != null) {
+			//			if (this.power.isWeaponPower()) {
+			//				SkillsBase sb = null;
+			//				try {
+			//					sb = SkillsBase.getSkillsBaseByName(this.power.getSkillName());
+			//				} catch (SQLException e) {}
+			//				if (sb != null) {
+			//					return pc.getBonuses().getByte("gt." + sb.getToken());
+			//				} else
+			//					return pc.getBonuses().getByte("gt." + this.power.getToken());
+			//			} else
+			//				return pc.getBonuses().getByte("gt." + this.power.getToken());
+			return PowerGrant.getGrantedTrains(this.power.getToken(), pc);
+		}
+		else
+			return 0;
+	}
+
+	/*
+	 * Getters
+	 */
+	public PowersBase getPower() {
+		return power;
+	}
+
+	public int getPowerID() {
+		return power.getUUID();
+	}
+
+	public boolean isTrained() {
+		return trained;
+	}
+
+	public static PlayerCharacter getOwner(CharacterPower cp) {
+		return PlayerCharacter.getFromCache(cp.ownerUID);
+	}
+
+	public void setTrained(boolean b) {
+		trained = b;
+	}
+
+	public int getTrains() {
+		return this.trains.get();
+	}
+
+	public short getGrantedTrains() {
+		return this.grantedTrains;
+	}
+
+	public int getTotalTrains() {
+		return (this.trains.get() + this.grantedTrains);
+	}
+
+	public float  getTrainingCost(PlayerCharacter pc, NPC trainer){
+		int charLevel = pc.getLevel();
+		int skillRank = this.trains.get() -1  + this.requiredLevel;
+
+
+		float baseCost = 50 * this.requiredLevel ; //TODO GET BASE COSTS OF SKILLS.
+
+
+
+		float sellPercent = -4f; //NOT SELL PERCENT!
+		float cost;
+		float const5;
+		int const2 = 1;
+		float const3 = 50;
+		float const4 = const3 + const2;
+		if (charLevel > 50)
+			const5 = 50 / const4;
+		else
+			const5 = charLevel/const4;
+
+		const5 = 1-const5;
+		const5 = (float) (Math.log(const5) / Math.log(2) * .75f);
+		float rounded5 = Math.round(const5);
+		const5 = rounded5 - const5;
+
+		const5 *= -1;
+
+		const5 = (float) (Math.pow(2, const5) - 1);
+
+		const5 +=1;
+		const5 = Math.scalb(const5, (int) rounded5);
+        const5 *= (charLevel - skillRank);
+        const5 *= sellPercent;
+
+		const5 = (float) (Math.log(const5) / Math.log(2) * 3);
+		rounded5 = Math.round(const5);
+		const5 = rounded5 - const5;
+		const5 *= -1;
+		const5 = (float) (Math.pow(2, const5) - 1);
+		const5 +=1;
+
+
+		const5 = Math.scalb(const5, (int) rounded5);
+		const5 += 1;
+		cost = const5 * baseCost;
+
+
+		if (Float.isNaN(cost))
+			cost = baseCost;
+		return cost;
+	}
+
+	public synchronized boolean train(PlayerCharacter pc) {
+		if (pc == null || this.power == null)
+			return false;
+
+		//see if any prereqs to train this power is met
+		if (!canTrain(pc))
+			return false;
+
+		boolean succeeded=true;
+		int oldTrains = this.trains.get();
+		int tr = oldTrains + this.grantedTrains;
+		if (pc.getTrainsAvailable() <= 0)
+			return false;
+		if (tr == this.power.getMaxTrains()) //at max, stop here
+			return false;
+		else if (tr > this.power.getMaxTrains()) //catch incase we somehow go over
+			this.trains.set((this.power.getMaxTrains() - this.grantedTrains));
+		else //add the train
+			succeeded = this.trains.compareAndSet(oldTrains, oldTrains+1);
+
+		if (this.trains.get() > this.power.getMaxTrains()) { //double check not over max trains
+			this.trains.set(this.power.getMaxTrains());
+			succeeded = false;
+		}
+
+		if (succeeded) {
+			this.trained = true;
+
+			//update database
+			pc.addDatabaseJob("Skills", MBServerStatics.THIRTY_SECONDS);
+
+			//subtract from trains available
+			pc.modifyTrainsAvailable(-1);
+
+			pc.calculateSkills();
+			return true;
+		} else
+			return false;
+	}
+	
+	public boolean reset(PlayerCharacter pc) {
+		if (pc == null || this.power == null)
+			return false;
+
+		//see if any prereqs to refine this power is met
+		
+		boolean succeeded=true;
+		int oldTrains = this.trains.get();
+		int tr = oldTrains + this.grantedTrains;
+		if (oldTrains < 1)
+			return false;
+		else //subtract the train
+			succeeded = this.trains.compareAndSet(oldTrains, 0);
+		if (succeeded) {
+			this.trained = true;
+
+			//update database
+			pc.addDatabaseJob("Skills", MBServerStatics.THIRTY_SECONDS);
+
+			//subtract from trains available
+			pc.modifyTrainsAvailable(oldTrains);
+
+			pc.calculateSkills();
+			return true;
+		} else
+			return false;
+	}
+
+	public boolean refine(PlayerCharacter pc) {
+		if (pc == null || this.power == null)
+			return false;
+
+		//see if any prereqs to refine this power is met
+		if (!canRefine(pc))
+			return false;
+
+		boolean succeeded=true;
+		int oldTrains = this.trains.get();
+		int tr = oldTrains + this.grantedTrains;
+		if (oldTrains < 1)
+			return false;
+		else //subtract the train
+			succeeded = this.trains.compareAndSet(oldTrains, oldTrains-1);
+		if (succeeded) {
+			this.trained = true;
+
+			//update database
+			pc.addDatabaseJob("Skills", MBServerStatics.THIRTY_SECONDS);
+
+			//subtract from trains available
+			pc.modifyTrainsAvailable(1);
+
+			pc.calculateSkills();
+			return true;
+		} else
+			return false;
+	}
+
+
+	/*
+	 * Utils
+	 */
+
+	/*
+	 * This iterates through players runes and adds and removes powers as needed
+	 * Don't Call this directly. Instead call pc.calculateSkills().
+	 */
+	public static void calculatePowers(PlayerCharacter pc) {
+		if (pc == null)
+			return;
+
+		// First add powers that don't exist
+		ConcurrentHashMap<Integer, CharacterPower> powers = pc.getPowers();
+		//		ArrayList<PowerReq> genericPowers = PowerReq.getPowerReqsForAll();
+		//		CharacterPower.grantPowers(genericPowers, powers, pc);
+		Race race = pc.getRace();
+		if (race != null) {
+			CharacterPower.grantPowers(race.getPowersGranted(), powers, pc);
+		} else
+			Logger.error( "Failed to find Race for player " + pc.getObjectUUID());
+		BaseClass bc = pc.getBaseClass();
+		if (bc != null) {
+			CharacterPower.grantPowers(bc.getPowersGranted(), powers, pc);
+		} else
+			Logger.error( "Failed to find BaseClass for player " + pc.getObjectUUID());
+		PromotionClass promo = pc.getPromotionClass();
+		if (promo != null)
+			CharacterPower.grantPowers(promo.getPowersGranted(), powers, pc);
+		ArrayList<CharacterRune> runes = pc.getRunes();
+		if (runes != null) {
+			for (CharacterRune rune : runes) {
+				CharacterPower.grantPowers(rune.getPowersGranted(), powers, pc);
+			}
+		} else
+			Logger.error("Failed to find Runes list for player " + pc.getObjectUUID());
+
+		// next remove any skills that no longer belong
+		Iterator<Integer> it = powers.keySet().iterator();
+		while (it.hasNext()) {
+			Integer token = it.next();
+			boolean valid = false;
+			//			if (CharacterPower.powerAllowed(token, genericPowers, pc))
+			//				continue;
+			if (CharacterPower.powerAllowed(token, race.getPowersGranted(), pc))
+				continue;
+			if (CharacterPower.powerAllowed(token, bc.getPowersGranted(), pc))
+				continue;
+			if (promo != null)
+				if (CharacterPower.powerAllowed(token, promo.getPowersGranted(), pc))
+					continue;
+			for (CharacterRune rune : runes) {
+				if (CharacterPower.powerAllowed(token, rune.getPowersGranted(), pc)) {
+					valid = true;
+					continue;
+				}
+			}
+
+			// if power doesn't belong to any runes or skill, then remove it
+			if (!valid) {
+				CharacterPower cp = powers.get(token);
+				DbManager.CharacterPowerQueries.DELETE_CHARACTER_POWER(cp.getObjectUUID());
+				it.remove();
+			}
+		}
+	}
+
+	/*
+	 * This grants powers for specific runes
+	 */
+	private static void grantPowers(ArrayList<PowerReq> powersGranted, ConcurrentHashMap<Integer, CharacterPower> powers, PlayerCharacter pc) {
+		ConcurrentHashMap<String, CharacterSkill> skills = pc.getSkills();
+
+		for (PowerReq powerreq : powersGranted) {
+			PowersBase powersBase = powerreq.getPowersBase();
+
+			if (powersBase == null)
+				continue;
+			// skip if player already has power
+			if (powers.containsKey(powerreq.getToken())){
+				CharacterPower cp = powers.get(powersBase.getToken());
+                if (cp != null)
+					if (cp.requiredLevel == 0) {
+						cp.requiredLevel = (int) powerreq.getLevel();
+					}
+
+				continue;
+			}
+
+			// If player not high enough level for power, then skip
+			if (pc.getLevel() < powerreq.getLevel())
+				continue;
+
+			// See if any prereq powers needed
+			boolean valid = true;
+			ConcurrentHashMap<Integer, Byte> preqs = powerreq.getPowerReqs();
+			for (Integer tok : preqs.keySet()) {
+				if (!powers.containsKey(tok))
+					valid = false;
+				else {
+					CharacterPower cpp = powers.get(tok);
+                    if ((cpp.getTrains() + cpp.grantedTrains) < preqs.get(tok))
+						valid = false;
+				}
+			}
+			if (!valid)
+				continue;
+
+			// See if any prereq skills needed
+			preqs = powerreq.getSkillReqs();
+			for (Integer tok : preqs.keySet()) {
+				if (tok == 0)
+					continue;
+				CharacterSkill found = null;
+				for (CharacterSkill sk : skills.values()) {
+					if (sk.getToken() == tok) {
+						found = sk;
+						continue;
+					}
+				}
+				if (found != null) {
+					if (found.getModifiedAmountBeforeMods() < preqs.get(tok))
+						valid = false;
+				} else
+					valid = false;
+			}
+			if (!valid)
+				continue;
+
+
+			if (!powers.containsKey(powersBase.getToken())) {
+				CharacterPower newPower = new CharacterPower(powersBase, pc);
+				CharacterPower cp = null;
+				try {
+					cp = DbManager.CharacterPowerQueries.ADD_CHARACTER_POWER(newPower);
+				} catch (Exception e) {
+					cp = null;
+				}
+				if (cp != null){
+					cp.requiredLevel = (int) powerreq.getLevel();
+					powers.put(powersBase.getToken(), cp);
+				}
+
+				else
+					Logger.error("Failed to add CharacterPower to player " + pc.getObjectUUID());
+			}else{
+				CharacterPower cp = powers.get(powersBase.getToken());
+                if (cp != null)
+					if (cp.requiredLevel == 0) {
+						cp.requiredLevel = (int) powerreq.getLevel();
+					}
+			}
+		}
+	}
+
+	public static void grantTrains(PlayerCharacter pc) {
+		if (pc == null)
+			return;
+		ConcurrentHashMap<Integer, CharacterPower> powers = pc.getPowers();
+		for (CharacterPower cp : powers.values()) {
+			cp.grantedTrains = cp.getGrantedTrains(pc);
+		}
+	}
+
+	/*
+	 * This verifies if a power is valid for a players rune
+	 */
+	private static boolean powerAllowed(Integer token, ArrayList<PowerReq> powersGranted, PlayerCharacter pc) {
+		ConcurrentHashMap<String, CharacterSkill> skills = pc.getSkills();
+		ConcurrentHashMap<Integer, CharacterPower> powers = pc.getPowers();
+		if (skills == null || powers == null)
+			return false;
+		for (PowerReq powerreq : powersGranted) {
+			PowersBase pb = powerreq.getPowersBase();
+			if (pb != null) {
+				if (pb.getToken() == token) {
+
+					//test level requirements
+					if (powerreq.getLevel() > pc.getLevel()) {
+						return false;
+					}
+
+					//test skill requirements are met
+					ConcurrentHashMap<Integer, Byte> skillReqs = powerreq.getSkillReqs();
+					for (int tok : skillReqs.keySet()) {
+						boolean valid = false;
+						if (tok == 0)
+							continue;
+						for (CharacterSkill skill : skills.values()) {
+							if (skill.getToken() == tok) {
+								if (skill.getModifiedAmountBeforeMods() < skillReqs.get(tok))
+									return false;
+								valid = true;
+								break;
+							}
+						}
+						if (!valid)
+							return false;
+					}
+
+					//test power prerequisites are met
+					ConcurrentHashMap<Integer, Byte> powerReqs = powerreq.getPowerReqs();
+					for (int tok : powerReqs.keySet()) {
+						if (!powers.containsKey(tok))
+							return false;
+						CharacterPower cp = powers.get(tok);
+						if (cp.getTotalTrains() < powerReqs.get(tok))
+							return false;
+					}
+
+					//everything passed. power is valid
+					return true;
+				}
+			}
+		}
+		return false;
+	}
+
+	//This verifies the power is not blocked from refining by prereqs on other powers.
+	private boolean canRefine(PlayerCharacter pc) {
+		if (this.power == null || pc == null)
+			return false;
+
+		ConcurrentHashMap<Integer, CharacterPower> powers = pc.getPowers();
+		Race race = pc.getRace();
+		if (race != null) {
+			if (!canRefine(race.getPowersGranted(), powers, pc))
+				return false;
+		} else
+			return false;
+		BaseClass bc = pc.getBaseClass();
+		if (bc != null) {
+			if (!canRefine(bc.getPowersGranted(), powers, pc))
+				return false;
+		} else
+			return false;
+		PromotionClass promo = pc.getPromotionClass();
+		if (promo != null)
+			if (!canRefine(promo.getPowersGranted(), powers, pc))
+				return false;
+		ArrayList<CharacterRune> runes = pc.getRunes();
+		if (runes != null) {
+			for (CharacterRune rune : runes) {
+				if (!canRefine(rune.getPowersGranted(), powers, pc))
+					return false;
+			}
+		}
+
+		//all tests passed. Can refine
+		return true;
+	}
+
+	private boolean canRefine(ArrayList<PowerReq> powersGranted, ConcurrentHashMap<Integer, CharacterPower> powers, PlayerCharacter pc) {
+		for (PowerReq pr : powersGranted) {
+			ConcurrentHashMap<Integer, Byte> powerReqs = pr.getPowerReqs();
+			for (int token : powerReqs.keySet()) {
+				if (token == this.power.getToken()) {
+					//this is a prereq, find the power and make sure it has enough trains
+					int trainsReq = (int)powerReqs.get(token);
+					for (CharacterPower cp : powers.values()) {
+                        if (cp.power.getToken() == pr.getToken()) {
+							if (this.getTotalTrains() <= trainsReq && cp.getTrains() > 0) {
+                                ErrorPopupMsg.sendErrorMsg(pc, "You must refine " + cp.power.getName() + " to 0 before refining any more from this power.");
+								return false;
+							}
+						}
+					}
+				}
+			}
+		}
+		return true;
+	}
+
+	private boolean canTrain(PlayerCharacter pc) {
+		if (this.power == null || pc == null)
+			return false;
+		int token = this.power.getToken();
+		boolean valid = false;
+		Race race = pc.getRace();
+		if (race != null) {
+			if (CharacterPower.powerAllowed(token, race.getPowersGranted(), pc))
+				return true;
+		} else
+			return false;
+		BaseClass bc = pc.getBaseClass();
+		if (bc != null) {
+			if (CharacterPower.powerAllowed(token, bc.getPowersGranted(), pc))
+				return true;
+		} else
+			return false;
+		PromotionClass promo = pc.getPromotionClass();
+		if (promo != null)
+			if (CharacterPower.powerAllowed(token, promo.getPowersGranted(), pc))
+				return true;
+		ArrayList<CharacterRune> runes = pc.getRunes();
+		for (CharacterRune rune : runes)
+			if (CharacterPower.powerAllowed(token, rune.getPowersGranted(), pc))
+				return true;
+		return false;
+	}
+
+	/*
+	 * Serializing
+	 */
+	
+	public static void serializeForClientMsg(CharacterPower characterPower, ByteBufferWriter writer) {
+		if (characterPower.power != null)
+			writer.putInt(characterPower.power.getToken());
+		else
+			writer.putInt(0);
+		writer.putInt(characterPower.getTrains());
+	}
+
+	public static CharacterPower getPower(int tableId) {
+		return DbManager.CharacterPowerQueries.GET_CHARACTER_POWER(tableId);
+	}
+
+	@Override
+	public void updateDatabase() {
+		DbManager.CharacterPowerQueries.updateDatabase(this);
+	}
+
+	public int getRequiredLevel() {
+		return requiredLevel;
+	}
+
+	public void setRequiredLevel(int requiredLevel) {
+		this.requiredLevel = requiredLevel;
+	}
+}
diff --git a/src/engine/objects/CharacterRune.java b/src/engine/objects/CharacterRune.java
new file mode 100644
index 00000000..0eb40073
--- /dev/null
+++ b/src/engine/objects/CharacterRune.java
@@ -0,0 +1,211 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.objects;
+
+import engine.Enum.DispatchChannel;
+import engine.gameManager.DbManager;
+import engine.net.ByteBufferWriter;
+import engine.net.DispatchMessage;
+import engine.net.client.msg.ApplyRuneMsg;
+import engine.server.MBServerStatics;
+import org.pmw.tinylog.Logger;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+
+public class CharacterRune extends AbstractGameObject {
+
+	private final RuneBase runeBase;
+	private final int player;
+	private final ArrayList<SkillReq> skillsGranted;
+	private final ArrayList<PowerReq> powersGranted;
+	private final ArrayList<RuneBaseEffect> effectsGranted;
+
+	/**
+	 * No Table ID Constructor
+	 */
+	public CharacterRune(RuneBase runeBase, int characterID) {
+		super();
+		this.runeBase = runeBase;
+		this.player = characterID;
+		if (this.runeBase != null) {
+			this.skillsGranted = DbManager.SkillReqQueries.GET_REQS_FOR_RUNE(this.runeBase.getObjectUUID());
+			this.powersGranted = PowerReq.getPowerReqsForRune(this.runeBase.getObjectUUID());
+		} else {
+			this.skillsGranted = new ArrayList<>();
+			this.powersGranted = new ArrayList<>();
+		}
+		if (this.runeBase != null)
+			this.effectsGranted = DbManager.RuneBaseEffectQueries.GET_EFFECTS_FOR_RUNEBASE(this.runeBase.getObjectUUID());
+		else
+			this.effectsGranted = new ArrayList<>();
+	}
+
+	/**
+	 * Normal Constructor
+	 */
+	public CharacterRune(RuneBase runeBase, int characterID, int newUUID) {
+		super(newUUID);
+		this.runeBase = runeBase;
+		this.player = characterID;
+		if (this.runeBase != null) {
+			this.skillsGranted = DbManager.SkillReqQueries.GET_REQS_FOR_RUNE(this.runeBase.getObjectUUID());
+			this.powersGranted = PowerReq.getPowerReqsForRune(this.runeBase.getObjectUUID());
+		} else {
+			this.skillsGranted = new ArrayList<>();
+			this.powersGranted = new ArrayList<>();
+		}
+		if (this.runeBase != null)
+			this.effectsGranted = DbManager.RuneBaseEffectQueries.GET_EFFECTS_FOR_RUNEBASE(this.runeBase.getObjectUUID());
+		else
+			this.effectsGranted = new ArrayList<>();
+	}
+
+	/**
+	 * ResultSet Constructor
+	 */
+	public CharacterRune(ResultSet rs) throws SQLException {
+		super(rs);
+
+		this.runeBase = RuneBase.getRuneBase(rs.getInt("RuneBaseID"));
+		this.player = rs.getInt("CharacterID");
+		if (this.runeBase != null) {
+			this.skillsGranted = DbManager.SkillReqQueries.GET_REQS_FOR_RUNE(this.runeBase.getObjectUUID());
+			this.powersGranted = PowerReq.getPowerReqsForRune(this.runeBase.getObjectUUID());
+			this.effectsGranted = DbManager.RuneBaseEffectQueries.GET_EFFECTS_FOR_RUNEBASE(this.runeBase.getObjectUUID());
+		} else {
+			Logger.error("Failed to find RuneBase for CharacterRune " + this.getObjectUUID());
+			this.skillsGranted = new ArrayList<>();
+			this.powersGranted = new ArrayList<>();
+			this.effectsGranted =  new ArrayList<>();
+		}
+	}
+
+	/*
+	 * Getters
+	 */
+	public RuneBase getRuneBase() {
+		return this.runeBase;
+	}
+
+	public int getRuneBaseID() {
+		if (this.runeBase != null)
+			return this.runeBase.getObjectUUID();
+		return 0;
+	}
+
+	public int getPlayerID() {
+		return this.player;
+	}
+
+	public ArrayList<SkillReq> getSkillsGranted() {
+		return this.skillsGranted;
+	}
+
+	public ArrayList<PowerReq> getPowersGranted() {
+		return this.powersGranted;
+	}
+
+	public ArrayList<RuneBaseEffect> getEffectsGranted() {
+		return this.effectsGranted;
+	}
+
+	/*
+	 * Serializing
+	 */
+	
+	public static void serializeForClientMsg(CharacterRune characterRune, ByteBufferWriter writer) {
+		if (characterRune.runeBase != null) {
+			int idd = characterRune.runeBase.getObjectUUID();
+			if (idd > 3000 && idd < 3050)
+				writer.putInt(4);
+			else
+				writer.putInt(5);
+			//			writer.putInt(this.runeBase.getMessageType());
+			writer.putInt(0);
+			writer.putInt(characterRune.runeBase.getObjectUUID());
+			writer.putInt(characterRune.getObjectType().ordinal());
+			writer.putInt(characterRune.getObjectUUID());
+		} else {
+			for (int i = 0; i < 5; i++)
+				writer.putInt(0);
+		}
+	}
+
+	public static boolean grantRune(PlayerCharacter pc, int runeID) {
+		//Verify not too many runes
+		ArrayList<CharacterRune> runes = pc.getRunes();
+		boolean worked = false;
+		synchronized (runes) {
+			if (runes == null || runes.size() > 12)
+				return false;
+
+			//Verify player doesn't already have rune
+			for (CharacterRune rune : runes) {
+                RuneBase rb = rune.runeBase;
+				if (rb == null || rb.getObjectUUID() == runeID)
+					return false;
+			}
+
+			RuneBase rb = RuneBase.getRuneBase(runeID);
+			if (rb == null)
+				return false;
+
+			//Attempt to add rune to database
+			CharacterRune toAdd = new CharacterRune(rb, pc.getObjectUUID());
+			CharacterRune rune = null;
+			try {
+				rune = DbManager.CharacterRuneQueries.ADD_CHARACTER_RUNE(toAdd);
+			} catch (Exception e) {
+				return false;
+			}
+			if (rune == null)
+				return false;
+
+			//attempt add rune to player
+			worked = pc.addRune(rune);
+
+			//worked, send ApplyRuneMsg
+			if (worked) {
+				ApplyRuneMsg arm = new ApplyRuneMsg(pc.getObjectType().ordinal(), pc.getObjectUUID(), runeID, rune.getObjectType().ordinal(), rune.getObjectUUID(), true);
+				DispatchMessage.dispatchMsgToInterestArea(pc, arm, DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, true, false);
+				CharacterSkill.calculateSkills(pc);
+				pc.applyBonuses();
+				return true;
+			} else
+				return false;
+		}
+
+	}
+
+	public static boolean removeRune(PlayerCharacter pc, int runeID) {
+		ArrayList<CharacterRune> runes = pc.getRunes();
+		synchronized (runes) {
+			for (CharacterRune rune : runes) {
+                RuneBase rb = rune.runeBase;
+				if (rb == null)
+					continue;
+				if (rb.getObjectUUID() == runeID && DbManager.CharacterRuneQueries.DELETE_CHARACTER_RUNE(rune)) {
+					runes.remove(runes.indexOf(rune));
+					CharacterSkill.calculateSkills(pc);
+					pc.applyBonuses();
+					return true;
+				}
+			}
+			return false;
+		}
+	}
+
+	@Override
+	public void updateDatabase() {
+		DbManager.CharacterRuneQueries.updateDatabase(this);
+	}
+}
diff --git a/src/engine/objects/CharacterSkill.java b/src/engine/objects/CharacterSkill.java
new file mode 100644
index 00000000..e2179392
--- /dev/null
+++ b/src/engine/objects/CharacterSkill.java
@@ -0,0 +1,1248 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+
+package engine.objects;
+
+import engine.Enum;
+import engine.Enum.CharacterSkills;
+import engine.Enum.ModType;
+import engine.Enum.SourceType;
+import engine.gameManager.DbManager;
+import engine.net.ByteBufferWriter;
+import engine.server.MBServerStatics;
+import org.pmw.tinylog.Logger;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicInteger;
+
+public class CharacterSkill extends AbstractGameObject {
+
+	private static final int[] maxTrains = {
+			29, 29, 29, 29, 29, //0 to 4
+			29, 32, 34, 36, 38, //5 to 9
+			40, 42, 43, 45, 47, //10 to 14
+			48, 49, 51, 52, 53, //15 to 19
+			55, 56, 57, 58, 59, //20 to 24
+			60, 62, 63, 64, 65, //25 to 29
+			66, 67, 68, 68, 69, //30 to 34
+			70, 71, 72, 73, 74, //35 to 39
+			75, 76, 76, 77, 78, //40 to 44
+			79, 80, 80, 81, 82, //45 to 49
+			83, 83, 84, 85, 85, //50 to 54
+			86, 87, 88, 88, 89, //55 to 59
+			90, 90, 91, 92, 92, //60 to 64
+			93, 94, 94, 95, 95, //65 to 69
+			96, 97, 97, 98, 99, //70 to 74
+			99, 100, 100, 101, 101, //75 to 79
+			102, 103, 103, 104, 104, //80 to 84
+			105, 105, 106, 106, 107, //85 to 89
+			108, 109, 109, 110, 110, //90 to 94
+			111, 112, 112, 113, 113, //95 to 99
+			114, 115, 115, 116, 116, //100 to 104
+			117, 118, 118, 119, 119, //105 to 109
+			120, 121, 121, 122, 122, //110 to 114
+			123, 124, 124, 125, 125, //115 to 119
+			126, 127, 127, 128, 128, //120 to 124
+			129, 130, 130, 131, 131, //125 to 129
+			132, 133, 133, 134, 134, //130 to 134
+			135, 136, 136, 137, 137, //135 to 139
+			138, 139, 139, 140, 140, //140 to 144
+			141, 142, 142, 143, 143, //145 to 149
+			144, 145, 145, 146, 146, //150 to 154
+			147, 148, 148, 149, 149, //155 to 159
+			150, 151, 151, 152, 152, //160 to 164
+			153, 154, 154, 155, 155, //165 to 169
+			156, 157, 157, 158, 158, //170 to 174
+			159, 160, 160, 161, 161, //175 to 179
+			162, 163, 163, 164, 164, //180 to 184
+			165, 166, 166, 167, 167, //185 to 189
+			168}; //190
+
+	private static final float[] baseSkillValues = {
+			0.0f, 0.0f, 0.2f, 0.4f, 0.6f,  //0 to 4
+			0.8f, 1.0f, 1.1666666f, 1.3333334f, 1.5f,  //5 to 9
+			1.6666667f, 1.8333334f, 2.0f, 2.2f, 2.4f,  //10 to 14
+			2.6f, 2.8f, 3.0f, 3.2f, 3.4f,  //15 to 19
+			3.6f, 3.8f, 4.0f, 4.2f, 4.4f,  //20 to 24
+			4.6f, 4.8f, 5.0f, 5.25f, 5.5f,  //25 to 29
+			5.75f, 6.0f, 6.2f, 6.4f, 6.6f,  //30 to 34
+			6.8f, 7.0f, 7.25f, 7.5f, 7.75f,  //35 to 39
+			8.0f, 8.2f, 8.4f, 8.6f, 8.8f,  //40 to 44
+			9.0f, 9.25f, 9.5f, 9.75f, 10.0f,  //45 to 49
+			10.25f, 10.5f, 10.75f, 11.0f, 11.2f,  //50 to 54
+			11.4f, 11.6f, 11.8f, 12.0f, 12.25f,  //55 to 59
+			12.5f, 12.75f, 13.0f, 13.25f, 13.5f,  //60 to 64
+			13.75f, 14.0f, 14.25f, 14.5f, 14.75f,  //65 to 69
+			15.0f, 15.333333f, 15.666667f, 16.0f, 16.25f,  //70 to 74
+			16.5f, 16.75f, 17.0f, 17.25f, 17.5f,  //75 to 79
+			17.75f, 18.0f, 18.25f, 18.5f, 18.75f,  //80 to 84
+			19.0f, 19.333334f, 19.666666f, 20.0f, 20.25f,  //85 to 89
+			20.5f, 20.75f, 21.0f, 21.25f, 21.5f,  //90 to 94
+			21.75f, 22.0f, 22.333334f, 22.666666f, 23.0f,  //95 to 99
+			23.25f, 23.5f, 23.75f, 24.0f, 24.333334f,  //100 to 104
+			24.666666f, 25.0f, 25.25f, 25.5f, 25.75f,  //105 to 109
+			26.0f, 26.333334f, 26.666666f, 27.0f, 27.333334f,  //110 to 114
+			27.666666f, 28.0f, 28.25f, 28.5f, 28.75f,  //115 to 119
+			29.0f, 29.333334f, 29.666666f, 30.0f, 30.333334f,  //120 to 124
+			30.666666f, 31.0f, 31.25f, 31.5f, 31.75f,  //125 to 129
+			32.0f, 32.333332f, 32.666668f, 33.0f, 33.333332f,  //130 to 134
+			33.666668f, 34.0f, 34.333332f, 34.666668f, 35.0f,  //135 to 139
+			35.333332f, 35.666668f, 36.0f, 36.333332f, 36.666668f,  //140 to 144
+			37.0f, 37.25f, 37.5f, 37.75f, 38.0f,  //145 to 149
+			38.333332f, 38.666668f, 39.0f, 39.333332f, 39.666668f,  //150 to 154
+			40.0f, 40.333332f, 40.666668f, 41.0f, 41.333332f,  //155 to 159
+			41.666668f, 42.0f, 42.333332f, 42.666668f, 43.0f,  //160 to 164
+			43.333332f, 43.666668f, 44.0f, 44.333332f, 44.666668f,  //165 to 169
+			45.0f, 45.5f, 46.0f, 46.333332f, 46.666668f,  //170 to 174
+			47.0f, 47.333332f, 47.666668f, 48.0f, 48.333332f,  //175 to 179
+			48.666668f, 49.0f, 49.333332f, 49.666668f, 50.0f,  //180 to 184
+			50.333332f, 50.666668f, 51.0f, 51.333332f, 51.666668f,  //185 to 189
+			52.0f, 52.5f, 53.0f, 53.333332f, 53.666668f,  //190 to 194
+			54.0f, 54.333332f, 54.666668f, 55.0f, 55.333332f,  //195 to 199
+			55.666668f, 56.0f, 56.333332f, 56.666668f, 57.0f,  //200 to 204
+			57.5f, 58.0f, 58.333332f, 58.666668f, 59.0f,  //205 to 209
+			59.333332f, 59.666668f, 60.0f, 60.5f, 61.0f,  //210 to 214
+			61.333332f, 61.666668f, 62.0f, 62.333332f, 62.666668f,  //215 to 219
+			63.0f, 63.5f, 64.0f, 64.333336f, 64.666664f,  //220 to 224
+			65.0f, 65.333336f, 65.666664f, 66.0f, 66.5f,  //225 to 229
+			67.0f, 67.333336f, 67.666664f, 68.0f, 68.5f,  //230 to 234
+			69.0f, 69.333336f, 69.666664f, 70.0f, 70.333336f,  //235 to 239
+			70.666664f, 71.0f, 71.5f, 72.0f, 72.5f,  //240 to 244
+			73.0f, 73.333336f, 73.666664f, 74.0f, 74.333336f,  //245 to 249
+			74.666664f, 75.0f, 75.5f, 76.0f, 76.333336f,  //250 to 254
+			76.666664f, 77.0f, 77.5f, 78.0f, 78.333336f,  //255 to 259
+			78.666664f, 79.0f, 79.5f, 80.0f, 80.333336f,  //260 to 264
+			80.666664f, 81.0f, 81.5f, 82.0f, 82.333336f,  //265 to 269
+			82.666664f, 83.0f, 83.5f, 84.0f, 84.333336f,  //270 to 274
+			84.666664f, 85.0f, 85.5f, 86.0f, 86.5f,  //275 to 279
+			87.0f, 87.333336f, 87.666664f, 88.0f, 88.5f,  //280 to 284
+			89.0f, 89.333336f, 89.666664f, 90.0f, 90.5f,  //285 to 289
+			91.0f, 91.5f, 92.0f, 92.333336f, 92.666664f,  //290 to 294
+			93.0f, 93.5f, 94.0f, 94.5f, 95.0f,  //295 to 299
+			95.333336f, 95.666664f, 96.0f, 96.5f, 97.0f,  //300 to 304
+			97.5f, 98.0f, 98.333336f, 98.666664f, 99.0f,  //305 to 309
+			99.5f, 100.0f, 100.5f, 101.0f, 101.5f,  //310 to 314
+			102.0f, 102.5f, 103.0f, 103.333336f, 103.666664f,  //315 to 319
+			104.0f, 104.333336f, 104.666664f, 105.0f, 105.5f,  //320 to 324
+			106.0f, 106.5f, 107.0f, 108.0f, 108.333336f,  //325 to 329
+			108.666664f, 109.0f, 109.333336f, 109.666664f, 110.0f,  //330 to 334
+			110.5f, 111.0f, 111.5f, 112.0f, 112.5f,  //335 to 339
+			113.0f, 113.333336f, 113.666664f, 114.0f, 114.5f,  //340 to 344
+			115.0f, 115.5f, 116.0f, 116.5f, 117.0f,  //345 to 349
+			117.5f, 118.0f, 118.333336f, 118.666664f, 119.0f,  //350 to 354
+			119.5f, 120.0f, 120.5f, 121.0f, 121.5f,  //355 to 359
+			122.0f, 122.5f, 123.0f, 123.333336f, 123.666664f,  //360 to 364
+			124.0f, 124.5f, 125.0f, 125.5f, 126.0f,  //365 to 369
+			126.5f, 127.0f, 127.5f, 128.0f, 128.5f,  //370 to 374
+			129.0f, 129.5f, 130.0f, 130.33333f, 130.66667f,  //375 to 379
+			131.0f, 131.5f, 132.0f, 132.5f, 133.0f,  //380 to 384
+			133.5f, 134.0f, 134.5f, 135.0f, 135.5f,  //385 to 389
+			136.0f, 136.5f, 137.0f, 137.5f, 138.0f,  //390 to 394
+			138.5f, 139.0f, 139.5f, 140.0f, 140.33333f,  //395 to 399
+			140.66667f, 141.0f, 141.5f, 142.0f, 142.5f,  //400 to 404
+			143.0f, 143.5f, 144.0f, 144.5f, 145.0f,  //405 to 409
+			145.5f, 146.0f, 146.5f, 147.0f, 147.5f,  //410 to 414
+			148.0f, 148.5f, 149.0f, 149.5f, 150.0f,  //415 to 419
+			150.5f, 151.0f, 151.5f, 152.0f, 152.5f,  //420 to 424
+			153.0f, 153.5f, 154.0f, 154.5f, 155.0f,  //425 to 429
+			155.5f, 156.0f, 156.5f, 157.0f, 157.5f,  //430 to 434
+			158.0f, 158.5f, 159.0f, 159.5f, 160.0f,  //435 to 439
+			160.5f, 161.0f, 161.5f, 162.0f, 162.5f,  //440 to 444
+			163.0f, 163.5f, 164.0f, 164.5f, 165.0f,  //445 to 449
+			165.5f, 166.0f, 166.5f, 167.0f, 167.5f,  //450 to 454
+			168.0f, 168.5f, 169.0f, 169.5f, 170.0f,  //455 to 459
+			170.5f, 171.0f, 171.5f, 172.0f, 172.5f,  //460 to 464
+			173.0f, 173.5f, 174.0f, 174.5f, 175.0f,  //465 to 469
+			176.0f, 176.5f, 177.0f, 177.5f, 178.0f,  //470 to 474
+			178.5f, 179.0f, 179.5f, 180.0f, 180.5f,  //475 to 479
+			181.0f, 181.5f, 182.0f, 182.5f, 183.0f,  //480 to 484
+			183.5f, 184.0f, 184.5f, 185.0f, 185.5f,  //485 to 489
+			186.0f, 187.0f, 187.5f, 188.0f, 188.5f,  //490 to 494
+			189.0f, 189.5f, 190.0f, 190.5f, 191.0f,  //495 to 499
+			191.5f, 192.0f, 192.5f, 193.0f, 193.5f,  //500 to 504
+			194.0f, 194.5f, 195.0f, 196.0f, 196.5f,  //505 to 509
+			197.0f, 197.5f, 198.0f, 198.5f, 199.0f,  //510 to 514
+			199.5f, 200.0f, 200.5f, 201.0f, 201.5f,  //515 to 519
+			202.0f, 203.0f, 203.5f, 204.0f, 204.5f,  //520 to 524
+			205.0f, 205.5f, 206.0f, 206.5f, 207.0f,  //525 to 529
+			207.5f, 208.0f, 209.0f, 209.5f, 210.0f,  //530 to 534
+			210.5f, 211.0f, 211.5f, 212.0f, 212.5f,  //535 to 539
+			213.0f, 214.0f, 214.5f, 215.0f, 215.5f,  //540 to 544
+			216.0f, 216.5f, 217.0f, 217.5f, 218.0f,  //545 to 549
+			218.5f, 219.0f, 220.0f, 220.5f, 221.0f,  //550 to 554
+			221.5f, 222.0f, 222.5f, 223.0f, 224.0f,  //555 to 559
+			224.5f, 225.0f, 225.5f, 226.0f, 226.5f,  //560 to 564
+			227.0f, 227.5f, 228.0f, 229.0f, 229.5f,  //565 to 569
+			230.0f, 230.5f, 231.0f, 231.5f, 232.0f,  //570 to 574
+			233.0f, 233.5f, 234.0f, 234.5f, 235.0f,  //575 to 579
+			235.5f, 236.0f, 237.0f, 237.5f, 238.0f,  //580 to 584
+			238.5f, 239.0f, 239.5f, 240.0f, 241.0f,  //585 to 589
+			241.5f, 242.0f, 242.5f, 243.0f, 243.5f,  //590 to 594
+			244.0f, 245.0f, 245.5f, 246.0f, 246.5f,  //595 to 599
+			247.0f}; //600
+
+
+	private SkillsBase skillsBase;
+	private AtomicInteger numTrains = new AtomicInteger();
+
+	private CharacterSkills skillType;
+
+	//Skill% before trains and before any effects or item bonuses
+	private float baseAmountBeforeMods;
+
+	//Skill% after trains but before any effects or item bonuses
+	private float modifiedAmountBeforeMods;
+	private boolean isMobOwner = false;
+
+	//Skill% before trains but after any effects or item bonuses
+	private float baseAmount;
+
+	//Skill% after trains and after any effects or item bonuses
+	private float modifiedAmount;
+
+	private int ownerUID;
+	private boolean trained = false;
+	private int requiredLevel = 0;
+
+	/**
+	 * No Table ID Constructor
+	 */
+	public CharacterSkill(SkillsBase skillsBase, PlayerCharacter pc) {
+		super();
+		this.skillsBase = skillsBase;
+		this.numTrains.set(0);
+		this.ownerUID = pc.getObjectUUID();
+		calculateBaseAmount();
+		calculateModifiedAmount();
+		this.skillType = CharacterSkills.GetCharacterSkillByToken(this.skillsBase.getToken());
+	}
+
+	/**
+	 * Normal Constructor
+	 */
+	public CharacterSkill(SkillsBase skillsBase, PlayerCharacter pc, int newUUID) {
+
+		super(newUUID);
+		this.skillsBase = skillsBase;
+		this.numTrains.set(0);
+		this.ownerUID = pc.getObjectUUID();
+		this.trained = true;
+		calculateBaseAmount();
+		calculateModifiedAmount();
+		this.skillType = CharacterSkills.GetCharacterSkillByToken(this.skillsBase.getToken());
+
+	}
+
+	/**
+	 * ResultSet Constructor
+	 */
+	public CharacterSkill(ResultSet rs, PlayerCharacter pc) throws SQLException {
+		super(rs);
+
+		int skillsBaseID = rs.getInt("SkillsBaseID");
+		this.skillsBase = DbManager.SkillsBaseQueries.GET_BASE(skillsBaseID);
+		this.numTrains.set(rs.getShort("trains"));
+		this.ownerUID = pc.getObjectUUID();
+		calculateBaseAmount();
+		calculateModifiedAmount();
+		this.skillType = CharacterSkills.GetCharacterSkillByToken(this.skillsBase.getToken());
+	}
+
+	public CharacterSkill(SkillsBase sb, Mob mob, int trains) {
+		super();
+		this.skillsBase = sb;
+		this.numTrains.set(trains);
+		this.ownerUID = mob.getObjectUUID();
+		this.isMobOwner = true;
+		calculateMobBaseAmount();
+		calculateModifiedAmount();
+		this.skillType = CharacterSkills.GetCharacterSkillByToken(this.skillsBase.getToken());
+	}
+
+	public CharacterSkill(ResultSet rs) throws SQLException {
+		super(rs);
+		int skillsBaseID = rs.getInt("SkillsBaseID");
+		this.skillsBase = DbManager.SkillsBaseQueries.GET_BASE(skillsBaseID);
+		this.numTrains.set(rs.getShort("trains"));
+		this.ownerUID = rs.getInt("CharacterID");
+		//		this.owner = DbManager.PlayerCharacterQueries.GET_PLAYER_CHARACTER(rs.getInt("CharacterID"));
+		calculateBaseAmount();
+		calculateModifiedAmount();
+		this.skillType = CharacterSkills.GetCharacterSkillByToken(this.skillsBase.getToken());
+	}
+
+	public static AbstractCharacter GetOwner(CharacterSkill cs){
+		if (cs.ownerUID == 0)
+			return null;
+		if (cs.isMobOwner)
+			return Mob.getFromCache(cs.ownerUID);
+		else
+			return PlayerCharacter.getFromCache(cs.ownerUID);
+	}
+
+	/*
+	 * Getters
+	 */
+
+	public static float getATR(AbstractCharacter ac, String name) {
+		if (ac == null)
+			return 0f;
+		float atr;
+		ConcurrentHashMap<String, CharacterSkill> skills = ac.getSkills();
+		CharacterSkill skill = skills.get(name);
+		if (skill != null)
+			atr = skill.getATR(ac);
+		else {
+			float mast = CharacterSkill.getQuickMastery(ac, name);
+			atr = (((int)mast * 7) + (ac.getStatDexCurrent() / 2));
+		}
+		//apply effect mods
+		PlayerBonuses bonus = ac.getBonuses();
+		if (bonus == null)
+			return atr;
+		atr += bonus.getFloat(ModType.OCV, SourceType.None);
+		float pos_Bonus = bonus.getFloatPercentPositive(ModType.OCV, SourceType.None);
+		atr *=  (1 + pos_Bonus);
+		//rUNES will already be applied
+	//	atr *= (1 + ((float)bonus.getShort("rune.Attack") / 100)); //precise
+		float neg_Bonus = bonus.getFloatPercentNegative(ModType.OCV, SourceType.None);
+		atr *= (1 +neg_Bonus);
+		return atr;
+	}
+
+	private float getATR(AbstractCharacter ac) {
+		return (((int)this.modifiedAmount * 7) + (ac.getStatDexCurrent() / 2));
+	}
+
+	public synchronized boolean train(PlayerCharacter pc) {
+		if (pc == null || this.skillsBase == null)
+			return false;
+
+		boolean running = false;
+
+		//trying out a table lookup
+		int intt = (int) pc.statIntBase;
+		int maxTrains = 0;
+		if (intt > 0 && intt < 191)
+			maxTrains = CharacterSkill.maxTrains[intt];
+		else
+			maxTrains = (int)(33 + 1.25 * (int) pc.statIntBase - 0.005 * Math.pow((int) pc.statIntBase, 2));
+
+
+		int oldTrains = this.numTrains.get();
+		boolean succeeded = true;
+		if (pc.getTrainsAvailable() <= 0)
+			return false;
+		if (oldTrains == maxTrains) //at gold, stop here
+			return false;
+		else if (oldTrains > maxTrains) //catch incase we somehow go over
+			this.numTrains.set(maxTrains);
+		else //add the train
+			succeeded = this.numTrains.compareAndSet(oldTrains, oldTrains+1);
+
+		if (this.numTrains.get() > maxTrains) { //double check not over max trains
+			this.numTrains.set(maxTrains);
+			succeeded = false;
+		}
+
+		if (succeeded) {
+			this.trained = true;
+
+			//subtract from trains available
+			pc.modifyTrainsAvailable(-1);
+
+			//update database
+			pc.addDatabaseJob("Skills", MBServerStatics.THIRTY_SECONDS);
+
+			//recalculate this skill
+			calculateBaseAmount();
+			calculateModifiedAmount();
+
+			//see if any new skills or powers granted
+			pc.calculateSkills();
+
+			//reapply all bonuses
+			pc.applyBonuses();
+
+			//update cache if running trains change
+			if (running)
+				pc.incVer();
+
+			return true;
+		} else
+			return false;
+	}
+
+	public float  getTrainingCost(PlayerCharacter pc, NPC trainer){
+		int charLevel = pc.getLevel();
+		int skillRank = this.getNumTrains() - 1 + this.requiredLevel;
+		float baseCost = 15 * this.requiredLevel; //TODO GET BASE COSTS OF SKILLS.
+
+
+
+		float sellPercent = -.20f;
+		float cost;
+		float const5;
+		int const2 = 1;
+		float const3 = 50;
+		float const4 = const3 + const2;
+
+		if (charLevel > 50)
+			const5 = 50 / const4;
+		else
+			const5 = charLevel/const4;
+
+		const5 = 1-const5;
+		const5 = (float) (Math.log(const5) / Math.log(2) * .75f);
+		float rounded5 = Math.round(const5);
+		const5 = rounded5 - const5;
+
+		const5 *= -1;
+
+		const5 = (float) (Math.pow(2, const5) - 1);
+
+		const5 +=1;
+		const5 = Math.scalb(const5, (int) rounded5);
+		const5 *= (charLevel - skillRank);
+		const5 *= sellPercent;
+		const5 = (float) (Math.log(const5) / Math.log(2) * 3);
+		rounded5 = Math.round(const5);
+		const5 = rounded5 - const5;
+		const5 *= -1;
+		const5 = (float) (Math.pow(2, const5) - 1);
+		const5 +=1;
+
+
+		const5 = Math.scalb(const5, (int) rounded5);
+		const5 += 1;
+		cost = const5 * (baseCost);
+
+
+		if (Float.isNaN(cost))
+			cost = baseCost;
+		return cost;
+	}
+
+	//Call this to refine skills and recalculate everything for pc.
+	public boolean refine(PlayerCharacter pc) {
+		return refine(pc, true);
+	}
+
+	public boolean refine(PlayerCharacter pc, boolean recalculate) {
+		if (pc == null || this.skillsBase == null)
+			return false;
+
+		int oldTrains = this.numTrains.get();
+		boolean succeeded = true;
+		if (this.getNumTrains() < 1)
+			return false;
+		succeeded = this.numTrains.compareAndSet(oldTrains, oldTrains-1);
+		if (succeeded) {
+			this.trained = true;
+
+			//add to trains available
+			pc.modifyTrainsAvailable(1);
+
+			//update database
+			pc.addDatabaseJob("Skills", MBServerStatics.THIRTY_SECONDS);
+
+			if (recalculate) {
+				//recalculate this skill
+				calculateBaseAmount();
+				calculateModifiedAmount();
+
+				//see if any skills or powers removed
+				pc.calculateSkills();
+
+				//reapply all bonuses
+				pc.applyBonuses();
+			}
+
+			return true;
+		} else
+			return false;
+	}
+	
+	
+	public boolean reset(PlayerCharacter pc, boolean recalculate) {
+		if (pc == null || this.skillsBase == null)
+			return false;
+
+		int oldTrains = this.numTrains.get();
+		boolean succeeded = true;
+		if (this.getNumTrains() < 1)
+			return false;
+		succeeded = this.numTrains.compareAndSet(oldTrains, 0);
+		if (succeeded) {
+			this.trained = true;
+
+			//add to trains available
+			pc.modifyTrainsAvailable(oldTrains);
+
+			//update database
+			pc.addDatabaseJob("Skills", MBServerStatics.THIRTY_SECONDS);
+
+			if (recalculate) {
+				//recalculate this skill
+				calculateBaseAmount();
+				calculateModifiedAmount();
+
+				//see if any skills or powers removed
+				pc.calculateSkills();
+
+				//reapply all bonuses
+				pc.applyBonuses();
+			}
+
+			return true;
+		} else
+			return false;
+	}
+
+
+	/*
+	 * Returns Skill Base for skill
+	 */
+	public SkillsBase getSkillsBase() {
+		return this.skillsBase;
+	}
+
+	/*
+	 * Returns number of trains in skill
+	 */
+	public int getNumTrains() {
+		return this.numTrains.get();
+	}
+
+
+
+	/*
+	 * Returns Skill% before trains added
+	 */
+	public float getBaseAmount() {
+		return this.baseAmount;
+	}
+
+	/*
+	 * Returns Skill% after trains added
+	 */
+	public float getModifiedAmount() {
+		return this.modifiedAmount;
+	}
+
+	/*
+	 * Returns Skill% before trains added, minus bonus from equip and effects
+	 */
+	public float getBaseAmountBeforeMods() {
+		return this.baseAmountBeforeMods;
+	}
+
+	/*
+	 * Returns Skill% after trains added, minus bonus from equip and effects
+	 */
+	public float getModifiedAmountBeforeMods() {
+		return this.modifiedAmountBeforeMods;
+	}
+
+	public String getName() {
+		if (this.skillsBase != null)
+			return this.skillsBase.getName();
+		return "";
+	}
+
+	public int getToken() {
+		if (this.skillsBase != null)
+			return this.skillsBase.getToken();
+		return 0;
+	}
+
+	public boolean isTrained() {
+		return trained;
+	}
+
+	public void syncTrains() {
+		this.trained = false;
+	}
+
+	/*
+	 * Serializing
+	 */
+	
+	public static void serializeForClientMsg(CharacterSkill characterSkill, ByteBufferWriter writer) {
+		if (characterSkill.skillsBase == null) {
+			Logger.error( "SkillsBase not found for skill " + characterSkill.getObjectUUID());
+			writer.putInt(0);
+			writer.putInt(0);
+		} else {
+			writer.putInt(characterSkill.skillsBase.getToken());
+			writer.putInt(characterSkill.numTrains.get());
+		}
+	}
+
+	/**
+	 * @ This updates all Base Skill Amouts for a player
+	 * Convienence method
+	 */
+	public static void updateAllBaseAmounts(PlayerCharacter pc) {
+		if (pc == null)
+			return;
+		ConcurrentHashMap<String, CharacterSkill> skills = pc.getSkills();
+		Iterator<String> it = skills.keySet().iterator();
+		while(it.hasNext()) {
+			String name = it.next();
+			CharacterSkill cs = skills.get(name);
+			if (cs != null)
+				cs.calculateBaseAmount();
+			// Logger.info("CharacterSkill", pc.getName() + ", skill: " +
+			// cs.getSkillsBase().getName() + ", trains: " + cs.numTrains +
+			// ", base: " + cs.baseAmount + ", mod: " + cs.modifiedAmount);
+		}
+
+		//Recalculate ATR, damage and defense
+		//pc.calculateAtrDefenseDamage();
+
+		//recalculate movement bonus
+		//pc.calculateSpeedMod();
+	}
+
+	/**
+	 * @ This updates all Modified skill Amounts for a player
+	 * Convienence method
+	 */
+	public static void updateAllModifiedAmounts(PlayerCharacter pc) {
+		if (pc == null)
+			return;
+		ConcurrentHashMap<String, CharacterSkill> skills = pc.getSkills();
+		Iterator<String> it = skills.keySet().iterator();
+		while(it.hasNext()) {
+			String name = it.next();
+			CharacterSkill cs = skills.get(name);
+			if (cs != null)
+				cs.calculateModifiedAmount();
+
+		}
+
+		//Recalculate ATR, damage and defense
+		//pc.calculateAtrDefenseDamage();
+	}
+
+	/**
+	 * @ Calculates Base Skill Percentage
+	 * Call this when stats change for a player
+	 */
+	public void calculateBaseAmount() {
+		if (CharacterSkill.GetOwner(this) == null) {
+			Logger.error("owner not found for owner uuid : " + this.ownerUID);
+			this.baseAmount = 1;
+			this.modifiedAmount = 1;
+			return;
+		}
+		
+		if (this.skillsBase == null) {
+			Logger.error("SkillsBase not found for skill " + this.getObjectUUID());
+			this.baseAmount = 1;
+			this.modifiedAmount = 1;
+			return;
+		}
+
+		//Get any rune bonus
+		float bonus = 0f;
+		//runes will already be calculated
+		if (CharacterSkill.GetOwner(this).getBonuses() != null) {
+			//Get bonuses from runes
+			bonus = CharacterSkill.GetOwner(this).getBonuses().getSkillBonus(this.skillsBase.sourceType);
+		}
+
+		//Get Base skill for unmodified stats
+		float base = 7f;
+		float statMod = 0.5f;
+		if (this.skillsBase.getStrMod() > 0)
+            statMod += (float)this.skillsBase.getStrMod() * (float) (int) ((PlayerCharacter) CharacterSkill.GetOwner(this)).statStrBase / 100f;
+		if (this.skillsBase.getDexMod() > 0)
+			statMod += (float)this.skillsBase.getDexMod() * (float) (int) ((PlayerCharacter) CharacterSkill.GetOwner(this)).statDexBase / 100f;
+		if (this.skillsBase.getConMod() > 0)
+			statMod += (float)this.skillsBase.getConMod() * (float) (int) ((PlayerCharacter) CharacterSkill.GetOwner(this)).statConBase / 100f;
+		if (this.skillsBase.getIntMod() > 0)
+			statMod += (float)this.skillsBase.getIntMod() * (float) (int) ((PlayerCharacter) CharacterSkill.GetOwner(this)).statIntBase / 100f;
+		if (this.skillsBase.getSpiMod() > 0)
+			statMod += (float)this.skillsBase.getSpiMod() * (float) (int) ((PlayerCharacter) CharacterSkill.GetOwner(this)).statSpiBase / 100f;
+		if (statMod < 1)
+			statMod = 1f;
+		else if (statMod > 600)
+			statMod = 600f;
+		base += CharacterSkill.baseSkillValues[(int)statMod];
+
+		if (base + bonus < 1f)
+			this.baseAmountBeforeMods = 1f;
+		else
+			this.baseAmountBeforeMods = base + bonus;
+		this.modifiedAmountBeforeMods = Math.round(this.baseAmountBeforeMods + calculateAmountAfterTrains());
+	}
+
+	public void calculateMobBaseAmount() {
+		if (CharacterSkill.GetOwner(this) == null) {
+			Logger.error("owner not found for owner uuid : " + this.ownerUID);
+			this.baseAmount = 1;
+			this.modifiedAmount = 1;
+			return;
+		}
+		
+		if (this.skillsBase == null) {
+			Logger.error("SkillsBase not found for skill " + this.getObjectUUID());
+			this.baseAmount = 1;
+			this.modifiedAmount = 1;
+			return;
+		}
+
+		//Get any rune bonus
+		float bonus = 0f;
+		//TODO SKILLS RUNES
+		
+		if (CharacterSkill.GetOwner(this).getBonuses() != null) {
+			//Get bonuses from runes
+			bonus = CharacterSkill.GetOwner(this).getBonuses().getSkillBonus(this.skillsBase.sourceType);
+		}
+
+		//Get Base skill for unmodified stats
+		float base = 7f;
+		float statMod = 0.5f;
+		if (this.skillsBase.getStrMod() > 0)
+			statMod += (float)this.skillsBase.getStrMod() * (float)((Mob)CharacterSkill.GetOwner(this)).getMobBase().getMobBaseStats().getBaseStr() / 100f;
+		if (this.skillsBase.getDexMod() > 0)
+			statMod += (float)this.skillsBase.getDexMod() * (float)((Mob)CharacterSkill.GetOwner(this)).getMobBase().getMobBaseStats().getBaseDex() / 100f;
+		if (this.skillsBase.getConMod() > 0)
+			statMod += (float)this.skillsBase.getConMod() * (float)((Mob)CharacterSkill.GetOwner(this)).getMobBase().getMobBaseStats().getBaseCon() / 100f;
+		if (this.skillsBase.getIntMod() > 0)
+			statMod += (float)this.skillsBase.getIntMod() * (float)((Mob)CharacterSkill.GetOwner(this)).getMobBase().getMobBaseStats().getBaseInt() / 100f;
+		if (this.skillsBase.getSpiMod() > 0)
+			statMod += (float)this.skillsBase.getSpiMod() * (float)((Mob)CharacterSkill.GetOwner(this)).getMobBase().getMobBaseStats().getBaseSpi() / 100f;
+		if (statMod < 1)
+			statMod = 1f;
+		else if (statMod > 600)
+			statMod = 600f;
+		base += CharacterSkill.baseSkillValues[(int)statMod];
+
+		if (base + bonus < 1f)
+			this.baseAmountBeforeMods = 1f;
+		else
+			this.baseAmountBeforeMods = base + bonus;
+		this.modifiedAmountBeforeMods = (int)(this.baseAmountBeforeMods + calculateAmountAfterTrains());
+	}
+
+	public void calculateModifiedAmount() {
+		if (CharacterSkill.GetOwner(this) == null || this.skillsBase == null) {
+			Logger.error( "owner or SkillsBase not found for skill " + this.getObjectUUID());
+			this.baseAmount = 1;
+			this.modifiedAmount = 1;
+			return;
+		}
+
+		//Get any rune bonus
+		float bonus = 0f;
+		if (CharacterSkill.GetOwner(this).getBonuses() != null) {
+			//Get bonuses from runes
+			bonus = CharacterSkill.GetOwner(this).getBonuses().getSkillBonus(this.skillsBase.sourceType);
+		}
+
+		//Get Base skill for modified stats
+		//TODO this fomula needs verified
+		float base = 7f;
+		float statMod = 0.5f;
+		if (this.skillsBase.getStrMod() > 0)
+			statMod += (float)this.skillsBase.getStrMod() * (float)CharacterSkill.GetOwner(this).getStatStrCurrent() / 100f;
+		if (this.skillsBase.getDexMod() > 0)
+			statMod += (float)this.skillsBase.getDexMod() * (float)CharacterSkill.GetOwner(this).getStatDexCurrent() / 100f;
+		if (this.skillsBase.getConMod() > 0)
+			statMod += (float)this.skillsBase.getConMod() * (float)CharacterSkill.GetOwner(this).getStatConCurrent() / 100f;
+		if (this.skillsBase.getIntMod() > 0)
+			statMod += (float)this.skillsBase.getIntMod() * (float)CharacterSkill.GetOwner(this).getStatIntCurrent() / 100f;
+		if (this.skillsBase.getSpiMod() > 0)
+			statMod += (float)this.skillsBase.getSpiMod() * (float)CharacterSkill.GetOwner(this).getStatSpiCurrent() / 100f;
+		if (statMod < 1)
+			statMod = 1f;
+		else if (statMod > 600)
+			statMod = 600f;
+		base += CharacterSkill.baseSkillValues[(int)statMod];
+		SourceType sourceType = SourceType.GetSourceType(this.skillsBase.getNameNoSpace());
+
+		//Get any rune, effect and item bonus
+		
+		if (CharacterSkill.GetOwner(this).getBonuses() != null) {
+			//add bonuses from effects/items and runes
+			base += bonus + CharacterSkill.GetOwner(this).getBonuses().getFloat(ModType.Skill, sourceType);
+		}
+
+		if (base < 1f)
+			this.baseAmount = 1f;
+		else
+			this.baseAmount = base;
+
+		float modAmount = this.baseAmount + calculateAmountAfterTrains();
+
+		if (CharacterSkill.GetOwner(this).getBonuses() != null) {
+			//Multiply any percent bonuses
+			modAmount *= (1 + CharacterSkill.GetOwner(this).getBonuses().getFloatPercentAll(ModType.Skill, sourceType));
+		}
+
+		this.modifiedAmount = (int)(modAmount);
+	}
+
+	/**
+	 * @ Calculates Modified Skill Percentage
+	 * Call this when number of trains change for skill
+	 */
+	public float calculateAmountAfterTrains() {
+		if (this.skillsBase == null) {
+			Logger.error( "SkillsBase not found for skill " + this.getObjectUUID());
+			this.modifiedAmount = this.baseAmount;
+		}
+		int amount;
+
+		int trains = this.numTrains.get();
+		if (trains < 10)
+			amount = (trains * 2);
+		else if (trains < 90)
+			amount = 10 + trains;
+		else if (trains < 134)
+			amount = 100 + ((trains-90) / 2);
+		else
+			amount = 122 + ((trains-134) / 3);
+
+		return amount;
+	}
+
+	public static int getTrainsAvailable(PlayerCharacter pc) {
+
+		if (pc == null)
+			return 0;
+		if (pc.getRace() == null || pc.getBaseClass() == null) {
+			Logger.error("Race or BaseClass not found for player " + pc.getObjectUUID());
+			return 0;
+		}
+		int raceBonus = 0;
+		int baseMod = 0;
+		int promoMod = 6;
+		int available = 0;
+
+		//get racial bonus;
+		if (pc.getRace().getRaceType().equals(Enum.RaceType.HUMANMALE) ||
+				pc.getRace().getRaceType().equals(Enum.RaceType.HUMANFEMALE) )
+			raceBonus = 1; //Human racial bonus;
+
+		//get base class trains
+		if (pc.getBaseClass().getObjectUUID() == 2500 || pc.getBaseClass().getObjectUUID() == 2502) {
+			baseMod = 4; //Fighter or Rogue
+		} else {
+			baseMod = 5; //Healer or Mage
+		}
+
+		int level = pc.getLevel();
+		if (level > 74)
+			available = 62 + (49 * (promoMod + baseMod + raceBonus)) + (9 * (baseMod + raceBonus));
+		else if (level > 69)
+			available = ((level - 69) * 3) + 45 + (49 * (promoMod + baseMod + raceBonus)) + (9 * (baseMod + raceBonus));
+		else if (level > 64)
+			available = ((level - 64) * 4) + 25 + (49 * (promoMod + baseMod + raceBonus)) + (9 * (baseMod + raceBonus));
+		else if (level > 59) //Between 60 and 65
+			available = ((level - 59) * 5) + (49 * (promoMod + baseMod + raceBonus)) + (9 * (baseMod + raceBonus));
+		else if (level > 10) //Between 11 and 59
+			available = ((level - 10) * (promoMod + baseMod + raceBonus)) + (9 * (baseMod + raceBonus));
+		//			available = (level - 59) + (49 * (promoMod + baseMod + raceBonus)) + (9 * (baseMod + raceBonus));
+		else if (level == 10 && (pc.getPromotionClass() != null)) //10 but promoted
+			available = (promoMod + baseMod + raceBonus) + (9 * (baseMod + raceBonus));
+		else //not promoted
+			available = (level-1) * (baseMod + raceBonus);
+
+		//next subtract trains in any skills
+		ConcurrentHashMap<String, CharacterSkill> skills = pc.getSkills();
+		Iterator<String> it = skills.keySet().iterator();
+		while(it.hasNext()) {
+			String name = it.next();
+			CharacterSkill cs = skills.get(name);
+			if (cs != null)
+				available -= cs.numTrains.get();
+		}
+
+		//TODO subtract any trains from powers
+		ConcurrentHashMap<Integer, CharacterPower> powers = pc.getPowers();
+		for (CharacterPower power : powers.values()) {
+			if (power != null)
+				available -= power.getTrains();
+		}
+
+		if(MBServerStatics.BONUS_TRAINS_ENABLED) {
+			available += 1000;
+		}
+
+		//		if (available < 0) {
+		//TODO readd this error log after test
+		//			Logger.error("CharacterSkill.getTrainsAvailable", "Number of trains available less then 0 for Player " + pc.getUUID());
+		//			available = 0;
+		//		}
+
+		//return what's left
+		return available;
+	}
+
+	/**
+	 * @ Returns mastery base when mastery not granted
+	 * to player. For calculating damage correctly
+	 */
+	public static float getQuickMastery(AbstractCharacter pc, String mastery) {
+		SkillsBase sb = SkillsBase.getFromCache(mastery);
+		if (sb == null) {
+			sb = DbManager.SkillsBaseQueries.GET_BASE_BY_NAME(mastery);
+			if (sb == null) {
+				//Logger.error("CharacterSkill.getQuickMastery", "Unable to find skillsbase of name " + mastery);
+				return 0f;
+			}
+		}
+
+		float bonus = 0f;
+		SourceType sourceType = SourceType.GetSourceType(sb.getNameNoSpace());
+		if (pc.getBonuses() != null) {
+			//Get bonuses from runes
+			bonus = pc.getBonuses().getSkillBonus(sb.sourceType);
+		}
+		float base = 4.75f;
+		base += (0.0025f * sb.getStrMod() * pc.getStatStrCurrent());
+		base += (0.0025f * sb.getDexMod() * pc.getStatDexCurrent());
+		base += (0.0025f * sb.getConMod() * pc.getStatConCurrent());
+		base += (0.0025f * sb.getIntMod() * pc.getStatIntCurrent());
+		base += (0.0025f * sb.getSpiMod() * pc.getStatSpiCurrent());
+		return base + bonus;
+	}
+
+	/*
+	 * This iterates through players runes and adds and removes skills as needed
+	 * Don't Call this directly. Instead call pc.calculateSkills().
+	 */
+	public static void calculateSkills(PlayerCharacter pc) {
+		if (pc == null)
+			return;
+
+		ConcurrentHashMap<String, CharacterSkill> skills = pc.getSkills();
+
+		//First add skills that don't exist
+		Race race = pc.getRace();
+		if (race != null) {
+			CharacterSkill.grantSkills(race.getSkillsGranted(), pc);
+		} else
+			Logger.error( "Failed to find Race for player " + pc.getObjectUUID());
+		BaseClass bc = pc.getBaseClass();
+		if (bc != null) {
+			CharacterSkill.grantSkills(bc.getSkillsGranted(), pc);
+		} else
+			Logger.error( "Failed to find BaseClass for player " + pc.getObjectUUID());
+		PromotionClass promo = pc.getPromotionClass();
+		if (promo != null)
+			CharacterSkill.grantSkills(promo.getSkillsGranted(), pc);
+		ArrayList<CharacterRune> runes = pc.getRunes();
+		if (runes != null) {
+			for (CharacterRune rune : runes) {
+				CharacterSkill.grantSkills(rune.getSkillsGranted(), pc);
+			}
+		} else
+			Logger.error("Failed to find Runes list for player " + pc.getObjectUUID());
+
+		//next remove any skills that no longer belong
+		Iterator<CharacterSkill> it = skills.values().iterator();
+		while(it.hasNext()) {
+			CharacterSkill cs = it.next();
+			if (cs == null)
+				continue;
+			SkillsBase sb = cs.skillsBase;
+			if (sb == null) {
+				DbManager.CharacterSkillQueries.DELETE_SKILL(cs.getObjectUUID());
+				it.remove();
+				continue;
+			}
+			boolean valid = false;
+			if (CharacterSkill.skillAllowed(sb.getObjectUUID(), race.getSkillsGranted(), pc))
+				continue;
+			if (CharacterSkill.skillAllowed(sb.getObjectUUID(), bc.getSkillsGranted(), pc))
+				continue;
+			if (promo != null)
+				if (CharacterSkill.skillAllowed(sb.getObjectUUID(), promo.getSkillsGranted(), pc))
+					continue;
+			for (CharacterRune rune : runes) {
+				if (CharacterSkill.skillAllowed(sb.getObjectUUID(), rune.getSkillsGranted(), pc)) {
+					valid = true;
+					continue;
+				}
+			}
+			//if skill doesn't belong to any runes, then remove it
+			if (!valid) {
+				DbManager.CharacterSkillQueries.DELETE_SKILL(cs.getObjectUUID());
+				it.remove();
+			}
+		}
+		CharacterSkill.updateAllBaseAmounts(pc);
+		CharacterSkill.updateAllModifiedAmounts(pc);
+		CharacterPower.calculatePowers(pc);
+	}
+
+	/*
+	 *This grants skills for specific runes
+	 */
+	private static void grantSkills(ArrayList<SkillReq> skillsGranted, PlayerCharacter pc) {
+		ConcurrentHashMap<String, CharacterSkill> skills = pc.getSkills();
+		if (skills == null)
+			return;
+
+		for (SkillReq skillreq : skillsGranted) {
+			SkillsBase skillsBase = skillreq.getSkillsBase();
+
+			//If player not high enough level for skill, then skip
+			if (pc.getLevel() < skillreq.getLevel())
+				continue;
+			//If player doesn't have prereq skills high enough then skip
+			boolean valid = true;
+			for (byte prereqSkill : skillreq.getSkillReqs()) {
+				SkillsBase sb = null;
+				sb = DbManager.SkillsBaseQueries.GET_BASE(prereqSkill);
+				if (sb != null) {
+					if (skills.containsKey(sb.getName())) {
+						if (validForWarrior(pc, skills.get(sb.getName()), skillreq)) {
+							valid = true;
+							break; //add if any prereq skills met
+						} else if (skills.get(sb.getName()).modifiedAmountBeforeMods >= 80) {
+							valid = true;
+							break; //add if any prereq skills met
+							// allow blade masters to use blade master without training sword above 80..
+						} else if (skillsBase.getObjectUUID() == 9){
+							valid = true;
+							break;
+						}
+
+					}
+				} else {
+					Logger.error("Failed to find SkillsBase of ID " + prereqSkill);
+				}
+				valid = false;
+			}
+			// Throwing does not need axe,dagger, or hammer at 80%
+			if (skillreq.getSkillID() == 43)
+				valid = true;
+			if (!valid)
+				continue;
+
+			//Skill valid for player. Add if don't already have
+			if (skillsBase != null) {
+				if (!skills.containsKey(skillsBase.getName())) {
+					CharacterSkill newSkill = new CharacterSkill(skillsBase, pc);
+					CharacterSkill cs = null;
+					try {
+						cs = DbManager.CharacterSkillQueries.ADD_SKILL(newSkill);
+					} catch (Exception e) {
+						cs = null;
+					}
+					if (cs != null){
+						cs.requiredLevel = (int) skillreq.getLevel();
+						skills.put(skillsBase.getName(), cs);
+					}
+
+					else
+						Logger.error("Failed to add CharacterSkill to player " + pc.getObjectUUID());
+				}
+				else{
+					CharacterSkill cs = skills.get(skillsBase.getName());
+					if (cs != null && cs.requiredLevel == 0) {
+						cs.requiredLevel = (int) skillreq.getLevel();
+					}
+				}
+
+			} else
+				Logger.error( "Failed to find SkillsBase for SkillReq " + skillreq.getObjectUUID());
+		}
+	}
+
+	private static boolean validForWarrior(PlayerCharacter pc, CharacterSkill skill, SkillReq skillreq) {
+		if (pc.getPromotionClass() == null || pc.getPromotionClass().getObjectUUID() != 2518 || skill == null || skillreq == null)
+			return false; //not a warrior
+		int sID = (skill.skillsBase != null) ? skill.skillsBase.getObjectUUID() : 0;
+		switch (skillreq.getSkillID()) {
+		case 3: //Axe mastery
+		case 19: //Great axe mastery
+			return (sID == 4) ? (skill.modifiedAmountBeforeMods >= 50) : false;
+		case 15: //Dagger mastery
+			return (sID == 16) ? (skill.modifiedAmountBeforeMods >= 50) : false;
+		case 20: //Great hammer mastery
+		case 22: //Hammer mastery
+			return (sID == 23) ? (skill.modifiedAmountBeforeMods >= 50) : false;
+		case 28: //Polearm mastery
+			return (sID == 29) ? (skill.modifiedAmountBeforeMods >= 50) : false;
+		case 21: //Great sword mastery
+		case 39: //Sword mastery
+			return (sID == 40) ? (skill.modifiedAmountBeforeMods >= 50) : false;
+		case 34: //Spear mastery
+			return (sID == 35) ? (skill.modifiedAmountBeforeMods >= 50) : false;
+		case 36: //Staff mastery
+			return (sID == 37) ? (skill.modifiedAmountBeforeMods >= 50) : false;
+		case 45: //Unarmed combat mastery
+			return (sID == 46) ? (skill.modifiedAmountBeforeMods >= 50) : false;
+		case 40:
+			return true;
+		case 9:
+			return true;
+		default:
+		}
+		return false;
+	}
+
+	/*
+	 * This verifies if a skill is valid for a players rune
+	 */
+	private static boolean skillAllowed(int UUID, ArrayList<SkillReq> skillsGranted, PlayerCharacter pc) {
+		ConcurrentHashMap<String, CharacterSkill> skills = pc.getSkills();
+		for (SkillReq skillreq : skillsGranted) {
+			SkillsBase sb = skillreq.getSkillsBase();
+			if (sb != null) {
+				if (sb.getObjectUUID() == UUID) {
+					if (skillreq.getLevel() <= pc.getLevel()) {
+						SkillsBase sbp = null;
+						if (skillreq.getSkillReqs().size() == 0)
+							return true;
+						for (byte prereqSkill : skillreq.getSkillReqs()) {
+							sbp = DbManager.SkillsBaseQueries.GET_BASE(prereqSkill);
+							if (sbp != null && skills.containsKey(sbp.getName())) {
+								if (validForWarrior(pc, skills.get(sbp.getName()), skillreq)) {
+									return true;
+								} else if (skills.get(sbp.getName()).modifiedAmountBeforeMods >= 80)
+									return true;
+							}
+						}
+
+						if (skillreq.getSkillID() == 43)
+							return true;
+						if (skillreq.getSkillID() == 9)
+							return true;
+					}
+				}
+			} else
+				Logger.error( "Failed to find SkillsBase for SkillReq " + skillreq.getObjectUUID());
+		}
+		return false;
+	}
+
+	//Print skills for player for debugging
+	public static void printSkills(PlayerCharacter pc) {
+		if (pc == null)
+			return;
+		ConcurrentHashMap<String, CharacterSkill> skills = pc.getSkills();
+		String out = "Player: " + pc.getObjectUUID() + ", SkillCount: " + skills.size();
+		Iterator<String> it = skills.keySet().iterator();
+		while(it.hasNext()) {
+			String name = it.next();
+			out += ", " + name;
+		}
+		Logger.info( out);
+	}
+
+	public static int getMaxTrains(int intt) {
+		if (intt > 0 && intt < 191)
+			return CharacterSkill.maxTrains[intt];
+		else
+			return (int)(33 + 1.25 * intt - 0.005 * Math.pow(intt, 2));
+	}
+
+	public int getSkillPercentFromAttributes(){
+		AbstractCharacter ac = CharacterSkill.GetOwner(this);
+
+		if (ac == null)
+			return 0;
+
+		float statMod = 0;
+
+		if (this.skillsBase.getStrMod() > 0){
+			float strengthModPercent = (float)this.skillsBase.getStrMod() * .01f;
+			strengthModPercent *= ac.getStatStrCurrent() * .01f + .6f;
+			statMod += strengthModPercent;
+		}
+
+		if (this.skillsBase.getDexMod() > 0){
+			float dexModPercent = (float)this.skillsBase.getDexMod() * .01f;
+			dexModPercent *= ac.getStatDexCurrent() * .01f + .6f;
+			statMod += dexModPercent;
+		}
+		if (this.skillsBase.getConMod() > 0){
+			float conModPercent = (float)this.skillsBase.getConMod() * .01f;
+			conModPercent *= ac.getStatConCurrent() * .01f + .6f;
+			statMod += conModPercent;
+		}
+		if (this.skillsBase.getIntMod() > 0){
+			float intModPercent = (float)this.skillsBase.getIntMod() * .01f;
+			intModPercent *= ac.getStatIntCurrent() * .01f + .6f;
+			statMod += intModPercent;
+		}
+		if (this.skillsBase.getSpiMod() > 0){
+			float spiModPercent = (float)this.skillsBase.getSpiMod() * .01f;
+			spiModPercent *= ac.getStatSpiCurrent() * .01f + .6f;
+			statMod += spiModPercent;
+		}
+
+		statMod = (float) (Math.pow(statMod, 1.5f) * 15f);
+		if (statMod < 1)
+			statMod = 1f;
+		else if (statMod > 600)
+			statMod = 600f;
+
+
+
+
+		return (int) statMod;
+	}
+
+	public int getSkillPercentFromTrains(){
+
+		int trains = this.numTrains.get();
+		if ( trains <= 10 )
+			return 2 * trains;
+		if ( trains <= 90 )
+			return trains + 10;
+		if ( trains  > 130 )
+			return (int) (120 - ((trains - 130) * -0.33000001));
+		return (int) (100 - ((trains - 90) * -0.5));
+
+	}
+
+	public int getTotalSkillPercet(){
+
+		AbstractCharacter ac = CharacterSkill.GetOwner(this);
+
+		if (ac == null)
+			return 0;
+
+		float bonus = 0f;
+		SourceType sourceType = SourceType.GetSourceType(this.skillsBase.getNameNoSpace());
+		if (CharacterSkill.GetOwner(this).getBonuses() != null) {
+			//Get bonuses from runes
+			bonus = CharacterSkill.GetOwner(this).getBonuses().getSkillBonus(this.skillsBase.sourceType);
+		}
+		return this.getSkillPercentFromTrains() + this.getSkillPercentFromAttributes();
+	}
+
+	@Override
+	public void updateDatabase() {
+		DbManager.CharacterSkillQueries.updateDatabase(this);
+	}
+
+	public int getRequiredLevel() {
+		return requiredLevel;
+	}
+
+	public void setRequiredLevel(int requiredLevel) {
+		this.requiredLevel = requiredLevel;
+	}
+}
diff --git a/src/engine/objects/CharacterTitle.java b/src/engine/objects/CharacterTitle.java
new file mode 100644
index 00000000..1ca8a4e6
--- /dev/null
+++ b/src/engine/objects/CharacterTitle.java
@@ -0,0 +1,121 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.objects;
+
+import engine.net.ByteBufferWriter;
+
+import java.nio.ByteBuffer;
+
+public enum CharacterTitle {
+
+	NONE(0,0,0,""),
+	CSR_1(255,0,0,"CCR"),
+	CSR_2(255,0,0,"CCR"),
+	CSR_3(255,0,0,"CCR"),
+	CSR_4(251,181,13,"CCR"),
+	DEVELOPER(166,153,114,"Programmer"),
+	QA(88,250,244,"GIRLFRIEND");
+
+	CharacterTitle(int _r, int _g, int _b, String _prefix) {
+		char[] str_header = ("^\\c" +
+								(((_r < 100)?((_r < 10)?"00":"0"):"") + ((byte) _r & 0xFF)) +
+								(((_g < 100)?((_g < 10)?"00":"0"):"") + ((byte) _g & 0xFF)) +
+								(((_b < 100)?((_b < 10)?"00":"0"):"") + ((byte) _b & 0xFF)) +
+                '<' + _prefix + "> ").toCharArray();
+
+		char[] str_footer = ("^\\c255255255").toCharArray();
+
+		this.headerLength = str_header.length;
+		this.footerLength = str_footer.length;
+
+		this.header = ByteBuffer.allocateDirect(headerLength << 1);
+		this.footer = ByteBuffer.allocateDirect(footerLength << 1);
+
+		ByteBufferWriter headWriter= new ByteBufferWriter(header);
+
+		for(char c : str_header) {
+			headWriter.putChar(c);
+		}
+
+		ByteBufferWriter footWriter = new ByteBufferWriter(footer);
+
+		for(char c : str_footer) {
+			footWriter.putChar(c);
+		}
+	}
+
+	int headerLength, footerLength;
+	private ByteBuffer header;
+	private ByteBuffer footer;
+
+	public void _serializeFirstName(ByteBufferWriter writer, String firstName) {
+		_serializeFirstName(writer, firstName, false);
+	}
+
+	public void _serializeFirstName(ByteBufferWriter writer, String firstName, boolean smallString) {
+		if(this.ordinal() == 0) {
+			if (smallString)
+				writer.putSmallString(firstName);
+			else
+				writer.putString(firstName);
+			return;
+		}
+
+		char[] chars = firstName.toCharArray();
+
+		if (smallString)
+			writer.put((byte)(chars.length + this.headerLength));
+		else
+			writer.putInt(chars.length + this.headerLength);
+		writer.putBB(header);
+
+		for(char c : chars) {
+			writer.putChar(c);
+		}
+	}
+
+	public void _serializeLastName(ByteBufferWriter writer, String lastName, boolean haln, boolean asciiLastName) {
+		_serializeLastName(writer, lastName, haln, asciiLastName, false);
+	}
+
+	public void _serializeLastName(ByteBufferWriter writer, String lastName, boolean haln, boolean asciiLastName, boolean smallString) {
+		if (!haln || asciiLastName) {
+			if(this.ordinal() == 0) {
+				if (smallString)
+					writer.putSmallString(lastName);
+				else
+					writer.putString(lastName);
+				return;
+			}
+		}
+
+		if (!haln || asciiLastName) {
+			char[] chars = lastName.toCharArray();
+
+			if (smallString)
+				writer.put((byte)(chars.length + this.footerLength));
+			else
+				writer.putInt(chars.length + this.footerLength);
+
+			for(char c : chars) {
+				writer.putChar(c);
+			}
+
+			writer.putBB(footer);
+		} else {
+			if (smallString)
+				writer.put((byte)this.footerLength);
+			else
+				writer.putInt(this.footerLength);
+			writer.putBB(footer);
+		}
+
+	}
+}
diff --git a/src/engine/objects/City.java b/src/engine/objects/City.java
new file mode 100644
index 00000000..f4ed1b52
--- /dev/null
+++ b/src/engine/objects/City.java
@@ -0,0 +1,1486 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+
+package engine.objects;
+
+import engine.Enum;
+import engine.Enum.*;
+import engine.InterestManagement.HeightMap;
+import engine.InterestManagement.RealmMap;
+import engine.InterestManagement.WorldGrid;
+import engine.db.archive.CityRecord;
+import engine.db.archive.DataWarehouse;
+import engine.gameManager.*;
+import engine.math.Bounds;
+import engine.math.FastMath;
+import engine.math.Vector2f;
+import engine.math.Vector3fImmutable;
+import engine.net.ByteBufferWriter;
+import engine.net.Dispatch;
+import engine.net.DispatchMessage;
+import engine.net.client.msg.ErrorPopupMsg;
+import engine.net.client.msg.TaxResourcesMsg;
+import engine.net.client.msg.ViewResourcesMessage;
+import engine.powers.EffectsBase;
+import engine.server.MBServerStatics;
+import engine.workthreads.DestroyCityThread;
+import engine.workthreads.TransferCityThread;
+import org.pmw.tinylog.Logger;
+
+import java.net.UnknownHostException;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ThreadLocalRandom;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+public class City extends AbstractWorldObject {
+
+	private String cityName;
+	private String motto;
+	private  String description;
+	public java.time.LocalDateTime established;
+	private  int isNoobIsle; //1: noob, 0: not noob: -1: not noob, no teleport
+	private int population = 0;
+	private int siegesWithstood = 0;
+	private  int realmID;
+	private  int radiusType;
+	private  float bindRadius;
+	private  float statLat;
+	private  float statAlt;
+	private  float statLon;
+	private  float bindX;
+	private  float bindZ;
+	private  byte isNpc;  //aka Safehold
+	private byte isCapital = 0;
+	private  byte isSafeHold;
+	private boolean forceRename = false;
+	public boolean hasBeenTransfered = false;
+
+	private boolean noTeleport = false; //used by npc cities
+	private boolean noRepledge = false; //used by npc cities
+	private boolean isOpen = false;
+
+	private int treeOfLifeID;
+	private Vector3fImmutable location = Vector3fImmutable.ZERO;
+	private Vector3fImmutable bindLoc;
+	protected Zone parentZone;
+	private int warehouseBuildingID = 0;
+	private boolean open = false;
+	private boolean reverseKOS = false;
+	public static long lastCityUpdate = 0;
+	public LocalDateTime realmTaxDate;
+	
+	public ReentrantReadWriteLock transactionLock = new ReentrantReadWriteLock();
+
+	// Players who have entered the city (used for adding and removing affects)
+
+	private final HashSet<Integer> _playerMemory = new HashSet<>();
+
+	public volatile boolean protectionEnforced = true;
+	private String hash;
+
+	/**
+	 * ResultSet Constructor
+	 */
+
+	public City(ResultSet rs) throws SQLException, UnknownHostException {
+		super(rs);
+		try{
+			this.cityName = rs.getString("name");
+			this.motto = rs.getString("motto");
+			this.isNpc = rs.getByte("isNpc");
+			this.isSafeHold = (byte) ((this.isNpc == 1) ? 1 : 0);
+			this.description = ""; // TODO Implement this!
+			this.isNoobIsle = rs.getByte("isNoobIsle"); // Noob
+			this.gridObjectType = GridObjectType.STATIC;
+			// Island
+			// City(00000001),
+			// Otherwise(FFFFFFFF)
+			this.population = rs.getInt("population");
+			this.siegesWithstood = rs.getInt("siegesWithstood");
+
+			java.sql.Timestamp establishedTimeStamp = rs.getTimestamp("established");
+
+			if (establishedTimeStamp != null)
+				this.established = java.time.LocalDateTime.ofInstant(establishedTimeStamp.toInstant(), ZoneId.systemDefault());
+
+			this.location = new Vector3fImmutable(rs.getFloat("xCoord"), rs.getFloat("yCoord"), rs.getFloat("zCoord"));
+			this.statLat = rs.getFloat("xCoord");
+			this.statAlt = rs.getFloat("yCoord");
+			this.statLon = rs.getFloat("zCoord");
+
+			java.sql.Timestamp realmTaxTimeStamp = rs.getTimestamp("realmTaxDate");
+
+			if (realmTaxTimeStamp != null)
+				this.realmTaxDate = realmTaxTimeStamp.toLocalDateTime();
+
+			if (this.realmTaxDate == null)
+				this.realmTaxDate = LocalDateTime.now();
+
+			this.treeOfLifeID = rs.getInt("treeOfLifeUUID");
+			this.bindX = rs.getFloat("bindX");
+			this.bindZ = rs.getFloat("bindZ");
+			this.bindLoc = new Vector3fImmutable(this.location.getX() + this.bindX,
+					this.location.getY(),
+					this.location.getZ() + this.bindZ);
+			this.radiusType = rs.getInt("radiusType");
+			float bindradiustemp = rs.getFloat("bindRadius");
+			if (bindradiustemp > 2)
+				bindradiustemp -=2;
+
+			this.bindRadius = bindradiustemp;
+
+			this.forceRename = rs.getInt("forceRename") == 1;
+			this.open = rs.getInt("open") == 1;
+
+			if (this.cityName.equals("Perdition") || this.cityName.equals("Bastion")) {
+				this.noTeleport = true;
+				this.noRepledge = true;
+			} else {
+				this.noTeleport = false;
+				this.noRepledge = false;
+			}
+
+			this.hash = rs.getString("hash");
+			
+			if (this.motto.isEmpty()){
+				Guild guild = this.getGuild();
+				
+				if (guild != null && guild.isErrant() == false)
+					this.motto = guild.getMotto();
+			}
+				
+
+			//Disabled till i finish.
+			// this.reverseKOS  = rs.getInt("kos") == 1;
+
+
+			Zone zone = ZoneManager.getZoneByUUID(rs.getInt("parent"));
+
+			if (zone != null)
+				setParent(zone);
+			
+			//npc cities without heightmaps except swampstone are specials.
+			
+				
+
+			this.realmID = rs.getInt("realmID");
+
+		}catch(Exception e){
+			Logger.error(e);
+		}
+
+		// *** Refactor: Is this working?  Intended to supress
+		//                login server errors from attempting to
+		//                 load cities/realms along with players
+
+
+
+	}
+
+	/*
+	 * Utils
+	 */
+
+	public boolean renameCity(String cityName){
+		if (!DbManager.CityQueries.renameCity(this, cityName))
+			return false;
+		if (!DbManager.CityQueries.updateforceRename(this, false))
+			return false;
+
+		this.cityName = cityName;
+		this.forceRename = false;
+		return true;
+	}
+
+	public boolean updateTOL(Building tol){
+		if (tol == null)
+			return false;
+		if (!DbManager.CityQueries.updateTOL(this, tol.getObjectUUID()))
+			return false;
+		this.treeOfLifeID = tol.getObjectUUID();
+		return true;
+	}
+
+	public boolean renameCityForNewPlant(String cityName){
+		if (!DbManager.CityQueries.renameCity(this, cityName))
+			return false;
+		if (!DbManager.CityQueries.updateforceRename(this, true))
+			return false;
+
+		this.cityName = cityName;
+		this.forceRename = true;
+		return true;
+	}
+
+	public void setForceRename(boolean forceRename) {
+		if (!DbManager.CityQueries.updateforceRename(this, forceRename))
+			return;
+		this.forceRename = forceRename;
+	}
+	public String getCityName() {
+
+		return cityName;
+	}
+
+	public String getMotto() {
+		return motto;
+	}
+
+	public String getDescription() {
+		return description;
+	}
+
+	public Building getTOL() {
+		if (this.treeOfLifeID == 0)
+			return null;
+
+		return BuildingManager.getBuildingFromCache(this.treeOfLifeID);
+
+	}
+
+	public int getIsNoobIsle() {
+		return isNoobIsle;
+	}
+
+	public int getPopulation() {
+		return population;
+	}
+
+	public int getSiegesWithstood() {
+		return siegesWithstood;
+	}
+
+	public float getLatitude() {
+		return this.location.x;
+	}
+
+	public float getLongitude() {
+		return this.location.z;
+	}
+
+	public float getAltitude() {
+		return this.location.y;
+	}
+
+	@Override
+	public Vector3fImmutable getLoc() {
+		return this.location;
+	}
+
+	public byte getIsNpcOwned() {
+		return isNpc;
+	}
+
+	public byte getIsSafeHold() {
+		return this.isSafeHold;
+	}
+
+	public boolean isSafeHold() {
+		return (this.isSafeHold == (byte) 1);
+	}
+
+	public byte getIsCapital() {
+		return isCapital;
+	}
+
+	public void setIsCapital(boolean state) {
+		this.isCapital = (state) ? (byte) 1 : (byte) 0;
+	}
+
+	public int getRadiusType() {
+		return this.radiusType;
+	}
+
+	public float getBindRadius() {
+		return this.bindRadius;
+	}
+
+	public int getRank() {
+		return (this.getTOL() == null) ? 0 : this.getTOL().getRank();
+	}
+
+	public Bane getBane() {
+		return Bane.getBane(this.getObjectUUID());
+	}
+
+	public void setParent(Zone zone) {
+
+		try {
+			
+		
+		this.parentZone = zone;
+		this.location = new Vector3fImmutable(zone.absX + statLat, zone.absY + statAlt, zone.absZ + statLon);
+		this.bindLoc = new Vector3fImmutable(this.location.x + this.bindX,
+				this.location.y,
+				this.location.z + this.bindZ);
+
+		// set city bounds
+
+		Bounds cityBounds = Bounds.borrow();
+		cityBounds.setBounds(new Vector2f(this.location.x + 64, this.location.z + 64), // location x and z are offset by 64 from the center of the city.
+				new Vector2f(Enum.CityBoundsType.GRID.extents, Enum.CityBoundsType.GRID.extents),
+				0.0f);
+		this.setBounds(cityBounds);
+		
+		if (zone.getHeightMap() == null && this.isNpc == 1 && this.getObjectUUID() != 1213 ){
+			HeightMap.GenerateCustomHeightMap(zone);
+			Logger.info(zone.getName() + " created custom heightmap");
+		}
+		}catch(Exception e){
+			Logger.error(e);
+		}
+	}
+
+	public Zone getParent() {
+		return this.parentZone;
+	}
+
+	public boolean isCityZone(Zone zone) {
+
+		if (zone == null || this.parentZone == null)
+			return false;
+
+		return zone.getObjectUUID() == this.parentZone.getObjectUUID();
+
+	}
+
+	public AbstractCharacter getOwner() {
+
+		if (this.getTOL() == null)
+			return null;
+
+		int ownerID = this.getTOL().getOwnerUUID();
+
+		if (ownerID == 0)
+			return null;
+
+		if (this.isNpc == 1)
+			return NPC.getNPC(ownerID);
+		else
+			return PlayerCharacter.getPlayerCharacter(ownerID);
+	}
+
+	public Guild getGuild() {
+
+		if (this.getTOL() == null)
+			return null;
+
+
+
+		if (this.isNpc == 1) {
+
+			if (this.getTOL().getOwner() == null)
+				return null;
+			return this.getTOL().getOwner().getGuild();
+		} else {
+			if (this.getTOL().getOwner() == null)
+				return null;
+			return this.getTOL().getOwner().getGuild();
+		}
+	}
+
+	public boolean openCity(boolean open){
+		if (!DbManager.CityQueries.updateOpenCity(this, open))
+			return false;
+		this.open = open;
+		return true;
+	}
+
+	
+	public static void _serializeForClientMsg(City city, ByteBufferWriter writer) {
+		City.serializeForClientMsg(city,writer);
+	}
+
+	/*
+	 * Serializing
+	 */
+
+	
+	public static void serializeForClientMsg(City city, ByteBufferWriter writer) {
+		AbstractCharacter guildRuler;
+		Guild rulingGuild;
+		Guild rulingNation;
+		java.time.LocalDateTime dateTime1900;
+
+		// Cities aren't a city without a TOL. Time to early exit.
+		// No need to spam the log here as non-existant TOL's are indicated
+		// during bootstrap routines.
+
+		if (city.getTOL() == null){
+
+			Logger.error( "NULL TOL FOR " + city.cityName);
+		}
+
+
+		// Assign city owner
+
+		if (city.getTOL() != null)
+			guildRuler = city.getTOL().getOwner();
+		else guildRuler = null;
+
+		// If is an errant tree, use errant guild for serialization.
+		// otherwise we serialize the soverign guild
+
+		if (guildRuler == null)
+			rulingGuild = Guild.getErrantGuild();
+		else
+			rulingGuild = guildRuler.getGuild();
+
+		rulingNation = rulingGuild.getNation();
+
+		// Begin Serialzing soverign guild data
+		writer.putInt(city.getObjectType().ordinal());
+		writer.putInt(city.getObjectUUID());
+		writer.putString(city.cityName);
+		writer.putInt(rulingGuild.getObjectType().ordinal());
+		writer.putInt(rulingGuild.getObjectUUID());
+
+		writer.putString(rulingGuild.getName());
+		writer.putString(city.motto);
+		writer.putString(rulingGuild.getLeadershipType());
+
+		// Serialize guild ruler's name
+		// If tree is abandoned blank out the name
+		// to allow them a rename.
+
+		if (guildRuler == null)
+			writer.putString("");
+		else
+			writer.putString(guildRuler.getFirstName() + ' ' + guildRuler.getLastName());
+
+		writer.putInt(rulingGuild.getCharter());
+		writer.putInt(0); // always 00000000
+
+		writer.put(city.isSafeHold);
+
+		writer.put((byte) 1);
+		writer.put((byte) 1);  // *** Refactor: What are these flags?
+		writer.put((byte) 1);
+		writer.put((byte) 1);
+		writer.put((byte) 1);
+
+		GuildTag._serializeForDisplay(rulingGuild.getGuildTag(),writer);
+		GuildTag._serializeForDisplay(rulingNation.getGuildTag(),writer);
+
+		writer.putInt(0);// TODO Implement description text
+
+		writer.put((byte) 1);
+
+		if (city.isCapital > 0)
+			writer.put((byte) 1);
+		else
+			writer.put((byte) 0);
+
+		writer.put((byte) 1);
+
+		// Begin serializing nation guild info
+
+		if (rulingNation.isErrant()){
+			writer.putInt(rulingGuild.getObjectType().ordinal());
+			writer.putInt(rulingGuild.getObjectUUID());
+		}
+
+		else{
+			writer.putInt(rulingNation.getObjectType().ordinal());
+			writer.putInt(rulingNation.getObjectUUID());
+		}
+
+
+		// Serialize nation name
+
+		if (rulingNation.isErrant())
+			writer.putString("None");
+		else
+			writer.putString(rulingNation.getName());
+
+		writer.putInt(city.getTOL().getRank());
+
+		if (city.isNoobIsle > 0)
+			writer.putInt(1);
+		else
+			writer.putInt(0xFFFFFFFF);
+
+		writer.putInt(city.population);
+
+		if (rulingNation.isErrant())
+			writer.putString(" ");
+		else
+			writer.putString(Guild.GetGL(rulingNation).getFirstName() + ' ' + Guild.GetGL(rulingNation).getLastName());
+
+
+		writer.putLocalDateTime(city.established);
+
+//		writer.put((byte) city.established.getDayOfMonth());
+//		writer.put((byte) city.established.minusMonths(1).getMonth().getValue());
+//		writer.putInt((int) years);
+//		writer.put((byte) hours);
+//		writer.put((byte) minutes);
+//		writer.put((byte) seconds);
+
+		writer.putFloat(city.location.x);
+		writer.putFloat(city.location.y);
+		writer.putFloat(city.location.z);
+
+		writer.putInt(city.siegesWithstood);
+
+		writer.put((byte) 1);
+		writer.put((byte) 0);
+		writer.putInt(0x64);
+		writer.put((byte) 0);
+		writer.put((byte) 0);
+		writer.put((byte) 0);
+	}
+
+	public static Vector3fImmutable getBindLoc(int cityID) {
+
+		City city;
+
+		city = City.getCity(cityID);
+
+		if (city == null)
+			return Enum.Ruins.getRandomRuin().getLocation();
+
+		return city.getBindLoc();
+	}
+
+	public Vector3fImmutable getBindLoc() {
+		Vector3fImmutable treeLoc = null;
+
+		if (this.getTOL() != null && this.getTOL().getRank() == 8)
+			treeLoc = this.getTOL().getStuckLocation();
+
+		if (treeLoc != null)
+			return treeLoc;
+
+		if (this.radiusType == 1 && this.bindRadius > 0f) {
+			//square radius
+			float x = this.bindLoc.getX();
+			float z = this.bindLoc.getZ();
+			float offset = ((ThreadLocalRandom.current().nextFloat() * 2) - 1) * this.bindRadius;
+			int direction = ThreadLocalRandom.current().nextInt(4);
+
+			switch (direction) {
+			case 0:
+				x += this.bindRadius;
+				z += offset;
+				break;
+			case 1:
+				x += offset;
+				z -= this.bindRadius;
+				break;
+			case 2:
+				x -= this.bindRadius;
+				z += offset;
+				break;
+			case 3:
+				x += offset;
+				z += this.bindRadius;
+				break;
+			}
+			return new Vector3fImmutable(x, this.bindLoc.getY(), z);
+		} else if (this.radiusType == 2 && this.bindRadius > 0f) {
+			//circle radius
+			Vector3fImmutable dir = FastMath.randomVector2D();
+			return this.bindLoc.scaleAdd(this.bindRadius, dir);
+		} else if (this.radiusType == 3 && this.bindRadius > 0f) {
+			//random inside square
+			float x = this.bindLoc.getX();
+			x += ((ThreadLocalRandom.current().nextFloat() * 2) - 1) * this.bindRadius;
+			float z = this.bindLoc.getZ();
+			z += ((ThreadLocalRandom.current().nextFloat() * 2) - 1) * this.bindRadius;
+			return new Vector3fImmutable(x, this.bindLoc.getY(), z);
+		} else if (this.radiusType == 4 && this.bindRadius > 0f) {
+			//random inside circle
+			Vector3fImmutable dir = FastMath.randomVector2D();
+			return this.bindLoc.scaleAdd(ThreadLocalRandom.current().nextFloat() * this.bindRadius, dir);
+		} else
+			//spawn at bindLoc
+			//System.out.println("x: " + this.bindLoc.x + ", z: " + this.bindLoc.z);
+			return this.bindLoc;
+	}
+
+	public static ArrayList<City> getCitiesToTeleportTo(PlayerCharacter pc) {
+
+		ArrayList<City> cities = new ArrayList<>();
+
+		if (pc == null)
+			return cities;
+
+		Guild pcG = pc.getGuild();
+
+		ConcurrentHashMap<Integer, AbstractGameObject> worldCities = DbManager.getMap(Enum.GameObjectType.City);
+
+		//add npc cities
+		for (AbstractGameObject ago : worldCities.values()) {
+
+			if (ago.getObjectType().equals(GameObjectType.City)) {
+				City city = (City) ago;
+
+				if (city.noTeleport)
+					continue;
+
+				if (city.parentZone != null && city.parentZone.isPlayerCity()) {
+
+                    if (pc.getAccount().status.equals(AccountStatus.ADMIN)) {
+                        cities.add(city);
+                    } else
+                        //list Player cities
+
+                        //open city, just list
+                        if (city.open && city.getTOL() != null && city.getTOL().getRank() > 4) {
+
+                            if (!BuildingManager.IsPlayerHostile(city.getTOL(), pc))
+                                cities.add(city); //verify nation or guild is same
+                        }
+
+						else if (Guild.sameNationExcludeErrant(city.getGuild(), pcG))
+							cities.add(city);
+
+
+				} else if (city.isNpc == 1) {
+					//list NPC cities
+					Guild g = city.getGuild();
+					if (g == null) {
+						if (city.isNpc == 1)
+							if (city.isNoobIsle == 1) {
+								if (pc.getLevel() < 21)
+									cities.add(city);
+							} else if (pc.getLevel() > 9)
+								cities.add(city);
+
+					} else if (pc.getLevel() >= g.getTeleportMin() && pc.getLevel() <= g.getTeleportMax()){
+
+
+						cities.add(city);
+					}
+				}
+
+
+			}
+		}
+
+		return cities;
+	}
+
+	public NPC getRuneMaster() {
+		NPC outNPC = null;
+
+		if (this.getTOL() == null)
+			return outNPC;
+
+		for (AbstractCharacter npc : getTOL().getHirelings().keySet()) {
+			if (npc.getObjectType() == GameObjectType.NPC)
+				if (((NPC)npc).getContract().isRuneMaster() == true)
+					outNPC = (NPC)npc;
+		}
+
+		return outNPC;
+	}
+
+	public static ArrayList<City> getCitiesToRepledgeTo(PlayerCharacter pc) {
+		ArrayList<City> cities = new ArrayList<>();
+		if (pc == null)
+			return cities;
+		Guild pcG = pc.getGuild();
+
+		ConcurrentHashMap<Integer, AbstractGameObject> worldCities = DbManager.getMap(Enum.GameObjectType.City);
+
+		//add npc cities
+		for (AbstractGameObject ago : worldCities.values()) {
+			if (ago.getObjectType().equals(GameObjectType.City)) {
+				City city = (City) ago;
+				if (city.noRepledge)
+					continue;
+
+				if (city.parentZone != null && city.parentZone.isPlayerCity()) {
+
+                    //list Player cities
+                    //open city, just list
+                    if (pc.getAccount().status.equals(AccountStatus.ADMIN)) {
+                        cities.add(city);
+                    } else if (city.open && city.getTOL() != null && city.getTOL().getRank() > 4) {
+
+                        if (!BuildingManager.IsPlayerHostile(city.getTOL(), pc))
+                            cities.add(city); //verify nation or guild is same
+                    } else if (Guild.sameNationExcludeErrant(city.getGuild(), pcG))
+                        cities.add(city);
+
+				} else if (city.isNpc == 1) {
+					//list NPC cities
+
+					Guild g = city.getGuild();
+					if (g == null) {
+						if (city.isNpc == 1)
+							if (city.isNoobIsle == 1) {
+								if (pc.getLevel() < 21)
+									cities.add(city);
+							} else if (pc.getLevel() > 9)
+								cities.add(city);
+					} else if (pc.getLevel() >= g.getRepledgeMin() && pc.getLevel() <= g.getRepledgeMax()){
+
+						cities.add(city);
+					}
+				}
+			}
+		}
+		return cities;
+	}
+
+	public boolean isOpen() {
+		return open;
+	}
+
+	public static void loadCities(Zone zone) {
+
+		ArrayList<City> cities = DbManager.CityQueries.GET_CITIES_BY_ZONE(zone.getObjectUUID());
+
+		for (City city : cities) {
+
+			city.setParent(zone);
+			city.setObjectTypeMask(MBServerStatics.MASK_CITY);
+            city.setLoc(city.location);
+            
+            //not player city, must be npc city..
+            if (!zone.isPlayerCity())
+            	zone.setNPCCity(true);
+            
+			if ((ConfigManager.serverType.equals(ServerType.WORLDSERVER)) && (city.hash == null)) {
+
+				city.setHash();
+
+				if (DataWarehouse.recordExists(Enum.DataRecordType.CITY, city.getObjectUUID()) == false) {
+					CityRecord cityRecord = CityRecord.borrow(city, Enum.RecordEventType.CREATE);
+					DataWarehouse.pushToWarehouse(cityRecord);
+				}
+			}
+		}
+	}
+
+
+
+	@Override
+	public void updateDatabase() {
+		// TODO Create update logic.
+	}
+
+	public static City getCity(int cityId) {
+
+		if (cityId == 0)
+			return null;
+
+		City city = (City) DbManager.getFromCache(Enum.GameObjectType.City, cityId);
+		if (city != null)
+			return city;
+
+		return DbManager.CityQueries.GET_CITY(cityId);
+
+	}
+	public static City GetCityFromCache(int cityId) {
+
+		if (cityId == 0)
+			return null;
+
+		return (City) DbManager.getFromCache(Enum.GameObjectType.City, cityId);
+	}
+
+	@Override
+	public void runAfterLoad() {
+
+		// Set city bounds
+		// *** Note: Moved to SetParent()
+		//     for some undocumented reason
+
+		// Set city motto to current guild motto
+
+		if (BuildingManager.getBuilding(this.treeOfLifeID) == null)
+			Logger.info( "City UID " + this.getObjectUUID() + " Failed to Load Tree of Life with ID " + this.treeOfLifeID);
+
+		if ((ConfigManager.serverType.equals(ServerType.WORLDSERVER))
+				&& (this.isNpc == (byte) 0)) {
+
+			Realm wsr = Realm.getRealm(this.realmID);
+
+			if (wsr != null)
+				wsr.addCity(this.getObjectUUID());
+			else
+				Logger.error("Unable to find realm of ID " + realmID + " for city " + this.getObjectUUID());
+		}
+
+		if (this.getGuild() != null) {
+			this.motto = this.getGuild().getMotto();
+
+			// Determine if this city is a nation capitol
+
+			if (this.getGuild().getGuildState() == GuildState.Nation)
+				for (Guild sub : this.getGuild().getSubGuildList()) {
+
+					if ( (sub.getGuildState() == GuildState.Protectorate) ||
+							(sub.getGuildState() == GuildState.Province))
+						this.isCapital = 1;
+				}
+
+			ArrayList<PlayerCharacter> guildList = Guild.GuildRoster(this.getGuild());
+
+			this.population = guildList.size();
+		}
+
+		// Banes are loaded for this city from the database at this point
+
+		if (this.getBane() == null)
+			return;
+
+		// if this city is baned, add the siege effect
+
+		try {
+			this.getTOL().addEffectBit((1 << 16));
+			this.getBane().getStone().addEffectBit((1 << 19));;
+		}catch(Exception e){
+			Logger.info("Failed ao add bane effects on city." + e.getMessage());
+		}
+	}
+
+	public void addCityEffect(EffectsBase effectBase, int rank) {
+
+		HashSet<AbstractWorldObject> currentPlayers;
+		PlayerCharacter player;
+
+		// Add this new effect to the current city effect collection.
+		// so any new player to the grid will have all effects applied
+
+		this.addEffectNoTimer(Integer.toString(effectBase.getUUID()), effectBase, rank, false);
+
+		// Any players currently in the zone will not be processed by the heartbeat
+		// if it's not the first effect toggled so we do it here manually
+
+		currentPlayers = WorldGrid.getObjectsInRangePartial(this.location, this.parentZone.getBounds().getHalfExtents().x * 1.2f, MBServerStatics.MASK_PLAYER);
+
+		for (AbstractWorldObject playerObject : currentPlayers) {
+
+			if (playerObject == null)
+				continue;
+			if (!this.isLocationOnCityZone(playerObject.getLoc()))
+				continue;
+
+			player = (PlayerCharacter) playerObject;
+			player.addCityEffect(Integer.toString(effectBase.getUUID()), effectBase, rank, MBServerStatics.FOURTYFIVE_SECONDS, true,this);
+		}
+
+	}
+
+	public void removeCityEffect(EffectsBase effectBase, int rank, boolean refreshEffect) {
+
+
+		PlayerCharacter player;
+
+		// Remove the city effect from the ago's internal collection
+
+		if (this.getEffects().containsKey(Integer.toString(effectBase.getUUID())))
+			this.getEffects().remove(Integer.toString(effectBase.getUUID()));
+
+		// Any players currently in the zone will not be processed by the heartbeat
+		// so we do it here manually
+
+
+		for (Integer playerID : this._playerMemory) {
+
+			player = PlayerCharacter.getFromCache(playerID);
+			if (player == null)
+				continue;
+
+			player.endEffectNoPower(Integer.toString(effectBase.getUUID()));
+
+			// Reapply effect with timeout?
+
+			if (refreshEffect == true)
+				player.addCityEffect(Integer.toString(effectBase.getUUID()), effectBase, rank, MBServerStatics.FOURTYFIVE_SECONDS, false,this);
+
+		}
+
+	}
+
+	public Warehouse getWarehouse() {
+		if (this.warehouseBuildingID == 0)
+			return null;
+		return Warehouse.warehouseByBuildingUUID.get(this.warehouseBuildingID);
+	}
+
+	public Realm getRealm() {
+
+		return Realm.getRealm(this.realmID);
+
+	}
+
+	public boolean isLocationOnCityGrid(Vector3fImmutable insideLoc) {
+
+		Bounds newBounds = Bounds.borrow();
+		newBounds.setBounds(insideLoc);
+		boolean collided = Bounds.collide(this.getBounds(), newBounds,0);
+		newBounds.release();
+		return collided;
+	}
+	
+	public boolean isLocationOnCityGrid(Bounds newBounds) {
+
+		boolean collided = Bounds.collide(this.getBounds(), newBounds,0);
+		return collided;
+	}
+
+	public boolean isLocationWithinSiegeBounds(Vector3fImmutable insideLoc) {
+
+		return insideLoc.isInsideCircle(this.getLoc(), CityBoundsType.SIEGE.extents);
+
+	}
+
+	public boolean isLocationOnCityZone(Vector3fImmutable insideLoc) {
+		return Bounds.collide(insideLoc, this.parentZone.getBounds());
+	}
+
+	private void applyAllCityEffects(PlayerCharacter player) {
+
+		Effect effect;
+		EffectsBase effectBase;
+
+		try {
+			for (String cityEffect : this.getEffects().keySet()) {
+
+				effect = this.getEffects().get(cityEffect);
+				effectBase = effect.getEffectsBase();
+
+				if (effectBase == null)
+					continue;
+
+				player.addCityEffect(Integer.toString(effectBase.getUUID()), effectBase, effect.getTrains(), MBServerStatics.FOURTYFIVE_SECONDS, true,this);
+			}
+		} catch (Exception e) {
+			Logger.error( e.getMessage());
+		}
+
+	}
+
+	private void removeAllCityEffects(PlayerCharacter player,boolean force) {
+
+		Effect effect;
+		EffectsBase effectBase;
+
+		try {
+			for (String cityEffect : this.getEffects().keySet()) {
+
+				effect = this.getEffects().get(cityEffect);
+				effectBase = effect.getEffectsBase();
+
+				if (player.getEffects().get(cityEffect) == null)
+					return;
+
+				//                player.endEffectNoPower(cityEffect);
+				player.addCityEffect(Integer.toString(effectBase.getUUID()), effectBase, effect.getTrains(), MBServerStatics.FOURTYFIVE_SECONDS, false,this);
+			}
+		} catch (Exception e) {
+			Logger.error(e.getMessage());
+		}
+	}
+
+	public void onEnter() {
+
+		HashSet<AbstractWorldObject> currentPlayers;
+		HashSet<Integer> currentMemory;
+		PlayerCharacter player;
+
+		// Gather current list of players within a distance defined by the x extent of the
+		// city zone.  As we want to grab all players with even a remote possibility of
+		// being in the zone, we might have to increase this distance.
+
+		currentPlayers = WorldGrid.getObjectsInRangePartial(this.location, this.parentZone.getBounds().getHalfExtents().x * 1.41421356237, MBServerStatics.MASK_PLAYER);
+		currentMemory = new HashSet<>();
+
+		for (AbstractWorldObject playerObject : currentPlayers) {
+
+			if (playerObject == null)
+				continue;
+
+			player = (PlayerCharacter) playerObject;
+			currentMemory.add(player.getObjectUUID());
+
+			// Player is already in our memory
+
+			if (_playerMemory.contains(player.getObjectUUID()))
+				continue;
+
+			if (!this.isLocationOnCityZone(player.getLoc()))
+				continue;
+
+			// Apply safehold affect to player if needed
+
+			if ((this.isSafeHold == 1))
+				player.setSafeZone(true);
+
+			//add spire effects.
+			if (this.getEffects().size() > 0)
+				this.applyAllCityEffects(player);
+
+			// Add player to our city's memory
+
+			_playerMemory.add(player.getObjectUUID());
+
+			// ***For debugging
+			// Logger.info("PlayerMemory for ", this.getCityName() + ": " + _playerMemory.size());
+		}
+		try {
+			onExit(currentMemory);
+		} catch (Exception e) {
+			Logger.error( e.getMessage());
+		}
+
+	}
+
+	/* All characters in city player memory but
+	 * not the current memory have obviously
+	 * left the city.  Remove their affects.
+	 */
+
+	private void onExit(HashSet<Integer> currentMemory) {
+
+		PlayerCharacter player;
+		int playerUUID = 0;
+		HashSet<Integer> toRemove = new HashSet<>();
+		Iterator<Integer> iter = _playerMemory.iterator();
+
+		while (iter.hasNext()) {
+
+			playerUUID = iter.next();
+
+
+
+			player = PlayerCharacter.getFromCache(playerUUID);
+			if (this.isLocationOnCityZone(player.getLoc()))
+				continue;
+
+			// Remove players safezone status if warranted
+			// they can assumed to be not on the citygrid at
+			// this point.
+
+
+			player.setSafeZone(false);
+
+			this.removeAllCityEffects(player,false);
+
+			// We will remove this player after iteration is complete
+			// so store it in a temporary collection
+
+			toRemove.add(playerUUID);
+
+			// ***For debugging
+			// Logger.info("PlayerMemory for ", this.getCityName() + ": " + _playerMemory.size());
+		}
+
+		// Remove players from city memory
+
+		_playerMemory.removeAll(toRemove);
+	}
+
+	public int getWarehouseBuildingID() {
+		return warehouseBuildingID;
+	}
+
+	public void setWarehouseBuildingID(int warehouseBuildingID) {
+		this.warehouseBuildingID = warehouseBuildingID;
+	}
+
+	public final void destroy() {
+
+		Thread destroyCityThread = new Thread(new DestroyCityThread(this));
+
+		destroyCityThread.setName("deestroyCity:" + this.getName());
+		destroyCityThread.start();
+	}
+
+	public final void transfer(AbstractCharacter newOwner) {
+
+		Thread transferCityThread = new Thread(new TransferCityThread(this, newOwner));
+
+		transferCityThread.setName("TransferCity:" + this.getName());
+		transferCityThread.start();
+	}
+
+	public final void claim(AbstractCharacter sourcePlayer) {
+
+		Guild sourceNation;
+		Guild sourceGuild;
+		Zone cityZone;
+
+		sourceGuild = sourcePlayer.getGuild();
+
+		if (sourceGuild == null)
+			return;
+
+		sourceNation = sourcePlayer.getGuild().getNation();
+
+		if (sourceGuild.isErrant())
+			return;
+
+		//cant claim tree with owned tree.
+
+		if (sourceGuild.getOwnedCity() != null)
+			return;
+
+		cityZone = this.parentZone;
+
+		// Can't claim a tree not in a player city zone
+
+		// Reset sieges withstood
+
+		this.setSiegesWithstood(0);
+
+		this.hasBeenTransfered = true;
+
+		// If currently a sub of another guild, desub when
+		// claiming your new tree and set as Landed
+
+		if (!sourceNation.isErrant() && sourceNation != sourceGuild) {
+			if (!DbManager.GuildQueries.UPDATE_PARENT(sourceGuild.getObjectUUID(), MBServerStatics.worldUUID)) {
+				ChatManager.chatGuildError((PlayerCharacter) sourcePlayer, "A Serious error has occurred. Please post details for to ensure transaction integrity");
+				return;
+			}
+
+			sourceNation.getSubGuildList().remove(sourceGuild);
+
+			if (sourceNation.getSubGuildList().isEmpty())
+				sourceNation.downgradeGuildState();
+		}
+
+		// Link the mew guild with the tree
+
+		if (!DbManager.GuildQueries.SET_GUILD_OWNED_CITY(sourceGuild.getObjectUUID(), this.getObjectUUID())) {
+			ChatManager.chatGuildError((PlayerCharacter) sourcePlayer, "A Serious error has occurred. Please post details for to ensure transaction integrity");
+			return;
+		}
+
+		sourceGuild.setCityUUID(this.getObjectUUID());
+
+		sourceGuild.setNation(sourceGuild);
+		sourceGuild.setGuildState(GuildState.Sovereign);
+		GuildManager.updateAllGuildTags(sourceGuild);
+		GuildManager.updateAllGuildBinds(sourceGuild, this);
+
+		// Build list of buildings within this parent zone
+
+		for (Building cityBuilding : cityZone.zoneBuildingSet) {
+
+			// Buildings without blueprints are unclaimable
+
+			if (cityBuilding.getBlueprintUUID() == 0)
+				continue;
+
+			// All protection contracts are void upon transfer of a city
+
+			// All protection contracts are void upon transfer of a city
+			//Dont forget to not Flip protection on Banestones and siege Equipment... Noob.
+			if (cityBuilding.getBlueprint() != null && !cityBuilding.getBlueprint().isSiegeEquip()
+					&& cityBuilding.getBlueprint().getBuildingGroup() != BuildingGroup.BANESTONE)
+				cityBuilding.setProtectionState(ProtectionState.NONE);
+
+			// Transfer ownership of valid city assets
+			// these assets are autoprotected.
+
+			if ((cityBuilding.getBlueprint().getBuildingGroup() == BuildingGroup.TOL)
+					|| (cityBuilding.getBlueprint().getBuildingGroup() == BuildingGroup.SPIRE)
+					|| (cityBuilding.getBlueprint().getBuildingGroup() == BuildingGroup.BARRACK)
+					|| (cityBuilding.getBlueprint().isWallPiece())
+					|| (cityBuilding.getBlueprint().getBuildingGroup() == BuildingGroup.SHRINE)) {
+
+				cityBuilding.claim(sourcePlayer);
+				cityBuilding.setProtectionState(ProtectionState.PROTECTED);
+			}
+		}
+
+		this.setForceRename(true);
+
+		// Reset city timer for map update
+
+		City.lastCityUpdate = System.currentTimeMillis();
+	}
+
+	public final boolean transferGuildLeader(PlayerCharacter sourcePlayer) {
+
+		Guild sourceGuild;
+		Zone cityZone;
+		sourceGuild = sourcePlayer.getGuild();
+
+
+		if (sourceGuild == null)
+			return false;
+
+		if (sourceGuild.isErrant())
+			return false;
+
+		cityZone = this.parentZone;
+
+		for (Building cityBuilding : cityZone.zoneBuildingSet) {
+
+			// Buildings without blueprints are unclaimable
+
+			if (cityBuilding.getBlueprintUUID() == 0)
+				continue;
+
+			// All protection contracts are void upon transfer of a city
+			//Dont forget to not Flip protection on Banestones and siege Equipment... Noob.
+
+			// Transfer ownership of valid city assets
+			// these assets are autoprotected.
+
+			if ((cityBuilding.getBlueprint().getBuildingGroup() == BuildingGroup.TOL)
+					|| (cityBuilding.getBlueprint().getBuildingGroup() == BuildingGroup.SPIRE)
+					|| (cityBuilding.getBlueprint().getBuildingGroup() == BuildingGroup.BARRACK)
+					|| (cityBuilding.getBlueprint().isWallPiece())
+					|| (cityBuilding.getBlueprint().getBuildingGroup() == BuildingGroup.SHRINE)
+					) {
+
+				cityBuilding.claim(sourcePlayer);
+				cityBuilding.setProtectionState(ProtectionState.PROTECTED);
+			} else if(cityBuilding.getBlueprint().getBuildingGroup() == BuildingGroup.WAREHOUSE)
+				cityBuilding.claim(sourcePlayer);
+
+
+		}
+		this.setForceRename(true);
+		CityRecord cityRecord = CityRecord.borrow(this, Enum.RecordEventType.TRANSFER);
+		DataWarehouse.pushToWarehouse(cityRecord);
+		return true;
+
+	}
+
+	/**
+	 * @return the forceRename
+	 */
+	public boolean isForceRename() {
+		return forceRename;
+	}
+
+	/**
+	 * @param siegesWithstood the siegesWithstood to set
+	 */
+	public void setSiegesWithstood(int siegesWithstood) {
+
+		// early exit if setting to current value
+
+		if (this.siegesWithstood == siegesWithstood)
+			return;
+
+		if (DbManager.CityQueries.updateSiegesWithstood(this, siegesWithstood) == true)
+			this.siegesWithstood = siegesWithstood;
+		else
+			Logger.error("Error when writing to database for cityUUID: " + this.getObjectUUID());
+	}
+
+	/**
+	 * @param population the population to set
+	 */
+	public void setPopulation(int population) {
+		this.population = population;
+	}
+
+	public boolean isReverseKOS() {
+		return reverseKOS;
+	}
+
+	public void setReverseKOS(boolean reverseKOS) {
+		this.reverseKOS = reverseKOS;
+	}
+
+	public String getHash() {
+		return hash;
+	}
+
+	public void setHash(String hash) {
+		this.hash = hash;
+	}
+
+	public void setHash() {
+
+		this.hash = DataWarehouse.hasher.encrypt(this.getObjectUUID());
+
+		// Write hash to player character table
+
+		DataWarehouse.writeHash(Enum.DataRecordType.CITY, this.getObjectUUID());
+	}
+
+	public boolean setRealmTaxDate(LocalDateTime realmTaxDate) {
+
+		if (!DbManager.CityQueries.updateRealmTaxDate(this, realmTaxDate))
+			return false;
+
+		this.realmTaxDate = realmTaxDate;
+		return true;
+
+	}
+
+	//TODO use this for taxing later.
+//	public boolean isAfterTaxPeriod(LocalDateTime dateTime,PlayerCharacter player){
+//		if (dateTime.isBefore(realmTaxDate)){
+//			String wait = "";
+//			float hours = 1000*60*60;
+//			float seconds = 1000;
+//			float hoursUntil = realmTaxDate.minus(dateTime.get).getMillis() /hours;
+//			int secondsUntil = (int) (realmTaxDate.minus(dateTime.getMillis()).getMillis() /seconds);
+//			if (hoursUntil < 1)
+//				wait = "You must wait " + secondsUntil + " seconds before taxing this city again!";
+//			else
+//				wait = "You must wait " + hoursUntil + " hours before taxing this city again!";
+//			ErrorPopupMsg.sendErrorMsg(player, wait);
+//			return false;
+//		}
+//
+//		return true;
+//	}
+
+	
+
+	public synchronized boolean TaxWarehouse(TaxResourcesMsg msg,PlayerCharacter player) {
+
+		// Member variable declaration
+		Building building = BuildingManager.getBuildingFromCache(msg.getBuildingID());
+		Guild playerGuild = player.getGuild();
+
+		if (building == null){
+			ErrorPopupMsg.sendErrorMsg(player, "Not a valid Building!");
+			return true;
+		}
+
+		City city = building.getCity();
+		if (city == null){
+			ErrorPopupMsg.sendErrorMsg(player, "This building does not belong to a city.");
+			return true;
+		}
+
+
+		if (playerGuild == null || playerGuild.isErrant()){
+			ErrorPopupMsg.sendErrorMsg(player, "You must belong to a guild to do that!");
+			return true;
+		}
+
+		if (playerGuild.getOwnedCity() == null){
+			ErrorPopupMsg.sendErrorMsg(player, "Your Guild needs to own a city!");
+			return true;
+		}
+
+		if (playerGuild.getOwnedCity().getTOL() == null){
+			ErrorPopupMsg.sendErrorMsg(player, "Cannot find Tree of Life for your city!");
+			return true;
+		}
+
+		if (playerGuild.getOwnedCity().getTOL().getRank() != 8){
+			ErrorPopupMsg.sendErrorMsg(player, "Your City needs to Own a realm!");
+			return true;
+		}
+
+		if (playerGuild.getOwnedCity().getRealm() == null){
+			ErrorPopupMsg.sendErrorMsg(player, "Cannot find realm for your city!");
+			return true;
+		}
+		Realm targetRealm = RealmMap.getRealmForCity(city);
+
+		if (targetRealm == null){
+			ErrorPopupMsg.sendErrorMsg(player, "Cannot find realm for city you are attempting to tax!");
+			return true;
+		}
+
+		if (targetRealm.getRulingCity() == null){
+			ErrorPopupMsg.sendErrorMsg(player, "Realm Does not have a ruling city!");
+			return true;
+		}
+
+		if (targetRealm.getRulingCity().getObjectUUID() != playerGuild.getOwnedCity().getObjectUUID()){
+			ErrorPopupMsg.sendErrorMsg(player, "Your guild does not rule this realm!");
+			return true;
+		}
+
+		if (playerGuild.getOwnedCity().getObjectUUID() == city.getObjectUUID()){
+			ErrorPopupMsg.sendErrorMsg(player, "You cannot tax your own city!");
+			return true;
+		}
+
+
+
+
+		if (!GuildStatusController.isTaxCollector(player.getGuildStatus())){
+			ErrorPopupMsg.sendErrorMsg(player, "You Must be a tax Collector!");
+			return true;
+		}
+
+
+		
+		if (this.realmTaxDate.isAfter(LocalDateTime.now()))
+			return true;
+		if (msg.getResources().size() == 0)
+			return true;
+
+		if (city.getWarehouse() == null)
+			return true;
+		Warehouse ruledWarehouse = playerGuild.getOwnedCity().getWarehouse();
+		if (ruledWarehouse == null)
+			return true;
+
+
+
+		ItemBase.getItemHashIDMap();
+
+		ArrayList<Integer>resources = new ArrayList<>();
+
+		float taxPercent = msg.getTaxPercent();
+		if (taxPercent > 20)
+			taxPercent = .20f;
+
+		for (int resourceHash:msg.getResources().keySet()){
+			if (ItemBase.getItemHashIDMap().get(resourceHash) != null)
+				resources.add(ItemBase.getItemHashIDMap().get(resourceHash));
+
+		}
+
+		for (Integer itemBaseID:resources){
+			ItemBase ib = ItemBase.getItemBase(itemBaseID);
+			if (ib == null)
+				continue;
+			if (ruledWarehouse.isAboveCap(ib, (int) (city.getWarehouse().getResources().get(ib) * taxPercent))){
+				ErrorPopupMsg.sendErrorMsg(player, "You're warehouse has enough " + ib.getName() + " already!");
+				return true;
+			}
+
+		}
+
+		if(!city.setRealmTaxDate(LocalDateTime.now().plusDays(7))){
+			ErrorPopupMsg.sendErrorMsg(player, "Failed to Update next Tax Date due to internal Error. City was not charged taxes this time.");
+			return false;
+		}
+		try{
+			city.getWarehouse().transferResources(player,msg,resources, taxPercent,ruledWarehouse);
+		}catch(Exception e){
+			Logger.info( e.getMessage());
+		}
+
+		// Member variable assignment
+
+		ViewResourcesMessage vrm = new ViewResourcesMessage(player);
+		vrm.setGuild(building.getGuild());
+		vrm.setWarehouseBuilding(BuildingManager.getBuildingFromCache(building.getCity().getWarehouse().getBuildingUID()));
+		vrm.configure();
+		Dispatch dispatch = Dispatch.borrow(player, vrm);
+		DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+		dispatch = Dispatch.borrow(player, msg);
+		DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+		return true;
+
+	}
+}
diff --git a/src/engine/objects/Colliders.java b/src/engine/objects/Colliders.java
new file mode 100644
index 00000000..cbd68bcf
--- /dev/null
+++ b/src/engine/objects/Colliders.java
@@ -0,0 +1,65 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.objects;
+
+import java.awt.geom.Line2D;
+
+
+public class Colliders  {
+
+
+	private Line2D collider;
+	private int doorID;
+	private boolean link = false;
+	public float startX;
+	public float startY;
+	public float endX;
+	public float endY;
+
+	public Colliders(Line2D collider, int doorID, boolean link) {
+		super();
+		this.collider = collider;
+		this.doorID = doorID;
+		this.link = link;
+	}
+	
+	public Colliders(float startX, float startY, float endX ,float endY, int doorID, boolean link) {
+		super();
+		this.startX = startX;
+		this.startY = startY;
+		this.endX = endX;
+		this.endY = endY;
+		this.doorID = doorID;
+		this.link = link;
+	}
+
+
+	public int getDoorID() {
+		return doorID;
+	}
+
+	public Line2D getCollider() {
+		return collider;
+	}
+
+
+	public boolean isLink() {
+		return link;
+	}
+
+
+	public void setLink(boolean link) {
+		this.link = link;
+	}
+
+
+
+
+}
diff --git a/src/engine/objects/Condemned.java b/src/engine/objects/Condemned.java
new file mode 100644
index 00000000..b4ef9744
--- /dev/null
+++ b/src/engine/objects/Condemned.java
@@ -0,0 +1,98 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.objects;
+
+import engine.gameManager.DbManager;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+
+
+
+public class Condemned  {
+
+	private int ID;
+	private int playerUID;
+	private int parent;
+	private int guildUID;
+	private int friendType;
+	private boolean active;
+	public static final int INDIVIDUAL = 2;
+	public static final int GUILD = 4;
+	public static final int NATION = 5;
+
+	
+
+
+
+	/**
+	 * ResultSet Constructor
+	 */
+	public Condemned(ResultSet rs) throws SQLException {
+		this.playerUID = rs.getInt("playerUID");
+		this.parent = rs.getInt("buildingUID");
+		this.guildUID = rs.getInt("guildUID");
+		this.friendType = rs.getInt("friendType");
+		this.active = rs.getBoolean("active");
+	}
+	
+	
+
+
+	public Condemned(int playerUID, int parent, int guildUID, int friendType) {
+		super();
+		this.playerUID = playerUID;
+		this.parent = parent;
+		this.guildUID = guildUID;
+		this.friendType = friendType;
+		this.active = false;
+	}
+
+
+
+
+	public int getPlayerUID() {
+		return playerUID;
+	}
+
+
+	public int getParent() {
+		return parent;
+	}
+
+
+	public int getGuildUID() {
+		return guildUID;
+	}
+
+
+	public int getFriendType() {
+		return friendType;
+	}
+
+	public boolean isActive() {
+		return active;
+	}
+
+
+
+
+	public boolean setActive(boolean active) {
+		if (!DbManager.BuildingQueries.updateActiveCondemn(this, active))
+			return false;
+		this.active = active;
+		return true;
+	}
+
+
+	
+	
+}
diff --git a/src/engine/objects/Contract.java b/src/engine/objects/Contract.java
new file mode 100644
index 00000000..b4ed0fb9
--- /dev/null
+++ b/src/engine/objects/Contract.java
@@ -0,0 +1,301 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.objects;
+
+import ch.claude_martin.enumbitset.EnumBitSet;
+import engine.Enum;
+import engine.gameManager.DbManager;
+import org.pmw.tinylog.Logger;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+
+public class Contract extends AbstractGameObject {
+
+	private final int contractID;
+	private final String name;
+	private final int mobbaseID;
+	private final int classID;
+	private final int extraRune;
+	private final int iconID;
+	private int vendorID;
+	private boolean isTrainer;
+	private VendorDialog vendorDialog;
+	private ArrayList<Integer> npcMenuOptions = new ArrayList<>();
+	private ArrayList<Integer> npcModTypeTable = new ArrayList<>();
+	private ArrayList<Integer> npcModSuffixTable = new ArrayList<>();
+	private ArrayList<Byte> itemModTable = new ArrayList<>();
+	private ArrayList<MobEquipment> sellInventory = new ArrayList<>();
+	private EnumBitSet<Enum.BuildingGroup> allowedBuildings;
+
+	private ArrayList<Integer> buyItemType = new ArrayList<>();
+	private ArrayList<Integer> buySkillToken = new ArrayList<>();
+	private ArrayList<Integer> buyUnknownToken = new ArrayList<>();
+
+	public int equipmentSet = 0;
+	public int inventorySet = 0;
+
+	/**
+	 * No Table ID Constructor
+	 */
+	 public Contract(int contractID, String name, int mobbaseID, int classID, int dialogID, int iconID, int extraRune) {
+		 super();
+		 this.contractID = contractID;
+		 this.name = name;
+		 this.mobbaseID = mobbaseID;
+		 this.classID = classID;
+		 this.iconID = iconID;
+		 this.extraRune = extraRune;
+		 this.vendorDialog = VendorDialog.getVendorDialog(dialogID);
+		 setBools();
+	 }
+
+	 /**
+	  * Normal Constructor
+	  */
+	 public Contract(int contractID, String name, int mobbaseID, int classID, int dialogID, int iconID, int extraRune, int newUUID) {
+		 super(newUUID);
+		 this.contractID = contractID;
+		 this.name = name;
+		 this.mobbaseID = mobbaseID;
+		 this.classID = classID;
+		 this.iconID = iconID;
+		 this.extraRune = extraRune;
+		 this.vendorDialog = VendorDialog.getVendorDialog(dialogID);
+		 setBools();
+	 }
+
+	
+
+	 /**
+	  * ResultSet Constructor
+	  */
+	 public Contract(ResultSet rs) throws SQLException {
+		 super(rs);
+		 this.contractID = rs.getInt("contractID");
+		 this.name = rs.getString("name");
+		 this.mobbaseID = rs.getInt("mobbaseID");
+		 this.classID = rs.getInt("classID");
+		 this.extraRune = rs.getInt("extraRune");
+		 this.vendorDialog = VendorDialog.getVendorDialog(rs.getInt("dialogID"));
+		 this.iconID = rs.getInt("iconID");
+		 this.vendorID = rs.getInt("vendorID");
+		 this.allowedBuildings = EnumBitSet.asEnumBitSet(rs.getLong("allowedBuildingTypeID"), Enum.BuildingGroup.class);
+		 this.equipmentSet = rs.getInt("equipSetID");
+		 this.inventorySet = rs.getInt("inventorySet");
+
+		 try {
+			 String menuoptions = rs.getString("menuoptions");
+
+			 if (!menuoptions.isEmpty()){
+				 String[] data = menuoptions.split(" ");
+				 for (String data1 : data) {
+					 this.npcMenuOptions.add(Integer.parseInt(data1));
+				 }
+			 }
+
+			 String modtypetable = rs.getString("pTable");
+			 if (!modtypetable.isEmpty()){
+				 String[] data = modtypetable.split(" ");
+				 for (String data1 : data) {
+					 this.npcModTypeTable.add(Integer.parseInt(data1));
+				 }
+			 }
+
+			 String suffix = rs.getString("sTable");
+
+			 if (!suffix.isEmpty()){
+				 String[] data1 = suffix.split(" ");
+
+				 for (String data11 : data1) {
+					 this.npcModSuffixTable.add(Integer.parseInt(data11));
+				 }
+			 }
+
+			 String itemMod = rs.getString("itemModTable");
+
+			 if (!itemMod.isEmpty()){
+				 String[] data2 = itemMod.split(" ");
+				 for (byte i = 0; i < data2.length; i++) {
+					 this.itemModTable.add(Byte.parseByte(data2[i]));
+				 }
+
+			 }
+
+		 } catch (SQLException | NumberFormatException e) {
+			 Logger.error( "Error when parsing mod tables");
+		 }
+		 setBools();
+	 }
+
+	 //Specify if trainer, merchant, banker, etc via classID
+	 private void setBools() {
+		 DbManager.ContractQueries.GET_GENERIC_INVENTORY(this);
+		 DbManager.ContractQueries.GET_SELL_LISTS(this);
+
+		 this.isTrainer = this.classID > 2499 && this.classID < 3050 || this.classID == 2028;
+
+	 }
+
+	 /*
+	  * Getters
+	  */
+	 public int getContractID() {
+		 return this.contractID;
+	 }
+
+	 public String getName() {
+		 return this.name;
+	 }
+
+	 public int getMobbaseID() {
+		 return this.mobbaseID;
+	 }
+
+	 public int getClassID() {
+		 return this.classID;
+	 }
+
+	 public int getExtraRune() {
+		 return this.extraRune;
+	 }
+
+	 public boolean isTrainer() {
+		 return this.isTrainer;
+	 }
+
+	 public int getIconID() {
+		 return this.iconID;
+	 }
+
+	 public int getVendorID() {
+		 return this.vendorID;
+	 }
+
+	 public VendorDialog getVendorDialog() {
+		 return this.vendorDialog;
+	 }
+
+	 public ArrayList<Integer> getNPCMenuOptions() {
+		 return this.npcMenuOptions;
+	 }
+
+	 public ArrayList<Integer> getNPCModTypeTable() {
+		 return this.npcModTypeTable;
+	 }
+
+	 public ArrayList<Integer> getNpcModSuffixTable() {
+		 return npcModSuffixTable;
+	 }
+
+	 public ArrayList<Byte> getItemModTable() {
+		 return itemModTable;
+	 }
+
+	 public ArrayList<MobEquipment> getSellInventory() {
+		 return this.sellInventory;
+	 }
+
+	 public int getPromotionClass() {
+		 if (this.classID < 2504 || this.classID > 2526)
+			 return 0;
+		 return this.classID;
+	 }
+
+	 public boolean isRuneMaster() {
+		 return (this.classID == 850);
+	 }
+
+	 public boolean isArtilleryCaptain() {
+		 return this.contractID == 839 || this.contractID == 842 ;
+	 }
+
+	
+	 @Override
+	 public void updateDatabase() {
+		 DbManager.ContractQueries.updateDatabase(this);
+	 }
+
+	public EnumBitSet<Enum.BuildingGroup> getAllowedBuildings() {
+		 return allowedBuildings;
+	 }
+
+	 public ArrayList<Integer> getBuyItemType() {
+		 return this.buyItemType;
+	 }
+
+	 public ArrayList<Integer> getBuySkillToken() {
+		 return this.buySkillToken;
+	 }
+
+	 public ArrayList<Integer> getBuyUnknownToken() {
+		 return this.buyUnknownToken;
+	 }
+
+	 public boolean canSlotinBuilding(Building building) {
+
+		 // Need a building to slot in a building!
+		 if (building == null)
+			 return false;
+
+		 // Can't slot in anything but a blueprintted building
+		 if (building.getBlueprintUUID() == 0)
+			 return false;
+
+		 // No buildings no slotting
+		 if (this.allowedBuildings.size() == 0)
+			 return false;
+
+		 // Binary match
+		 return (building.getBlueprint().getBuildingGroup().elementOf(this.allowedBuildings));
+
+     }
+
+	public int getEquipmentSet() {
+		return equipmentSet;
+	}
+	
+	public static boolean NoSlots(Contract contract){
+		switch(contract.contractID){
+		case 830:
+		case 838:
+		case 847:
+		case 860:
+		case 866:
+		case 865:
+		case 1502003:
+		case 889:
+		case 890:
+		case 896:
+		case 974:
+		case 1064:
+		case 1172:
+		case 1267:
+		case 1368:
+		case 1468:
+		case 1520:
+		case 1528:
+		case 1553:
+		case 1578:
+		case 1617:
+		case 1667:
+		case 1712:
+		case 893:
+		case 820:
+			return true;
+			
+		}
+		
+		if (contract.isTrainer)
+			return true;
+		return false;
+	}
+}
diff --git a/src/engine/objects/Corpse.java b/src/engine/objects/Corpse.java
new file mode 100644
index 00000000..e9c4c195
--- /dev/null
+++ b/src/engine/objects/Corpse.java
@@ -0,0 +1,419 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+
+
+
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.objects;
+
+import engine.Enum.GameObjectType;
+import engine.Enum.GridObjectType;
+import engine.Enum.ItemType;
+import engine.InterestManagement.WorldGrid;
+import engine.exception.SerializationException;
+import engine.gameManager.BuildingManager;
+import engine.gameManager.DbManager;
+import engine.job.JobContainer;
+import engine.job.JobScheduler;
+import engine.jobs.RemoveCorpseJob;
+import engine.net.ByteBufferWriter;
+import engine.net.DispatchMessage;
+import engine.net.client.msg.UnloadObjectsMsg;
+import engine.server.MBServerStatics;
+import org.pmw.tinylog.Logger;
+
+import java.util.ArrayList;
+import java.util.concurrent.atomic.AtomicInteger;
+
+public class Corpse extends AbstractWorldObject {
+
+	private static AtomicInteger corpseCounter = new AtomicInteger(0);
+
+	private String firstName;
+	private String lastName;
+	private int level;
+	private int belongsToType;
+	private int belongsToID;
+	private ArrayList<Item> inventory;
+	public JobContainer cleanup;
+	private boolean asciiLastName = true;
+	private boolean hasGold = false;
+	private int inBuildingID = 0;
+	private int inFloorID = -1;
+	private int inBuilding = -1;
+
+	/**
+	 * No Id Constructor
+	 */
+	public Corpse( int newUUID, AbstractCharacter belongsTo, boolean safeZone,boolean enterWorld) {
+		super(newUUID);
+		this.setObjectType();
+		this.inventory = new ArrayList<>();
+		this.gridObjectType = GridObjectType.STATIC;
+		this.setObjectTypeMask(MBServerStatics.MASK_CORPSE);
+		if (belongsTo != null) {
+			this.firstName = belongsTo.getFirstName();
+			this.lastName = belongsTo.getLastName();
+			this.asciiLastName = belongsTo.asciiLastName();
+			this.level = belongsTo.getLevel();
+			this.belongsToType = belongsTo.getObjectType().ordinal();
+			this.belongsToID = belongsTo.getObjectUUID();
+            this.inBuilding = belongsTo.getInBuilding();
+            this.inFloorID = belongsTo.getInFloorID();
+            this.inBuildingID = belongsTo.getInBuildingID();
+			this.setLoc(belongsTo.getLoc());
+		} else {
+			Logger.error("No player passed in for corpse");
+			this.firstName = "";
+			this.lastName = "";
+			this.level = 1;
+			this.belongsToType = 0;
+			this.belongsToID = 0;
+		}
+		this.setObjectTypeMask(MBServerStatics.MASK_CORPSE);
+
+		if (!safeZone)
+			transferInventory(belongsTo,enterWorld);
+
+
+	}
+
+	public boolean removeItemFromInventory(Item item) {
+		synchronized (this.inventory) {
+			if (this.inventory.contains(item)) {
+				this.inventory.remove(item);
+				return true;
+			}
+			return false;
+		}
+	}
+
+	public void transferInventory(AbstractCharacter belongsTo,boolean enterWorld) {
+		if (belongsTo == null) {
+			Logger.error( "Can't find player that corpse " + this.getObjectUUID() + " belongs to");
+			return;
+		}
+
+		//TODO transfer items from players inventory and trade window to corpse
+		CharacterItemManager cim = belongsTo.getCharItemManager();
+		if (cim != null)
+			cim.transferEntireInventory(this.inventory, this,enterWorld);
+		else
+			Logger.error( "Can't find inventory for player " + belongsTo.getObjectUUID());
+	}
+
+	public static int getNextCorpseCount() {
+		return Corpse.corpseCounter.addAndGet(2); //newUUID and runeID
+	}
+
+	//Create a new corpse
+	public static Corpse makeCorpse(AbstractCharacter belongsTo,boolean enterWorld) {
+		boolean safeZone = false;
+		if (belongsTo != null && belongsTo.getObjectType() == GameObjectType.PlayerCharacter)
+			safeZone = ((PlayerCharacter)belongsTo).isInSafeZone();
+
+
+
+		Corpse corpse = new Corpse(Corpse.getNextCorpseCount(), belongsTo, safeZone,enterWorld);
+
+		//create cleanup job
+		if (corpse != null) {
+			RemoveCorpseJob rcj = new RemoveCorpseJob(corpse);
+            corpse.cleanup = JobScheduler.getInstance().scheduleJob(rcj, MBServerStatics.CORPSE_CLEANUP_TIMER_MS);
+			DbManager.addToCache(corpse);
+		}
+
+		return corpse;
+	}
+
+	//Get existing corpse
+	public static Corpse getCorpse(int newUUID) {
+		return (Corpse) DbManager.getFromCache(GameObjectType.Corpse, newUUID);
+	}
+
+	public Item lootItem(Item i, PlayerCharacter looter) {
+		//make sure looter exists
+		if (looter == null)
+			return null;
+
+		//get looters item manager
+		CharacterItemManager looterItems = looter.getCharItemManager();
+		if (looterItems == null)
+			return null;
+
+		synchronized (this.inventory) {
+
+			//make sure player has item in inventory
+			if (!this.inventory.contains(i))
+				return null;
+
+			//get weight of item
+			ItemBase ib = i.getItemBase();
+			if (ib == null)
+				return null;
+			short weight = ib.getWeight();
+
+			//make sure looter has room for item
+			if (ib.getType().equals(ItemType.GOLD) == false && !looterItems.hasRoomInventory(weight))
+				return null;
+
+			//attempt to transfer item in db
+			if (ib.getType().equals(ItemType.GOLD)) {
+				if (!looterItems.moveGoldToInventory(i, i.getNumOfItems()))
+					return null;
+			} else if (!i.moveItemToInventory(looter))
+				return null;
+
+			//db transfer successful, remove from this character
+			this.inventory.remove(this.inventory.indexOf(i));
+		}
+
+		//add item to looter.
+		if (!looterItems.addItemToInventory(i))
+			return null;
+
+		//calculate new weights
+		looterItems.calculateInventoryWeight();
+
+		return i;
+	}
+
+
+	//remove corpse from world
+	public static void removeCorpse(int newUUID, boolean fromTimer) {
+		Corpse c = (Corpse) DbManager.getFromCache(GameObjectType.Corpse, newUUID);
+		if (c == null)
+			Logger.error( "No corpse found of ID " + newUUID);
+		else
+			Corpse.removeCorpse(c, fromTimer);
+	}
+
+	public static void removeCorpse(Corpse corpse, boolean fromTimer) {
+		if (corpse == null)
+			return;
+
+		corpse.purgeInventory();
+
+		//cleanup timer
+		if (!fromTimer) {
+			JobScheduler.getInstance().cancelScheduledJob(corpse.cleanup);
+		}
+		corpse.cleanup = null;
+
+		//Remove from world
+		UnloadObjectsMsg uom = new UnloadObjectsMsg();
+		uom.addObject(corpse);
+		DispatchMessage.sendToAllInRange(corpse, uom);
+		WorldGrid.RemoveWorldObject(corpse);
+
+		//clear from cache
+		DbManager.removeFromCache(corpse);
+	}
+
+	
+	public static void _serializeForClientMsg(Corpse corpse, ByteBufferWriter writer)
+			throws SerializationException {}
+
+	public static void _serializeForClientMsg(Corpse corpse, ByteBufferWriter writer, boolean aln)
+			throws SerializationException {
+
+		Building building = null;
+		if (corpse.inBuildingID != 0)
+			building =  BuildingManager.getBuildingFromCache(corpse.inBuildingID);
+
+		//Send Rune Count
+		writer.putInt(0);
+		writer.putInt(0);
+		writer.putInt(1);
+
+		//Send Corpse Rune
+		writer.putInt(1);
+		writer.putInt(0);
+		writer.putInt(MBServerStatics.TOMBSTONE);
+		writer.putInt(corpse.getObjectType().ordinal());
+		writer.putInt((corpse.getObjectUUID() + 1));
+
+		//Send Stats
+		writer.putInt(5);
+		writer.putInt(MBServerStatics.STAT_STR_ID); // Strength ID
+		writer.putInt(5000);
+		writer.putInt(MBServerStatics.STAT_SPI_ID); // Spirit ID
+		writer.putInt(0);
+		writer.putInt(MBServerStatics.STAT_CON_ID); // Constitution ID
+		writer.putInt(0);
+		writer.putInt(MBServerStatics.STAT_DEX_ID); // Dexterity ID
+		writer.putInt(0);
+		writer.putInt(MBServerStatics.STAT_INT_ID); // Intelligence ID
+		writer.putInt(0);
+
+		//Send Name
+		writer.putString(corpse.firstName);
+		if (aln && !corpse.asciiLastName)
+			writer.putString("");
+		else
+			writer.putString(corpse.lastName);
+		writer.putInt(0);
+		writer.putInt(0);
+		writer.putInt(0);
+		writer.put((byte)1);
+
+		//Send Corpse Info
+		writer.putInt(0);
+		writer.putInt(corpse.getObjectType().ordinal());
+		writer.putInt((corpse.getObjectUUID()));
+		writer.putFloat(10f); //FaceDir or scale
+		writer.putFloat(10); //FaceDir or scale
+		writer.putFloat(10); //FaceDir or scale
+
+		writer.putFloat(corpse.getLoc().x);
+		writer.putFloat(corpse.getLoc().y);
+		writer.putFloat(corpse.getLoc().z);
+
+		writer.putFloat(6.235f); //1.548146f); //w
+		writer.putInt(0);
+		writer.putInt(0);
+
+		//Send BelongsToInfo
+		writer.putInt(((corpse.level / 10))); //Rank
+		writer.putInt(corpse.level); //Level
+		writer.putInt(1);
+		writer.putInt(1);
+		writer.putInt(1); //Missing this?
+		writer.putInt(2);
+		writer.putInt(1);
+		//		writer.putInt(0); //not needed?
+		writer.putInt(0);
+
+		writer.putInt(corpse.belongsToType);
+		writer.putInt(corpse.belongsToID);
+
+		writer.putInt(0);
+		writer.putInt(0);
+
+
+
+		for (int i=0;i<9;i++)
+			writer.putInt(0);
+		writer.putShort((short)0);
+		writer.put((byte)0);
+
+		//Send Errant Guild Info
+		for (int i=0;i<13;i++)
+			writer.putInt(0);
+		writer.putInt(16);
+		writer.putInt(16);
+		writer.putInt(16);
+		writer.putInt(0);
+		writer.putInt(0); //Missing this?
+		writer.putInt(16);
+		writer.putInt(16);
+		writer.putInt(16);
+		writer.putInt(0);
+		writer.putInt(0);
+		writer.putInt(0);
+
+		//Send unknown counter
+		writer.putInt(1);
+		writer.putInt(0x047A0E67); //What is this?
+		writer.put((byte)0);
+
+		//Send unknown
+		writer.putInt(0);
+		writer.putInt(0);
+		writer.putFloat(1293.4449f); //Unknown
+		writer.putFloat(-100f); //Unknown
+		writer.putInt(0);
+		writer.put((byte)0);
+
+		writer.put((byte)0); //End datablock
+
+	}
+
+
+	public boolean hasGold() {
+		return this.hasGold;
+	}
+
+	public void setHasGold(boolean value) {
+		this.hasGold = value;
+	}
+
+	public ArrayList<Item> getInventory() {
+		synchronized(this.inventory) {
+			return this.inventory;
+		}
+	}
+
+	/**
+	 * Delete and remove all items in the inventory
+	 */
+	private void purgeInventory() {
+		//make a copy so we're not inside synchronized{} while waiting for all items to be junked
+		ArrayList<Item> inventoryCopy;
+		synchronized(this.inventory) {
+			inventoryCopy = new ArrayList<>(this.inventory);
+			this.inventory.clear();
+		}
+
+		for (Item item : inventoryCopy) {
+			item.junk();
+		}
+	}
+
+	@Override
+	public void updateDatabase() {
+	}
+
+	@Override
+	public void runAfterLoad() {}
+
+	public int getBelongsToType() {
+		return this.belongsToType;
+	}
+
+	public int getBelongsToID() {
+		return this.belongsToID;
+	}
+
+	@Override
+	public String getName() {
+		if (this.firstName.length() == 0) {
+			return "Unknown corpse";
+		}
+		if (this.lastName.length() == 0) {
+			return this.firstName;
+		}
+		return this.firstName + ' ' + this.lastName;
+	}
+
+	public int getInBuilding() {
+		return inBuilding;
+	}
+
+	public void setInBuilding(int inBuilding) {
+		this.inBuilding = inBuilding;
+	}
+
+	public int getInFloorID() {
+		return inFloorID;
+	}
+
+	public void setInFloorID(int inFloorID) {
+		this.inFloorID = inFloorID;
+	}
+}
diff --git a/src/engine/objects/Effect.java b/src/engine/objects/Effect.java
new file mode 100644
index 00000000..85f73226
--- /dev/null
+++ b/src/engine/objects/Effect.java
@@ -0,0 +1,665 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+ package engine.objects;
+
+import engine.Enum.DamageType;
+import engine.Enum.EffectSourceType;
+import engine.gameManager.PowersManager;
+import engine.job.AbstractJob;
+import engine.job.AbstractScheduleJob;
+import engine.job.JobContainer;
+import engine.jobs.AbstractEffectJob;
+import engine.jobs.DamageOverTimeJob;
+import engine.jobs.NoTimeJob;
+import engine.jobs.PersistentAoeJob;
+import engine.net.ByteBufferWriter;
+import engine.net.client.ClientConnection;
+import engine.powers.ActionsBase;
+import engine.powers.EffectsBase;
+import engine.powers.PowersBase;
+import engine.powers.effectmodifiers.AbstractEffectModifier;
+
+import java.util.HashSet;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+
+public class Effect {
+
+	private JobContainer jc;
+
+	//Fail Conditions
+	private boolean cancelOnAttack;
+	private boolean cancelOnAttackSwing;
+	private boolean cancelOnCast;
+	private boolean cancelOnCastSpell;
+	private boolean cancelOnEquipChange;
+	private boolean cancelOnLogout;
+	private boolean cancelOnMove;
+	private boolean cancelOnNewCharm;
+	private boolean cancelOnSit;
+	private boolean cancelOnTakeDamage;
+	private boolean cancelOnTerritoryClaim;
+	private boolean cancelOnUnEquip;
+	private boolean cancelOnStun;
+	private boolean bakedInStat = false;
+	private boolean isStatic = false;
+	private int effectSourceType = 0;
+	private int effectSourceID = 0;
+	
+
+	private EffectsBase eb;
+	private int trains;
+	private float damageAmount = 0f;
+
+	private AtomicBoolean cancel = new AtomicBoolean(false);
+	
+	//private AbstractWorldObject owner;
+
+	/**
+	 * Generic Constructor
+	 */
+	public Effect(JobContainer jc, EffectsBase eb, int trains) {
+		this.jc = jc;
+		this.cancelOnAttack = false;
+		this.cancelOnAttackSwing = false;
+		this.cancelOnCast = false;
+		this.cancelOnCastSpell = false;
+		this.cancelOnEquipChange = false;
+		this.cancelOnLogout = false;
+		this.cancelOnMove = false;
+		this.cancelOnNewCharm = false;
+		this.cancelOnSit = false;
+		this.cancelOnTakeDamage = false;
+		this.cancelOnTerritoryClaim = false;
+		this.cancelOnUnEquip = false;
+		this.cancelOnStun = false;
+		this.eb = eb;
+		this.trains = trains;
+	}
+	
+	public Effect(JobContainer jc, EffectsBase eb, int trains,boolean isStatic) {
+		this.jc = jc;
+		this.cancelOnAttack = false;
+		this.cancelOnAttackSwing = false;
+		this.cancelOnCast = false;
+		this.cancelOnCastSpell = false;
+		this.cancelOnEquipChange = false;
+		this.cancelOnLogout = false;
+		this.cancelOnMove = false;
+		this.cancelOnNewCharm = false;
+		this.cancelOnSit = false;
+		this.cancelOnTakeDamage = false;
+		this.cancelOnTerritoryClaim = false;
+		this.cancelOnUnEquip = false;
+		this.cancelOnStun = false;
+		this.eb = eb;
+		this.trains = trains;
+		this.isStatic = isStatic;
+	}
+
+	//called when effect ends. Send message to client to remove effect
+	public void endEffect() {
+		if (this.jc != null) {
+			AbstractJob aj = jc.getJob();
+			if (aj == null)
+				return;
+			if (aj instanceof AbstractEffectJob) {
+				((AbstractEffectJob)aj).setSkipCancelEffect(false);
+				((AbstractEffectJob)aj).endEffect();
+			}
+		}
+	}
+	
+	public void endEffectNoPower() {
+		if (this.jc != null) {
+			AbstractJob aj = jc.getJob();
+			if (aj == null)
+				return;
+			if (aj instanceof AbstractEffectJob) {
+				((AbstractEffectJob)aj).setSkipCancelEffect(false);
+				((AbstractEffectJob)aj).endEffectNoPower();
+			}
+		}
+	}
+
+	//Called when effect ends before timer done
+	public void cancelJob() {
+		if (this.jc != null) {
+			AbstractJob aj = jc.getJob();
+			if (aj == null)
+				return;
+			if (aj instanceof AbstractEffectJob)
+				((AbstractEffectJob)aj).setSkipCancelEffect(false);
+			if (aj instanceof AbstractScheduleJob) {
+				((AbstractScheduleJob)aj).cancelJob();
+			}
+		}
+	}
+
+	public void cancelJob(boolean skipEffect) {
+		if (this.jc != null) {
+			AbstractJob aj = jc.getJob();
+			if (aj == null)
+				return;
+			if (skipEffect && aj instanceof AbstractEffectJob) {
+				((AbstractEffectJob)aj).setSkipCancelEffect(skipEffect);
+			}
+			if (aj instanceof AbstractScheduleJob) {
+				((AbstractScheduleJob)aj).cancelJob();
+			}
+		}
+	}
+
+	public boolean applyBonus(Item item) {
+		if (this.jc == null)
+			return false;
+		AbstractJob aj = jc.getJob();
+		if (aj == null)
+			return false;
+		if (aj instanceof AbstractEffectJob) {
+			AbstractEffectJob aej = (AbstractEffectJob)aj;
+			EffectsBase eb = aej.getEffect();
+			if (eb == null)
+				return false;
+			HashSet<AbstractEffectModifier> aems = eb.getModifiers();
+			for(AbstractEffectModifier aem : aems)
+				aem.applyBonus(item, aej.getTrains());
+			return true;
+		}
+		return false;
+	}
+
+	public boolean applyBonus(Building building) {
+		if (this.jc == null)
+			return false;
+		AbstractJob aj = jc.getJob();
+		if (aj == null)
+			return false;
+		if (aj instanceof AbstractEffectJob) {
+			AbstractEffectJob aej = (AbstractEffectJob)aj;
+			EffectsBase eb = aej.getEffect();
+			if (eb == null)
+				return false;
+			HashSet<AbstractEffectModifier> aems = eb.getModifiers();
+			for(AbstractEffectModifier aem : aems)
+				aem.applyBonus(building, aej.getTrains());
+			return true;
+		}
+		return false;
+	}
+
+	public boolean applyBonus(AbstractCharacter ac) {
+		if (this.jc == null)
+			return false;
+		AbstractJob aj = jc.getJob();
+		if (aj == null)
+			return false;
+		if (aj instanceof AbstractEffectJob) {
+			AbstractEffectJob aej = (AbstractEffectJob)aj;
+			EffectsBase eb = aej.getEffect();
+			if (eb == null)
+				return false;
+			HashSet<AbstractEffectModifier> aems = eb.getModifiers();
+			for(AbstractEffectModifier aem : aems)
+				aem.applyBonus(ac, aej.getTrains());
+			return true;
+		}
+		return false;
+	}
+
+	public boolean applyBonus(Item item, AbstractCharacter ac) {
+		if (this.jc == null)
+			return false;
+		AbstractJob aj = jc.getJob();
+		if (aj == null)
+			return false;
+		if (aj instanceof AbstractEffectJob) {
+			AbstractEffectJob aej = (AbstractEffectJob)aj;
+			EffectsBase eb = aej.getEffect();
+			if (eb == null)
+				return false;
+			HashSet<AbstractEffectModifier> aems = eb.getModifiers();
+			for(AbstractEffectModifier aem : aems) {
+				aem.applyBonus(item, aej.getTrains());
+				aem.applyBonus(ac, aej.getTrains());
+			}
+			return true;
+		}
+		return false;
+	}
+
+	public HashSet<AbstractEffectModifier> getEffectModifiers() {
+		if (this.jc == null)
+			return null;
+		AbstractJob aj = jc.getJob();
+		if (aj == null)
+			return null;
+		if (aj instanceof AbstractEffectJob) {
+			AbstractEffectJob aej = (AbstractEffectJob)aj;
+			EffectsBase eb = aej.getEffect();
+			if (eb == null)
+				return null;
+			return eb.getModifiers();
+		}
+		return null;
+	}
+
+
+	//Send this effect to a client when loading a player
+	public void sendEffect(ClientConnection cc) {
+		if (this.jc == null || this.eb == null || cc == null)
+			return;
+		AbstractJob aj = this.jc.getJob();
+		if (aj == null || (!(aj instanceof AbstractEffectJob)))
+			return;
+		this.eb.sendEffect((AbstractEffectJob)aj, (this.jc.timeToExecutionLeft() / 1000), cc);
+	}
+	
+	public void sendEffectNoPower(ClientConnection cc) {
+		if (this.jc == null || this.eb == null || cc == null)
+			return;
+		AbstractJob aj = this.jc.getJob();
+		if (aj == null || (!(aj instanceof AbstractEffectJob)))
+			return;
+		this.eb.sendEffectNoPower((AbstractEffectJob)aj, (this.jc.timeToExecutionLeft() / 1000), cc);
+	}
+	
+	public void sendSpireEffect(ClientConnection cc, boolean onEnter) {
+		if (this.jc == null || this.eb == null || cc == null)
+			return;
+		AbstractJob aj = this.jc.getJob();
+		if (aj == null || (!(aj instanceof AbstractEffectJob)))
+			return;
+		int duration = 45;
+		if (onEnter)
+			duration = -1;
+		this.eb.sendEffectNoPower((AbstractEffectJob)aj, duration, cc);
+	}
+
+	public void serializeForItem(ByteBufferWriter writer, Item item) {
+		if (this.jc == null) {
+			blankFill(writer);
+			return;
+		}
+		AbstractJob aj = this.jc.getJob();
+		if (aj == null || (!(aj instanceof AbstractEffectJob))) {
+			blankFill(writer);
+			return;
+		}
+		AbstractEffectJob aej = (AbstractEffectJob)aj;
+		PowersBase pb = aej.getPower();
+		ActionsBase ab = aej.getAction();
+		if (this.eb == null) {
+			blankFill(writer);
+			return;
+		} else if (pb == null && !(this.jc.noTimer())) {
+			blankFill(writer);
+			return;
+		}
+		if (this.jc.noTimer()) {
+			if (pb == null)
+				writer.putInt(this.eb.getToken());
+			else
+				writer.putInt(pb.getToken());
+			writer.putInt(aej.getTrains());
+			writer.putInt(1);
+			writer.put((byte)1);
+			writer.putInt(item.getObjectType().ordinal());
+			writer.putInt(item.getObjectUUID());
+			
+			writer.putString(item.getName());
+			writer.putFloat(-1000f);
+		} else {
+			float duration = this.jc.timeToExecutionLeft() / 1000;
+			writer.putInt(this.eb.getToken());
+			writer.putInt(aej.getTrains());
+			writer.putInt(0);
+			writer.put((byte)0);
+			writer.putInt(pb.getToken());
+			writer.putString(pb.getName());
+			writer.putFloat(duration);
+		}
+	}
+
+	public void serializeForClientMsg(ByteBufferWriter writer) {
+		AbstractJob aj = this.jc.getJob();
+		if (aj == null || (!(aj instanceof AbstractEffectJob))) {
+			//TODO put error message here
+			blankFill(writer);
+			return;
+		}
+		AbstractEffectJob aej = (AbstractEffectJob)aj;
+		PowersBase pb = aej.getPower();
+		ActionsBase ab = aej.getAction();
+		if (ab == null || pb == null || this.eb == null) {
+			//TODO put error message here
+			blankFill(writer);
+			return;
+		}
+		
+		if ( aej instanceof PersistentAoeJob){
+			blankFill(writer);
+			return;
+		}
+
+		float duration = this.jc.timeToExecutionLeft() / 1000;
+		if (aej instanceof DamageOverTimeJob)
+			duration = ab.getDurationInSeconds(aej.getTrains()) - (((DamageOverTimeJob)aej).getIteration()*5);
+		
+		
+
+		writer.putInt(pb.getToken());
+		writer.putInt(aej.getTrains());
+		writer.putInt(0);
+		writer.put((byte)0);
+		writer.putInt(this.eb.getToken());
+		writer.putString(pb.getName());
+		writer.putFloat(duration);
+	}
+	
+	public boolean serializeForLoad(ByteBufferWriter writer) {
+		AbstractJob aj = this.jc.getJob();
+		if (aj == null || (!(aj instanceof AbstractEffectJob))) {
+			return false;
+		}
+		
+		
+		AbstractEffectJob aej = (AbstractEffectJob)aj;
+		PowersBase pb = aej.getPower();
+		ActionsBase ab = aej.getAction();
+		if (this.eb == null) {
+			return false;
+		}
+		if ( aej instanceof PersistentAoeJob){
+			return false;
+		}
+	
+
+		float duration = this.jc.timeToExecutionLeft() / 1000;
+		if (aej instanceof DamageOverTimeJob)
+			if (ab != null)
+			duration = ab.getDurationInSeconds(aej.getTrains()) - (((DamageOverTimeJob)aej).getIteration()*5);
+		if (aej instanceof NoTimeJob)
+			duration = -1;
+		
+		int sendToken = this.getEffectToken();
+		
+		if (aej.getAction() != null)
+		if ( aej.getAction().getPowerAction() != null
+				&& PowersManager.ActionTokenByIDString.containsKey(aej.getAction().getPowerAction().getIDString()))
+			try{
+				sendToken = PowersManager.ActionTokenByIDString.get(aej.getAction().getPowerAction().getIDString());
+			}catch(Exception e){
+				sendToken = this.getEffectToken();
+			}
+		
+
+		writer.putInt(sendToken);
+        writer.putInt(this.trains);
+		writer.putInt(0); //?
+		if (aej.getEffectSourceID() != 0){
+			writer.put((byte) 1);
+			writer.putInt(aej.getEffectSourceType());
+			writer.putInt(aej.getEffectSourceID());
+		}else{
+			writer.put((byte)0);
+			writer.putInt(pb != null ? pb.getToken() : 0);
+		}
+		writer.putString(pb != null ? pb.getName() : eb.getName());
+
+		writer.putFloat(duration);
+        return true;
+	}
+
+	private static void blankFill(ByteBufferWriter writer) {
+		writer.putInt(0);
+		writer.putInt(0);
+		writer.putInt(0);
+		writer.put((byte)0);
+		writer.putInt(0);
+		writer.putInt(0);
+		writer.putInt(0);
+	}
+
+	public float getDuration() {
+		float duration = 0f;
+		if (this.jc != null)
+			duration = this.jc.timeToExecutionLeft() / 1000;
+		return duration;
+	}
+
+	public boolean containsSource(EffectSourceType source) {
+		if (this.eb != null)
+			return this.eb.containsSource(source);
+		return false;
+	}
+
+	public JobContainer getJobContainer() {
+		return this.jc;
+	}
+
+	public int getTrains() {
+		return this.trains;
+	}
+
+	public void setTrains(int value) {
+		this.trains = value;
+	}
+
+	public float getDamageAmount() {
+		return this.damageAmount;
+	}
+
+	public AbstractJob getJob() {
+		if (this.jc == null)
+			return null;
+		return jc.getJob();
+	}
+
+	public boolean bakedInStat() {
+		return this.bakedInStat;
+	}
+
+	public void setBakedInStat(boolean value) {
+		this.bakedInStat = value;
+	}
+
+	public PowersBase getPower() {
+		if (this.jc == null)
+			return null;
+		AbstractJob aj = jc.getJob();
+		if (aj == null || (!(aj instanceof AbstractEffectJob)))
+			return null;
+		return ((AbstractEffectJob)aj).getPower();
+	}
+
+	public int getPowerToken() {
+		if (this.jc == null)
+			return 0;
+		AbstractJob aj = jc.getJob();
+		if (aj == null || (!(aj instanceof AbstractEffectJob)))
+			return 0;
+		PowersBase pb = ((AbstractEffectJob)aj).getPower();
+		if (pb == null)
+			return 0;
+		return pb.getToken();
+	}
+
+	public int getEffectToken() {
+		if (this.eb != null)
+			return this.eb.getToken();
+		return 0;
+	}
+
+	public EffectsBase getEffectsBase() {
+		return this.eb;
+	}
+
+	public String getName() {
+		if (this.jc == null)
+			return "";
+		AbstractJob aj = this.jc.getJob();
+		if (aj == null || !(aj instanceof AbstractEffectJob))
+			return "";
+		AbstractEffectJob aej = (AbstractEffectJob)aj;
+		PowersBase pb = aej.getPower();
+		if (pb == null)
+			return "";
+		return pb.getName();
+	}
+
+	public boolean cancel() {
+		return this.cancel.compareAndSet(false, true);
+	}
+
+	public boolean canceled() {
+		return this.cancel.get();
+	}
+
+	public boolean cancelOnAttack() {
+		if (this.eb == null)
+			return true;
+		return this.eb.cancelOnAttack();
+	}
+
+	public boolean cancelOnAttackSwing() {
+		if (this.eb == null)
+			return true;
+		return this.eb.cancelOnAttackSwing();
+	}
+
+	public boolean cancelOnCast() {
+		if (this.eb == null)
+			return true;
+		return this.eb.cancelOnCast();
+	}
+
+	public boolean cancelOnCastSpell() {
+		if (this.eb == null)
+			return true;
+		return this.eb.cancelOnCastSpell();
+	}
+
+	public boolean cancelOnEquipChange() {
+		if (this.eb == null)
+			return true;
+		return this.eb.cancelOnEquipChange();
+	}
+
+	public boolean cancelOnLogout() {
+		if (this.eb == null)
+			return true;
+		return this.eb.cancelOnLogout();
+	}
+
+	public boolean cancelOnMove() {
+		if (this.eb == null || this.cancelOnMove)
+			return true;
+		return this.eb.cancelOnMove();
+	}
+
+	public boolean cancelOnNewCharm() {
+		if (this.eb == null)
+			return true;
+		return this.eb.cancelOnNewCharm();
+	}
+
+	public boolean cancelOnSit() {
+		if (this.eb == null)
+			return true;
+		return this.eb.cancelOnSit();
+	}
+
+	public boolean cancelOnStun() {
+		if (this.eb == null)
+			return true;
+		return this.cancelOnStun;
+	}
+
+	public boolean cancelOnTakeDamage() {
+		if (this.eb == null)
+			return true;
+		if (this.eb.damageTypeSpecific()) {
+			return false; //handled in call from resists
+		} else {
+			return this.eb.cancelOnTakeDamage();
+		}
+	}
+
+	//Used for verifying when damage absorbers fails
+	public boolean cancelOnTakeDamage(DamageType type, float amount) {
+		if (!this.eb.cancelOnTakeDamage())
+			return false;
+		if (this.eb == null || amount < 0f)
+			return false;
+		if (this.eb.damageTypeSpecific()) {
+			if (type == null)
+				return false;
+			if (this.eb.containsDamageType(type)) {
+				this.damageAmount += amount;
+                return this.damageAmount > this.eb.getDamageAmount(this.trains);
+			} else
+				return false;
+		} else
+			return false; //handled by call from AbstractCharacter
+	}
+
+	public boolean isDamageAbsorber() {
+		if (this.eb == null)
+			return false;
+		if (!this.eb.cancelOnTakeDamage())
+			return false;
+        return this.eb.damageTypeSpecific();
+    }
+
+	public boolean cancelOnTerritoryClaim() {
+		if (this.eb == null)
+			return true;
+		return this.eb.cancelOnTerritoryClaim();
+	}
+
+	public boolean cancelOnUnEquip() {
+		if (this.eb == null)
+			return true;
+		return this.eb.cancelOnUnEquip();
+	}
+
+	public void setPAOE() {
+		this.cancelOnStun = true;
+		this.cancelOnMove = true;
+	}
+
+	public boolean isStatic() {
+		return isStatic;
+	}
+	
+	public void setIsStatic(boolean isStatic) {
+		this.isStatic = isStatic;
+		
+	}
+
+	public int getEffectSourceID() {
+		return effectSourceID;
+	}
+
+	public void setEffectSourceID(int effectSourceID) {
+		this.effectSourceID = effectSourceID;
+	}
+
+	public int getEffectSourceType() {
+		return effectSourceType;
+	}
+
+	public void setEffectSourceType(int effectSourceType) {
+		this.effectSourceType = effectSourceType;
+	}
+
+
+}
\ No newline at end of file
diff --git a/src/engine/objects/EffectsResourceCosts.java b/src/engine/objects/EffectsResourceCosts.java
new file mode 100644
index 00000000..c5cf7e51
--- /dev/null
+++ b/src/engine/objects/EffectsResourceCosts.java
@@ -0,0 +1,76 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.objects;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+
+public class EffectsResourceCosts extends AbstractGameObject {
+
+	private String IDString;
+	private int resourceID;
+	private int amount;
+	private int UID;
+
+	/**
+	 * No Table ID Constructor
+	 */
+	public EffectsResourceCosts() {
+
+	}
+
+	/**
+	 * ResultSet Constructor
+	 */
+	public EffectsResourceCosts(ResultSet rs) throws SQLException {
+
+		this.UID = rs.getInt("UID");
+		this.IDString = rs.getString("IDString");
+		this.resourceID = rs.getInt("resource");
+		this.amount = rs.getInt("amount");
+	}
+
+	
+
+	public String getIDString() {
+		return this.IDString;
+	}
+
+
+	
+	public int getAmount() {
+		return this.amount;
+	}
+
+	
+
+	public int getResourceID() {
+		return resourceID;
+	}
+
+	@Override
+	public void removeFromCache() {
+		// TODO Auto-generated method stub
+		
+	}
+
+	@Override
+	public void updateDatabase() {
+		// TODO Auto-generated method stub
+		
+	}
+
+	public int getUID() {
+		return UID;
+	}
+
+	
+}
diff --git a/src/engine/objects/EnchantmentBase.java b/src/engine/objects/EnchantmentBase.java
new file mode 100644
index 00000000..9ae2e406
--- /dev/null
+++ b/src/engine/objects/EnchantmentBase.java
@@ -0,0 +1,92 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+ package engine.objects;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+
+public class EnchantmentBase extends AbstractGameObject {
+
+	private final String name;
+	private final String prefix;
+	private final String suffix;
+
+	private final byte attributeID;
+	private final int modValue;
+
+	/**
+	 * No Table ID Constructor
+	 */
+	public EnchantmentBase(String name, String prefix, String suffix,
+			byte attributeID, int modValue) {
+		super();
+		this.name = name;
+		this.prefix = prefix;
+		this.suffix = suffix;
+		this.attributeID = attributeID;
+		this.modValue = modValue;
+	}
+
+	/**
+	 * Normal Constructor
+	 */
+	public EnchantmentBase(String name, String prefix, String suffix,
+			byte attributeID, int modValue, int newUUID) {
+		super(newUUID);
+		this.name = name;
+		this.prefix = prefix;
+		this.suffix = suffix;
+		this.attributeID = attributeID;
+		this.modValue = modValue;
+	}
+
+	/**
+	 * ResultSet Constructor
+	 */
+	public EnchantmentBase(ResultSet rs) throws SQLException {
+		super(rs);
+
+		this.name = rs.getString("name");
+		this.prefix = rs.getString("prefix");
+		this.suffix = rs.getString("suffix");
+		this.attributeID = rs.getByte("attributeID");
+		this.modValue = rs.getInt("modValue");
+
+	}
+
+	/*
+	 * Getters
+	 */
+	public String getName() {
+		return name;
+	}
+
+	public String getPrefix() {
+		return prefix;
+	}
+
+	public String getSuffix() {
+		return suffix;
+	}
+
+	public byte getAttributeID() {
+		return attributeID;
+	}
+
+	public int getModValue() {
+		return modValue;
+	}
+
+	@Override
+	public void updateDatabase() {
+		// TODO Create update logic.
+	}
+}
diff --git a/src/engine/objects/EquipmentSetEntry.java b/src/engine/objects/EquipmentSetEntry.java
new file mode 100644
index 00000000..b3bdf8a6
--- /dev/null
+++ b/src/engine/objects/EquipmentSetEntry.java
@@ -0,0 +1,47 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.objects;
+
+import engine.gameManager.DbManager;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.HashMap;
+
+public class EquipmentSetEntry {
+
+	private float dropChance;
+	private int itemID;
+
+	static HashMap<Integer, ArrayList<EquipmentSetEntry>> EquipmentSetMap = new HashMap<>();
+
+	/**
+	 * ResultSet Constructor
+	 */
+
+	public EquipmentSetEntry(ResultSet rs) throws SQLException {
+		this.dropChance = (rs.getFloat("dropChance"));
+		this.itemID = (rs.getInt("itemID"));
+	}
+
+	public static void LoadAllEquipmentSets() {
+		EquipmentSetMap = DbManager.ItemBaseQueries.LOAD_EQUIPMENT_FOR_NPC_AND_MOBS();
+	}
+
+	float getDropChance() {
+		return dropChance;
+	}
+
+	public int getItemID() {
+		return itemID;
+	}
+
+}
diff --git a/src/engine/objects/Experience.java b/src/engine/objects/Experience.java
new file mode 100644
index 00000000..c0ae278a
--- /dev/null
+++ b/src/engine/objects/Experience.java
@@ -0,0 +1,441 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.objects;
+
+import engine.Enum.TargetColor;
+import engine.gameManager.ZoneManager;
+import engine.math.Vector3fImmutable;
+import engine.server.MBServerStatics;
+
+import java.util.ArrayList;
+import java.util.TreeMap;
+
+public class Experience  {
+
+	private static final TreeMap<Integer, Integer> ExpToLevel;
+	private static final int[] LevelToExp = { Integer.MIN_VALUE, // Pad
+			// everything
+			// over 1
+
+			// R0
+			0, // Level 1
+			150, // Level 2
+			1200, // Level 3
+			4050, // Level 4
+			9600, // Level 5
+			18750, // Level 6
+			32400, // Level 7
+			51450, // Level 8
+			76800, // Level 9
+
+			// R1
+			109350, // Level 10
+			150000, // Level 11
+			199650, // Level 12
+			259200, // Level 13
+			329550, // Level 14
+			411600, // Level 15
+			506250, // Level 16
+			614400, // Level 17
+			736950, // Level 18
+			874800, // Level 19
+
+			// R2
+			1028850, // Level 20
+			1200000, // Level 21
+			1389150, // Level 22
+			1597200, // Level 23
+			1825050, // Level 24
+			2073600, // Level 25
+			2343750, // Level 26
+			2636400, // Level 27
+			2952450, // Level 28
+			3292800, // Level 29
+
+			// R3
+			3658350, // Level 30
+			4050000, // Level 31
+			4468650, // Level 32
+			4915200, // Level 33
+			5390550, // Level 34
+			5895600, // Level 35
+			6431250, // Level 36
+			6998400, // Level 37
+			7597950, // Level 38
+			8230800, // Level 39
+
+			// R4
+			8897850, // Level 40
+			10091520, // Level 41
+			11396777, // Level 42
+			12820187, // Level 43
+			14368505, // Level 44
+			16048666, // Level 45
+			17867790, // Level 46
+			19833183, // Level 47
+			21952335, // Level 48
+			24232919, // Level 49
+
+			// R5
+			26682793, // Level 50
+			29310000, // Level 51
+			32122766, // Level 52
+			35129502, // Level 53
+			38338805, // Level 54
+			41759452, // Level 55
+			45400409, // Level 56
+			49270824, // Level 57
+			53380030, // Level 58
+			57737542, // Level 59
+
+			// R6
+			62353064, // Level 60
+			67236479, // Level 61
+			72397859, // Level 62
+			77847457, // Level 63
+			83595712, // Level 64
+			89653247, // Level 65
+			96030869, // Level 66
+			102739569, // Level 67
+			109790524, // Level 68
+			117195093, // Level 69
+
+			// R7
+			124964822, // Level 70
+			133111438, // Level 71
+			141646855, // Level 72
+			150583171, // Level 73
+			159932666, // Level 74
+			169707808, // Level 75
+			179921247, // Level 76
+
+	};
+
+	private static final float[] MaxExpPerLevel = { Float.MIN_VALUE, // Pad
+			// everything
+			// over
+			// 1
+
+			// R0
+			15, // Level 1
+			105, // Level 2
+			285, // Level 3
+			555, // Level 4
+			610, // Level 5
+			682.5f, // Level 6
+			730, // Level 7
+			975, // Level 8
+			1251.92f, // Level 9
+
+			// R1
+			1563.46f, // Level 10
+			1909.62f, // Level 11
+			2290.38f, // Level 12
+			2705.77f, // Level 13
+			3155.77f, // Level 14
+			3640.38f, // Level 15
+			4159.62f, // Level 16
+			4713.46f, // Level 17
+			5301.92f, // Level 18
+			5925, // Level 19
+
+			// R2
+			6582.69f, // Level 20
+			7275, // Level 21
+			8001.92f, // Level 22
+			8763.46f, // Level 23
+			9559.62f, // Level 24
+			10390.38f, // Level 25
+			11255.77f, // Level 26
+			12155.77f, // Level 27
+			13090.38f, // Level 28
+			14059.62f, // Level 29
+
+			// R3
+			15063.46f, // Level 30
+			16101.92f, // Level 31
+			17175, // Level 32
+			18282.69f, // Level 33
+			19425, // Level 34
+			20601.92f, // Level 35
+			21813.46f, // Level 36
+			23059.62f, // Level 37
+			24340.38f, // Level 38
+			25655.77f, // Level 39
+
+			// R4
+			45910.38f, // Level 40
+			34348.87f, // Level 41
+			37458.16f, // Level 42
+			40745.21f, // Level 43
+			44214.76f, // Level 44
+			47871.68f, // Level 45
+			51720.87f, // Level 46
+			55767.16f, // Level 47
+			60015.37f, // Level 48
+			64470.37f, // Level 49
+
+			// R5
+			69137.03f, // Level 50
+			74020.16f, // Level 51
+			79124.63f, // Level 52
+			84455.34f, // Level 53
+			90017.03f, // Level 54
+			95814.66f, // Level 55
+			101853.03f, // Level 56
+			108137, // Level 57
+			114671.37f, // Level 58
+			121461.11f, // Level 59
+
+			// R6
+			128510.92f, // Level 60
+			135825.79f, // Level 61
+			143410.47f, // Level 62
+			151269.87f, // Level 63
+			159408.82f, // Level 64
+			167832.16f, // Level 65
+			176544.74f, // Level 66
+			185551.45f, // Level 67
+			194857.08f, // Level 68
+			204466.55f, // Level 69
+
+			// R7
+			214384.63f, // Level 70
+			224616.24f, // Level 71
+			235166.21f, // Level 72
+			246039.34f, // Level 73
+			257240.58f, // Level 74
+			1 // 268774.71 //Level 75
+
+	};
+
+	static {
+		ExpToLevel = new TreeMap<>();
+
+		// flip keys and values for other Map
+		for (int i = 1; i < LevelToExp.length; i++) {
+			ExpToLevel.put(LevelToExp[i], i);
+		}
+	} // end Static block
+
+	// Used to calcuate the amount of experience a monster grants in the
+	// following formula
+	// expGranted = a(moblevel)^2 + b(moblevel) + c
+	private static final float EXPQUADA = 10.0f;
+	private static final float EXPQUADB = 6.0f;
+	private static final float EXPQUADC = -10.0f;
+
+	// Adds addtional exp per addtional member of a group using the following
+	// (expGranted / group.size()) * (groupBonus * (group.size()-1) +1)
+	private static final float GROUP_BONUS = 0.5f; // 0.2 grants (20%) addtional
+	// exp per group member
+
+	// called to determine current level based on xp
+	public static int getLevel(int experience) {
+		int expKey = ExpToLevel.floorKey(experience);
+		int level = ExpToLevel.get(expKey);
+		if (level > MBServerStatics.LEVELCAP) {
+			level = MBServerStatics.LEVELCAP;
+		}
+		return level;
+	}
+
+	// Get the base xp for a level
+	public static int getBaseExperience(int level) {
+		if (level < LevelToExp.length) {
+			return LevelToExp[level];
+		}
+
+		int fLevel = level - 1;
+		int baseXP = fLevel * fLevel * fLevel;
+		return (int) ((fLevel < 40) ? (baseXP * 150)
+				: (baseXP * (150 + (7.6799998 * (level - 40)))));
+	}
+
+	// Get XP needed for the next level
+	public static int getExpForNextLevel(int experience, int currentLevel) {
+		return (getBaseExperience(currentLevel + 1) - experience);
+	}
+
+	// Max XP granted for killing a blue, yellow or orange mob
+	public static float maxXPPerKill(int level) {
+		if (level < 1)
+			level = 1;
+		if (level > 75)
+			level = 75;
+		return MaxExpPerLevel[level];
+		// return (LevelToExp[level + 1] - LevelToExp[level])/(11 + level/2);
+		// return ((((level * level)-level)*50)+16);
+	}
+
+	// Returns a penalty modifier depending on mob color
+	public static double getConMod(AbstractCharacter pc, AbstractCharacter mob) {
+		switch (TargetColor.getCon(pc, mob)) {
+		case Red:
+			return 1.25;
+		case Orange:
+			return 1.15;
+		case Yellow:
+			return 1.05;
+		case Blue:
+			return 1;
+		case Cyan:
+			return 0.8;
+		case Green:
+			return 0.5;
+		default:
+			return 0;
+		}
+	}
+
+	public static double getGroupMemberPenalty(double leadership,
+			PlayerCharacter currPlayer, ArrayList<PlayerCharacter> players,
+			int highestLevel) {
+
+		double penalty = 0.0;
+		int adjustedGroupSize = 0;
+		int totalLevels = 0;
+		int level = currPlayer.getLevel();
+
+		// Group Size Penalty
+		if (players.size() > 2)
+			penalty = (players.size() - 2) * 1.5;
+
+		// Calculate Penalty For Highest level -> Current Player difference, !=
+		// check to prevent divide by zero error
+		if (highestLevel != level)
+			penalty += ((highestLevel - level) * .5);
+
+		// double avgLevels = totalLevels / adjustedGroupSize;
+		// if (adjustedGroupSize >= 1)
+		// if (level < avgLevels)
+		// penalty += ((avgLevels - level) * .5);
+
+		// Extra noob penalty
+		if ((highestLevel - level) > 25)
+			penalty += (highestLevel - level - 25);
+
+		return penalty;
+	}
+
+	public static void doExperience(PlayerCharacter killer, AbstractCharacter mob, Group g) {
+		// Check for some failure conditions
+		if (killer == null || mob == null)
+			return;
+
+		double xp = 0.0;
+
+		//Get the xp modifier for the world
+		float xpMod = MBServerStatics.EXP_RATE_MOD;
+
+
+
+		if (g != null) { // Do group EXP stuff
+
+			int leadership = 0;
+			int highestLevel = 0;
+			double penalty = 0.0;
+
+			ArrayList<PlayerCharacter> giveEXPTo = new ArrayList<>();
+
+			// Check if leader is within range of kill and then get leadership
+			// skill
+			Vector3fImmutable killLoc = mob.getLoc();
+			if (killLoc.distanceSquared2D(g.getGroupLead().getLoc()) < (MBServerStatics.EXP_RANGE * MBServerStatics.EXP_RANGE)) {
+				CharacterSkill leaderskill = g.getGroupLead().skills
+						.get("Leadership");
+				if (leaderskill != null)
+					leadership = leaderskill.getNumTrains();
+				if (leadership > 90)
+					leadership = 90; // leadership caps at 90%
+			}
+
+			// Check every group member for distance to see if they get xp
+			for (PlayerCharacter pc : g.getMembers()) {
+				if (pc.isAlive()) { // Skip if the player is dead.
+
+					// Check within range
+					if (killLoc.distanceSquared2D(pc.getLoc()) < (MBServerStatics.EXP_RANGE * MBServerStatics.EXP_RANGE)) {
+						giveEXPTo.add(pc);
+						// Track highest level character
+						if (pc.getLevel() > highestLevel)
+							highestLevel = pc.getLevel();
+					}
+				}
+			}
+
+			// Process every player in the group getting XP
+			for (PlayerCharacter pc : giveEXPTo) {
+				if (pc.getLevel() >= MBServerStatics.LEVELCAP)
+					continue;
+
+				// Sets Max XP with server exp mod taken into account.
+				xp = (double) xpMod * maxXPPerKill(pc.getLevel());
+
+				// Adjust XP for Mob Level
+				xp *= getConMod(pc, mob);
+
+				// Process XP for this member
+				penalty = getGroupMemberPenalty(leadership, pc, giveEXPTo,
+						highestLevel);
+
+				// Leadership Penalty Reduction
+				if (leadership > 0)
+					penalty -= ((leadership) * 0.01) * penalty;
+
+				// Modify for hotzone
+				if (xp != 0)
+					if (ZoneManager.inHotZone(mob.getLoc()))
+						xp *= MBServerStatics.HOT_EXP_RATE_MOD;
+
+				// Check for 0 XP due to white mob, otherwise subtract penalty
+				// xp
+				if (xp == 0) {
+					xp = 1;
+				} else {
+					xp -= (penalty * 0.01) * xp;
+
+					// Errant Penalty Calculation
+					if (pc.getGuild().isErrant())
+						xp *= 0.6;
+				}
+
+				if (xp == 0)
+					xp = 1;
+
+				// Grant the player the EXP
+				pc.grantXP((int) Math.floor(xp));
+			}
+
+		} else { // Give EXP to a single character
+			if (!killer.isAlive()) // Skip if the player is dead.
+				return;
+
+			if (killer.getLevel() >= MBServerStatics.LEVELCAP)
+				return;
+
+			// Get XP and adjust for Mob Level with world xp modifier taken into account
+			xp = (double) xpMod * maxXPPerKill(killer.getLevel());
+			xp *= getConMod(killer, mob);
+
+			// Modify for hotzone
+			if (ZoneManager.inHotZone(mob.getLoc()))
+				xp *= MBServerStatics.HOT_EXP_RATE_MOD;
+
+			// Errant penalty
+			if (xp != 1)
+				if (killer.getGuild().isErrant())
+					xp *= .6;
+
+			// Grant XP
+			killer.grantXP((int) Math.floor(xp));
+		}
+	}
+}
diff --git a/src/engine/objects/Formation.java b/src/engine/objects/Formation.java
new file mode 100644
index 00000000..28d147a1
--- /dev/null
+++ b/src/engine/objects/Formation.java
@@ -0,0 +1,118 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.objects;
+
+import engine.math.Vector3f;
+
+public class Formation {
+
+	// Offsets are as follows.
+	// X determines left/right offset
+	// Y not used
+	// Z determines front/back offset
+
+	private static final Vector3f[] COLUMN = { new Vector3f(0, 0, 0), // Group
+																		// Lead
+			new Vector3f(6, 0, 0), // Player 1 offset
+			new Vector3f(0, 0, -6), // Player 2 offset
+			new Vector3f(6, 0, -6), // Player 3 offset
+			new Vector3f(0, 0, -12), // Player 4 offset
+			new Vector3f(6, 0, -12), // Player 5 offset
+			new Vector3f(0, 0, -18), // Player 6 offset
+			new Vector3f(6, 0, -18), // Player 7 offset
+			new Vector3f(0, 0, -24), // Player 8 offset
+			new Vector3f(6, 0, -24) }; // Player 9 offset
+
+	private static final Vector3f[] LINE = { new Vector3f(0, 0, 0),
+			new Vector3f(0, 0, -6), new Vector3f(0, 0, -12),
+			new Vector3f(0, 0, -18), new Vector3f(0, 0, -24),
+			new Vector3f(0, 0, -30), new Vector3f(0, 0, -36),
+			new Vector3f(0, 0, -42), new Vector3f(0, 0, -48),
+			new Vector3f(0, 0, -54) };
+
+	private static final Vector3f[] BOX = { new Vector3f(0, 0, 0),
+			new Vector3f(-6, 0, 0), new Vector3f(6, 0, 0),
+			new Vector3f(-6, 0, -6), new Vector3f(0, 0, -6),
+			new Vector3f(6, 0, -6), new Vector3f(-6, 0, -12),
+			new Vector3f(0, 0, -12), new Vector3f(5, 0, -12),
+			new Vector3f(0, 0, -18) };
+
+	private static final Vector3f[] TRIANGLE = { new Vector3f(0, 0, 0),
+			new Vector3f(-6, 0, -6), new Vector3f(6, 0, -6),
+			new Vector3f(-12, 0, -12), new Vector3f(0, 0, -12),
+			new Vector3f(12, 0, -12), new Vector3f(-18, 0, -18),
+			new Vector3f(-6, 0, -18), new Vector3f(6, 0, -18),
+			new Vector3f(18, 0, -18) };
+
+	private static final Vector3f[] CIRCLE = { new Vector3f(0, 0, 0),
+			new Vector3f(-12, 0, -3), new Vector3f(12, 0, -3),
+			new Vector3f(-18, 0, -12), new Vector3f(18, 0, -12),
+			new Vector3f(-18, 0, -21), new Vector3f(18, 0, -21),
+			new Vector3f(-12, 0, -30), new Vector3f(12, 0, -30),
+			new Vector3f(0, 0, -33) };
+
+	private static final Vector3f[] RANKS = { new Vector3f(0, 0, 0),
+			new Vector3f(0, 0, -6), new Vector3f(-6, 0, 0),
+			new Vector3f(-6, 0, -6), new Vector3f(6, 0, 0),
+			new Vector3f(6, 0, -6), new Vector3f(-12, 0, 0),
+			new Vector3f(-12, 0, -6), new Vector3f(12, 0, 0),
+			new Vector3f(12, 0, -6) };
+
+	private static final Vector3f[] WEDGE = { new Vector3f(0, 0, 0),
+			new Vector3f(6, 0, 0), new Vector3f(-6, 0, -6),
+			new Vector3f(12, 0, -6), new Vector3f(-12, 0, -12),
+			new Vector3f(18, 0, -12), new Vector3f(-18, 0, -18),
+			new Vector3f(24, 0, -18), new Vector3f(-24, 0, -24),
+			new Vector3f(30, 0, -24) };
+
+	private static final Vector3f[] INVERSEWEDGE = { new Vector3f(0, 0, 0),
+			new Vector3f(6, 0, 0), new Vector3f(-6, 0, 6),
+			new Vector3f(12, 0, 6), new Vector3f(-12, 0, 12),
+			new Vector3f(18, 0, 12), new Vector3f(-18, 0, 18),
+			new Vector3f(24, 0, 18), new Vector3f(-24, 0, 24),
+			new Vector3f(30, 0, 24) };
+
+	private static final Vector3f[] T = { new Vector3f(0, 0, 0),
+			new Vector3f(-6, 0, 0), new Vector3f(6, 0, 0),
+			new Vector3f(0, 0, -6), new Vector3f(-12, 0, 0),
+			new Vector3f(12, 0, 0), new Vector3f(0, 0, -12),
+			new Vector3f(-18, 0, 0), new Vector3f(18, 0, 0),
+			new Vector3f(0, 0, -18) };
+
+	public static Vector3f getOffset(int formation, int position) {
+		if (position > 9 || position < 0) {
+			// TODO log error here
+			position = 0;
+		}
+
+		switch (formation) {
+		case 0:
+			return Formation.COLUMN[position];
+		case 1:
+			return Formation.LINE[position];
+		case 2:
+			return Formation.BOX[position];
+		case 3:
+			return Formation.TRIANGLE[position];
+		case 4:
+			return Formation.CIRCLE[position];
+		case 5:
+			return Formation.RANKS[position];
+		case 6:
+			return Formation.WEDGE[position];
+		case 7:
+			return Formation.INVERSEWEDGE[position];
+		case 9:
+			return Formation.T[position];
+		default: // default to box
+			return Formation.BOX[position];
+		}
+	}
+}
diff --git a/src/engine/objects/Group.java b/src/engine/objects/Group.java
new file mode 100644
index 00000000..934deb78
--- /dev/null
+++ b/src/engine/objects/Group.java
@@ -0,0 +1,182 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.objects;
+
+import engine.gameManager.GroupManager;
+import engine.job.JobScheduler;
+import engine.jobs.UpdateGroupJob;
+import engine.net.Dispatch;
+import engine.net.DispatchMessage;
+import engine.net.client.msg.group.GroupUpdateMsg;
+import engine.server.MBServerStatics;
+
+import java.util.Collections;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+
+public class Group extends AbstractWorldObject {
+
+	private PlayerCharacter groupLead;
+    public final Set<PlayerCharacter> members;
+
+	private boolean splitGold = true;
+	private int formation = 2;
+
+	private UpdateGroupJob updateGroupJob = null;
+
+	/**
+	 * No Id Constructor
+	 */
+	public Group( PlayerCharacter pc) {
+		super();
+		this.groupLead = pc;
+		this.members = Collections.newSetFromMap(new ConcurrentHashMap<>());
+	}
+
+	/**
+	 * Normal Constructor
+	 */
+	public Group( PlayerCharacter pc, int newUUID) {
+		super(newUUID);
+		this.groupLead = pc;
+        this.members = Collections.newSetFromMap(new ConcurrentHashMap<>());
+	}
+
+	/*
+	 * Getters
+	 */
+	public PlayerCharacter getGroupLead() {
+		return this.groupLead;
+	}
+
+	public Set<PlayerCharacter> getMembers() {
+		return this.members;
+	}
+
+	public boolean getSplitGold() {
+		return this.splitGold;
+	}
+
+	public int getFormation() {
+		return this.formation;
+	}
+
+	public String getFormationName() {
+		return MBServerStatics.FORMATION_NAMES[this.formation];
+	}
+
+	/*
+	 * Setters
+	 */
+	public void setFormation(int value) {
+		if (value < 0 || value > 8)
+			value = 2; // Default Box
+		this.formation = value;
+	}
+
+	public boolean setGroupLead(int ID) {
+		for (PlayerCharacter pc : this.members) {
+			if (pc.getObjectUUID() == ID) {
+				this.groupLead = pc;
+				return true;
+			}
+		}
+		return false;
+	}
+
+	public void setSplitGold(boolean value) {
+		this.splitGold = value;
+	}
+
+	/*
+	 * Utils
+	 */
+	public boolean isGroupLead(int ID) {
+		return (this.groupLead.getObjectUUID() == ID);
+	}
+
+	public boolean isGroupLead(PlayerCharacter pc) {
+		if (pc == null || this.groupLead == null)
+			return false;
+		return (this.groupLead.getObjectUUID() == pc.getObjectUUID());
+	}
+
+	public boolean toggleSplitGold() {
+        this.splitGold = this.splitGold == false;
+		return this.splitGold;
+	}
+
+	public void sendUpdate(GroupUpdateMsg msg) {
+
+        for (PlayerCharacter pc : this.members) {
+            Dispatch dispatch = Dispatch.borrow(pc, msg);
+            DispatchMessage.dispatchMsgDispatch(dispatch, engine.Enum.DispatchChannel.SECONDARY);
+		}
+	}
+
+	public boolean addGroupMember(PlayerCharacter pc) {
+
+		if (this.members.size() > 9) // group full
+			return false;
+
+		if (this.members.contains(pc)) // Can't add player twice
+			return false;
+
+		this.members.add(pc);
+		return true;
+	}
+
+	public int removeGroupMember(PlayerCharacter pc) {
+
+		this.members.remove(pc); // remove player
+		return this.members.size();
+	}
+
+	public void clearMembers() {
+		this.members.clear();
+	}
+
+	public static boolean sameGroup(PlayerCharacter a, PlayerCharacter b) {
+
+		if (a == null || b == null)
+			return false;
+
+		Group aG = GroupManager.getGroup(a);
+		Group bG = GroupManager.getGroup(b);
+
+		if (aG == null || bG == null)
+			return false;
+
+        return aG.getObjectUUID() == bG.getObjectUUID();
+
+    }
+
+	public void addUpdateGroupJob() {
+		this.updateGroupJob = new UpdateGroupJob(this);
+		JobScheduler.getInstance().scheduleJob(this.updateGroupJob, MBServerStatics.UPDATE_GROUP_RATE);
+	}
+
+	public void removeUpdateGroupJob() {
+		this.updateGroupJob.cancelJob();
+		this.updateGroupJob = null;
+	}
+
+	/*
+	 * Database
+	 */
+	@Override
+	public void updateDatabase() {
+		// TODO Create update logic.
+	}
+
+	@Override
+	public void runAfterLoad() {}
+}
diff --git a/src/engine/objects/Guild.java b/src/engine/objects/Guild.java
new file mode 100644
index 00000000..e28009af
--- /dev/null
+++ b/src/engine/objects/Guild.java
@@ -0,0 +1,1297 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+
+
+
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.objects;
+
+import engine.Enum;
+import engine.Enum.*;
+import engine.db.archive.DataWarehouse;
+import engine.db.archive.GuildRecord;
+import engine.db.handlers.dbGuildHandler;
+import engine.gameManager.*;
+import engine.net.ByteBufferWriter;
+import engine.net.Dispatch;
+import engine.net.DispatchMessage;
+import engine.net.client.msg.AllianceChangeMsg;
+import engine.net.client.msg.ErrorPopupMsg;
+import engine.net.client.msg.UpdateClientAlliancesMsg;
+import engine.net.client.msg.guild.GuildInfoMsg;
+import engine.server.MBServerStatics;
+import org.joda.time.DateTime;
+import org.pmw.tinylog.Logger;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class Guild extends AbstractWorldObject {
+
+	private final String name;
+	private Guild nation;
+	private static Guild g;
+	private final GuildTag guildTag;
+	// TODO add these to database
+	private String motto = "";
+	private String motd = "";
+	private String icmotd = "";
+	private String nmotd = "";
+	private int guildLeaderUUID;
+	private int realmsOwned;
+	private final int charter;
+	private int cityUUID = 0;
+	private final String leadershipType; // Have to see how this is sent to the client
+	private final int repledgeMin;
+	private final int repledgeMax;
+	private final int repledgeKick;
+	private final int teleportMin;
+	private final int teleportMax;
+	private int mineTime;
+	private  ArrayList<PlayerCharacter> banishList;
+	private  ArrayList<PlayerCharacter> characterKOSList;
+	private  ArrayList<Guild> guildKOSList;
+	private  ArrayList<Guild> allyList = new ArrayList<>();
+	private  ArrayList<Guild> enemyList = new ArrayList<>();
+	private ArrayList<Guild> recommendList = new ArrayList<>();
+	private ArrayList<Guild> subGuildList;
+	private int nationUUID = 0;
+	private GuildState guildState = GuildState.Errant;
+	private ConcurrentHashMap<Integer,Condemned> guildCondemned = new ConcurrentHashMap<>();
+	private String hash;
+	private boolean ownerIsNPC;
+
+	public HashMap<Integer,GuildAlliances> guildAlliances = new HashMap<>();
+
+	/**
+	 * No Id Constructor
+	 */
+	public Guild(String name, Guild nat, int charter,
+			String leadershipType, GuildTag gt, String motto) {
+		super();
+		this.name = name;
+		this.nation = nat;
+		this.charter = charter;
+		this.realmsOwned = 0;
+		this.leadershipType = leadershipType;
+
+		this.banishList = new ArrayList<>();
+		this.characterKOSList = new ArrayList<>();
+		this.guildKOSList = new ArrayList<>();
+		this.allyList = new ArrayList<>();
+		this.enemyList = new ArrayList<>();
+		this.subGuildList = new ArrayList<>();
+
+		this.guildTag = gt;
+
+		//set for player city
+		this.repledgeMin = 1;
+		this.repledgeMax = 100;
+		this.repledgeKick = 100;
+		this.teleportMin = 1;
+		this.teleportMax = 100;
+		this.mineTime = 0;
+		this.motto = motto;
+	}
+
+	/**
+	 * Normal Constructor
+	 */
+	public Guild( String name, Guild nat, int charter,
+			String leadershipType, GuildTag gt, int newUUID) {
+		super(newUUID);
+		this.name = name;
+		this.nation = nat;
+
+		this.charter = charter;
+		this.realmsOwned = 0;
+		this.leadershipType = leadershipType;
+
+		this.banishList = new ArrayList<>();
+		this.characterKOSList = new ArrayList<>();
+		this.guildKOSList = new ArrayList<>();
+		this.allyList = new ArrayList<>();
+		this.enemyList = new ArrayList<>();
+		this.subGuildList = new ArrayList<>();
+		this.guildTag = gt;
+
+		//set for player city
+		this.repledgeMin = 1;
+		this.repledgeMax = 100;
+		this.repledgeKick = 100;
+		this.teleportMin = 1;
+		this.teleportMax = 100;
+		this.mineTime = 0;
+		this.hash = "ERRANT";
+	}
+
+	/**
+	 * ResultSet Constructor
+	 */
+	public Guild(ResultSet rs) throws SQLException {
+		super(rs);
+		DbObjectType objectType;
+
+		this.name = rs.getString("name");
+		this.charter = rs.getInt("charter");
+		this.leadershipType = rs.getString("leadershipType");
+
+		this.guildTag = new GuildTag(rs.getInt("backgroundColor01"),
+				rs.getInt("backgroundColor02"),
+				rs.getInt("symbolColor"),
+				rs.getInt("symbol"),
+				rs.getInt("backgroundDesign"));
+
+		//Declare Nations and Subguilds
+		this.nationUUID = rs.getInt("parent");
+		this.cityUUID = rs.getInt("ownedCity");
+		this.guildLeaderUUID = rs.getInt("leaderUID");
+		this.motto = rs.getString("motto");
+		this.motd = rs.getString("motd");
+		this.icmotd = rs.getString("icMotd");
+		this.nmotd = rs.getString("nationMotd");
+
+		this.repledgeMin = rs.getInt("repledgeMin");
+		this.repledgeMax = rs.getInt("repledgeMax");
+		this.repledgeKick = rs.getInt("repledgeKick");
+		this.teleportMin = rs.getInt("teleportMin");
+		this.teleportMax = rs.getInt("teleportMax");
+
+		this.mineTime = rs.getInt("mineTime");
+		this.hash = rs.getString("hash");
+
+	}
+
+	public void setNation(Guild nation) {
+		if (nation == null)
+			this.nation = Guild.getErrantGuild();
+		else
+		this.nation = nation;
+	}
+
+	/*
+	 * Getters
+	 */
+	@Override
+	public String getName() {
+		return name;
+	}
+
+	public String getLeadershipType() {
+		return leadershipType;
+	}
+
+	public Guild getNation() {
+		
+		if (this.nation == null)
+			return Guild.getErrantGuild();
+		return this.nation;
+	}
+
+	public boolean isNation() {
+        return this.nation != null && this.cityUUID != 0 && this.nation == this;
+    }
+
+	public City getOwnedCity() {
+
+		return City.getCity(this.cityUUID);
+	}
+
+	public void setCityUUID(int cityUUID) {
+		this.cityUUID = cityUUID;
+	}
+
+	public ArrayList<PlayerCharacter> getBanishList() {
+		if (banishList == null)
+			return new ArrayList<>();
+		return banishList;
+	}
+
+	public ArrayList<PlayerCharacter> getCharacterKOSList() {
+		return characterKOSList;
+	}
+
+	public ArrayList<Guild> getGuildKOSList() {
+		return guildKOSList;
+	}
+
+	public ArrayList<Guild> getAllyList() {
+		return allyList;
+	}
+
+	public ArrayList<Guild> getEnemyList() {
+		return enemyList;
+	}
+
+	public ArrayList<Guild> getSubGuildList() {
+
+		return subGuildList;
+	}
+
+	public GuildTag getGuildTag() {
+		return this.guildTag;
+	}
+
+	public int getCharter() {
+		return charter;
+	}
+
+	public int getGuildLeaderUUID() {
+		return this.guildLeaderUUID;
+	}
+
+	public static AbstractCharacter GetGL(Guild guild) {
+		if (guild == null)
+			return null;
+
+		if (guild.guildLeaderUUID == 0)
+			return null;
+
+		if (guild.ownerIsNPC)
+			return NPC.getFromCache(guild.guildLeaderUUID);
+
+		return PlayerCharacter.getFromCache(guild.guildLeaderUUID);
+	}
+
+	public String getMOTD() {
+		return this.motd;
+	}
+
+	public String getICMOTD() {
+		return this.icmotd;
+	}
+
+	public boolean isNPCGuild() {
+
+		return this.ownerIsNPC;
+	}
+
+	public int getRepledgeMin() {
+		return this.repledgeMin;
+	}
+
+	public int getRepledgeMax() {
+		return this.repledgeMax;
+	}
+
+	public int getRepledgeKick() {
+		return this.repledgeKick;
+	}
+
+	public int getTeleportMin() {
+		return this.teleportMin;
+	}
+
+	public int getTeleportMax() {
+		return this.teleportMax;
+	}
+
+	public int getMineTime() {
+		return this.mineTime;
+	}
+
+	/*
+	 * Setters
+	 */
+	public void setGuildLeaderUUID(int value) {
+		this.guildLeaderUUID = value;
+	}
+
+	public boolean setGuildLeader(AbstractCharacter ac) {
+		if (ac == null)
+			return false;
+		// errant guilds cant be guild leader.
+		if (this.isErrant())
+			return false;
+		
+		if (!DbManager.GuildQueries.SET_GUILD_LEADER(ac.getObjectUUID(), this.getObjectUUID())){
+			if (ac.getObjectType().equals(GameObjectType.PlayerCharacter))
+			ChatManager.chatGuildError((PlayerCharacter)ac, "Failed to change guild leader!");
+			return false;
+		}
+		
+		PlayerCharacter oldGuildLeader = PlayerCharacter.getFromCache(this.guildLeaderUUID);
+		
+		//old guildLeader no longer has guildLeadership stauts.
+		if (oldGuildLeader != null)
+			oldGuildLeader.setGuildLeader(false);
+		
+		if (ac.getObjectType().equals(GameObjectType.PlayerCharacter))
+			((PlayerCharacter)ac).setGuildLeader(true);
+		this.guildLeaderUUID = ac.getObjectUUID();
+		
+		return true;
+	}
+	
+	public boolean setGuildLeaderForCreate(AbstractCharacter ac) {
+		if (ac == null)
+			return false;
+		// errant guilds cant be guild leader.
+		if (this.isErrant())
+			return false;
+		
+		if (ac.getObjectType().equals(GameObjectType.PlayerCharacter))
+			((PlayerCharacter)ac).setGuildLeader(true);
+		this.guildLeaderUUID = ac.getObjectUUID();
+		
+		return true;
+	}
+
+	public void setMOTD(String value) {
+		this.motd = value;
+	}
+
+	public void setICMOTD(String value) {
+		this.icmotd = value;
+	}
+
+	public int getBgc1() {
+		if (this.guildTag != null)
+			return this.guildTag.backgroundColor01;
+		return 16;
+	}
+
+	public int getBgc2() {
+		if (this.guildTag != null)
+			return this.guildTag.backgroundColor02;
+		else
+			return 16;
+	}
+
+	public int getBgDesign() {
+		if (this.guildTag != null)
+			return this.guildTag.backgroundDesign;
+		return 0;
+	}
+
+	public int getSc() {
+		if (this.guildTag != null)
+			return this.guildTag.symbolColor;
+		return 16;
+	}
+
+	public int getSymbol() {
+		if (this.guildTag != null)
+			return this.guildTag.symbol;
+		return 0;
+	}
+
+	/*
+	 * Utils
+	 */
+	
+
+	public static Guild getErrantGuild() {
+		return g;
+	}
+	
+	public static void CreateErrantGuild(){
+		
+			g = new Guild( "None", Guild.getErrantNation(), 0,
+					"Anarchy", GuildTag.ERRANT, 0);
+			g.getObjectType();
+		
+	}
+
+	public boolean isErrant() {
+		if (this.getObjectUUID() == Guild.g.getObjectUUID())
+			return true;
+        return this.getObjectUUID() == Guild.errant.getObjectUUID();
+    }
+
+
+
+	public static boolean sameGuild(Guild a, Guild b) {
+		if (a == null || b == null)
+			return false;
+        return a.getObjectUUID() == b.getObjectUUID();
+    }
+
+	public static boolean sameGuildExcludeErrant(Guild a, Guild b) {
+		if (a == null || b == null)
+			return false;
+		if (a.isErrant() || b.isErrant())
+			return false;
+        return a.getObjectUUID() == b.getObjectUUID();
+    }
+
+	public static boolean sameGuildIncludeErrant(Guild a, Guild b) {
+		if (a == null || b == null)
+			return false;
+		if (a.isErrant() || b.isErrant())
+			return true;
+        return a.getObjectUUID() == b.getObjectUUID();
+    }
+
+	public static boolean sameNation(Guild a, Guild b) {
+		if (a == null || b == null)
+			return false;
+		if (a.getObjectUUID() == b.getObjectUUID())
+			return true;
+        if (a.nation == null || b.nation == null)
+			return false;
+        return a.nation.getObjectUUID() == b.nation.getObjectUUID();
+    }
+
+	public static boolean sameNationExcludeErrant(Guild a, Guild b) {
+		if (a == null || b == null)
+			return false;
+		if (a.getObjectUUID() == b.getObjectUUID())
+			return true;
+        if (a.nation == null || b.nation == null)
+			return false;
+        return a.nation.getObjectUUID() == b.nation.getObjectUUID() && !a.nation.isErrant();
+    }
+
+	public boolean isGuildLeader(int uuid) {
+
+        return (this.guildLeaderUUID == uuid);
+	}
+
+	public static boolean isTaxCollector(int uuid) {
+		//TODO add the handling for this later
+		return false;
+	}
+
+	/**
+	 * Removes a PlayerCharacter from this (non-Errant) Guild.
+	 *
+	 * @param pc PlayerCharacter to be removed
+	 */
+	public void removePlayer(PlayerCharacter pc, GuildHistoryType historyType) {
+
+		if (this.isErrant()) {
+			Logger.warn( "Attempted to remove a PlayerCharacter (" + pc.getObjectUUID() + ") from an errant guild.");
+			return;
+		}
+
+		//Add to Guild History
+		if (pc.getGuild() != null){
+			if (DbManager.GuildQueries.ADD_TO_GUILDHISTORY(pc.getGuildUUID(), pc, DateTime.now(), historyType)){
+                GuildHistory guildHistory = new GuildHistory(pc.getGuildUUID(), pc.getGuild().name,DateTime.now(), historyType) ;
+				pc.getGuildHistory().add(guildHistory);
+			}
+		}
+
+		// Clear Guild Ranks
+		pc.resetGuildStatuses();
+		pc.setGuild(Guild.getErrantGuild());
+
+		pc.incVer();
+		DispatchMessage.sendToAllInRange(pc, new GuildInfoMsg(pc, Guild.getErrantGuild(), 2));
+
+	}
+
+	public void upgradeGuildState(boolean nation){
+		if (nation){
+			this.guildState = GuildState.Nation;
+			return;
+		}
+		switch(this.guildState){
+
+		case Errant:
+			this.guildState = GuildState.Petitioner;
+			break;
+		case Sworn:
+			//Can't upgrade
+			break;
+		case Protectorate:
+			this.guildState = GuildState.Province;
+			break;
+		case Petitioner:
+			this.guildState = GuildState.Sworn;
+			break;
+		case Province:
+			//Can't upgrade
+			break;
+		case Nation:
+			//Can't upgrade
+			break;
+		case Sovereign:
+			this.guildState = GuildState.Protectorate;
+			break;
+		}
+
+	}
+
+	public void downgradeGuildState(){
+
+		switch(this.guildState){
+		case Errant:
+			break;
+		case Sworn:
+			this.guildState = GuildState.Errant;
+			break;
+		case Protectorate:
+			this.guildState = GuildState.Sovereign;
+			break;
+		case Petitioner:
+			this.guildState = GuildState.Errant;
+			break;
+		case Province:
+			this.guildState = GuildState.Sovereign;
+			break;
+		case Nation:
+			this.guildState = GuildState.Sovereign;
+			break;
+		case Sovereign:
+			this.guildState = GuildState.Errant;
+			break;
+		}
+
+	}
+
+	public boolean canSubAGuild(Guild toSub){
+
+		boolean canSub;
+		
+		if (this.equals(toSub))
+			return false;
+
+		switch(this.guildState){
+		case Nation:
+		case Sovereign:
+			canSub = true;
+			break;
+		default:
+			canSub = false;
+		}
+
+		switch(toSub.guildState){
+		case Errant:
+		case Sovereign:
+			canSub = true;
+			break;
+		default:
+			canSub = false;
+		}
+
+		return canSub;
+	}
+
+	public static boolean canSwearIn(Guild toSub){
+
+		boolean canSwear = false;
+
+		switch(toSub.guildState){
+
+		case Protectorate:
+		case Petitioner:
+			canSwear = true;
+			break;
+		default:
+			canSwear = false;
+		}
+
+		return canSwear;
+	}
+
+	/*
+	 * Serialization
+	 */
+	
+	public static void _serializeForClientMsg(Guild guild, ByteBufferWriter writer) {
+Guild.serializeForClientMsg(guild,writer, null, false);
+	}
+
+	public static void serializeForClientMsg(Guild guild, ByteBufferWriter writer, PlayerCharacter pc, boolean reshowGuild) {
+		writer.putInt(guild.getObjectType().ordinal());
+		writer.putInt(guild.getObjectUUID());
+        writer.putInt(guild.nation.getObjectType().ordinal());
+        writer.putInt(guild.nation.getObjectUUID());
+
+		if (pc == null) {
+			writer.putInt(0);
+			writer.putInt(0);
+			writer.putInt(0); // Defaults
+			writer.putInt(0); // Defaults
+		} else {
+			writer.putString(guild.name);
+            writer.putString(guild.nation.name);
+			writer.putInt(GuildStatusController.getTitle(pc.getGuildStatus())); // TODO Double check this is
+			// title and rank
+			if (GuildStatusController.isGuildLeader(pc.getGuildStatus()))
+				writer.putInt(PlayerCharacter.GetPlayerRealmTitle(pc));
+			else
+				writer.putInt(GuildStatusController.getRank(pc.getGuildStatus()));
+			//writer.putInt(GuildStatusController.getRank(pc.getGuildStatus()));
+		}
+
+		City ownedCity = guild.getOwnedCity();
+
+		if (ownedCity != null){
+			Realm realm = guild.getOwnedCity().getRealm();
+			if (realm != null && realm.getRulingCity() != null){
+				if (realm.getRulingCity().equals(ownedCity)){
+					writer.putInt(realm.getCharterType());
+				}else
+					writer.putInt(0);
+			}else{
+				writer.putInt(0);
+			}
+		}else
+			writer.putInt(0);
+
+		writer.putFloat(200);
+		writer.putFloat(200); // Pad
+
+		GuildTag._serializeForDisplay(guild.guildTag,writer);
+        GuildTag._serializeForDisplay(guild.nation.guildTag,writer);
+		if (reshowGuild) {
+			writer.putInt(1);
+			writer.putInt(guild.getObjectType().ordinal());
+			writer.putInt(guild.getObjectUUID());
+
+		} else
+			writer.putInt(0); // Pad
+	}
+
+	public static void serializeForTrack(Guild guild,ByteBufferWriter writer) {
+		Guild.serializeGuildForTrack(guild,writer);
+		if (guild.nation != null)
+			Guild.serializeGuildForTrack(guild.nation,writer);
+		else
+			Guild.addErrantForTrack(writer);
+	}
+
+	public static void serializeGuildForTrack(Guild guild, ByteBufferWriter writer) {
+		writer.putInt(guild.getObjectType().ordinal());
+		writer.putInt(guild.getObjectUUID());
+		writer.put((byte) 1);
+		GuildTag._serializeForDisplay(guild.guildTag,writer);
+	}
+
+	public  static void serializeErrantForTrack(ByteBufferWriter writer) {
+		addErrantForTrack(writer); //Guild
+		addErrantForTrack(writer); //Nation
+	}
+
+	public int getRealmsOwnedFlag(){
+		int flag = 0;
+        switch(realmsOwned){
+		case 0:
+			flag = 0;
+		case 1:
+		case 2:
+			flag = 1;
+			break;
+		case 3:
+		case 4:
+			flag = 2;
+			break;
+		case 5:
+			flag = 3;
+			break;
+		default:
+			flag = 3;
+			break;
+		}
+		return flag;
+	}
+
+	private static void addErrantForTrack(ByteBufferWriter writer) {
+		writer.putInt(0); //type
+		writer.putInt(0); //ID
+		writer.put((byte) 1);
+		writer.putInt(16); //Tags
+		writer.putInt(16);
+		writer.putInt(16);
+		writer.putInt(0);
+		writer.putInt(0);
+	}
+
+	public void serializeForPlayer(ByteBufferWriter writer) {
+		writer.putInt(this.getObjectType().ordinal());
+		writer.putInt(this.getObjectUUID());
+        writer.putInt(this.nation.getObjectType().ordinal());
+        writer.putInt(this.nation.getObjectUUID());
+
+	}
+
+	private static Guild errant;
+
+	public static Guild getErrantNation() {
+		if (Guild.errant == null)
+			Guild.errant = new Guild("None", null, 10, "Despot Rule", GuildTag.ERRANT, 0);
+		return Guild.errant;
+	}
+
+	/*
+	 * Game Object Manager
+	 */
+	public static Guild getGuild(final int objectUUID) {
+
+		if (objectUUID == 0)
+			return Guild.getErrantGuild();
+		Guild guild  = (Guild) DbManager.getFromCache(Enum.GameObjectType.Guild, objectUUID);
+		if (guild != null)
+			return guild;
+		
+		Guild dbGuild = DbManager.GuildQueries.GET_GUILD(objectUUID);
+		
+		if (dbGuild == null)
+			return Guild.getErrantGuild();
+		else
+			return dbGuild;
+	}
+
+
+	@Override
+	public void updateDatabase() {
+		DbManager.GuildQueries.updateDatabase(this);
+	}
+
+	public boolean isRealmRuler() {
+
+		City ownedCity;
+		Building tol;
+
+		ownedCity = this.getOwnedCity();
+
+		if (ownedCity == null)
+			return false;
+
+		tol = ownedCity.getTOL();
+
+		if (tol == null)
+			return false;
+
+        return tol.getRank() == 8;
+
+    }
+
+	@Override
+	public void runAfterLoad() {
+
+		try {
+			DbObjectType objectType = DbManager.BuildingQueries.GET_UID_ENUM(this.guildLeaderUUID);
+			this.ownerIsNPC = (objectType == DbObjectType.NPC);
+		} catch (Exception e) {
+			this.ownerIsNPC = false;
+			Logger.error("Failed to find Object Type for owner " + this.guildLeaderUUID);
+		}
+
+
+		//LOad Owners in Cache so we do not have to continuely look in the db for owner.
+		if (this.ownerIsNPC){
+			if (NPC.getNPC(this.guildLeaderUUID) == null)
+				Logger.info( "Guild UID " + this.getObjectUUID() + " Failed to Load NPC Owner with ID " + this.guildLeaderUUID);
+
+		}else if (this.guildLeaderUUID != 0){
+			if (PlayerCharacter.getPlayerCharacter(this.guildLeaderUUID) == null)
+				Logger.info( "Guild UID " + this.getObjectUUID() + " Failed to Load Player Owner with ID " + this.guildLeaderUUID);
+		}
+
+		// If loading this guild for the first time write it's character record to disk
+
+        if (ConfigManager.serverType.equals(ServerType.WORLDSERVER)
+				&& (hash == null)) {
+
+			this.setHash();
+
+			if (DataWarehouse.recordExists(Enum.DataRecordType.GUILD, this.getObjectUUID()) == false) {
+				GuildRecord guildRecord = GuildRecord.borrow(this, Enum.RecordEventType.CREATE);
+				DataWarehouse.pushToWarehouse(guildRecord);
+			}
+
+		}
+
+		if (MBServerStatics.worldUUID == nationUUID && this.cityUUID != 0)
+			this.nation = this;
+		else if (nationUUID == 0 || (MBServerStatics.worldUUID == nationUUID && this.cityUUID == 0)) {
+			this.nation = Guild.getErrantGuild();
+			this.nmotd = "";
+		} else
+			this.nation = Guild.getGuild(nationUUID);
+		
+		if (this.nation == null)
+			this.nation = Guild.getErrantGuild();
+		//Get guild states.
+		try {
+			this.subGuildList = DbManager.GuildQueries.GET_SUB_GUILDS(this.getObjectUUID());
+		}catch(Exception e){
+
+			this.subGuildList = new ArrayList<>();
+			Logger.error( "FAILED TO LOAD SUB GUILDS FOR UUID " + this.getObjectUUID());
+		}
+
+        if (this.nation == this && subGuildList.size() > 0)
+			this.guildState = GuildState.Nation;
+		else if (this.nation.equals(this))
+			this.guildState = GuildState.Sovereign;
+		else if (!this.nation.isErrant() && this.cityUUID != 0)
+			this.guildState = GuildState.Province;
+		else if (!this.nation.isErrant())
+			this.guildState = GuildState.Sworn;
+		else
+			this.guildState = GuildState.Errant;
+
+		if (this.cityUUID == 0)
+			return;
+
+
+		// Calculate number of realms this guild controls
+		// Only do this on the game server to avoid loading a TOL/City/Zone needlessly
+
+		if ((ConfigManager.serverType.equals(ServerType.WORLDSERVER)) &&
+				(this.isRealmRuler() == true)) {
+			this.realmsOwned++;
+            if (!this.nation.equals(this)) {
+                this.nation.realmsOwned++;
+            }
+		}
+
+		if (ConfigManager.serverType.equals(ServerType.WORLDSERVER)){
+
+			//add alliance list, clear all lists as there seems to be a bug where alliances are doubled, need to find where.
+			//possible runAfterLoad being called twice?!?!
+			this.banishList = dbGuildHandler.GET_GUILD_BANISHED(this.getObjectUUID());
+			this.characterKOSList = DbManager.GuildQueries.GET_GUILD_KOS_CHARACTER(this.getObjectUUID());
+			this.guildKOSList = DbManager.GuildQueries.GET_GUILD_KOS_GUILD(this.getObjectUUID());
+
+			this.allyList.clear();
+			this.enemyList.clear();
+			this.recommendList.clear();
+
+			try{
+				DbManager.GuildQueries.LOAD_ALL_ALLIANCES_FOR_GUILD(this);
+				for (GuildAlliances guildAlliance:this.guildAlliances.values()){
+					if (guildAlliance.isRecommended()){
+						Guild recommendedGuild = Guild.getGuild(guildAlliance.getAllianceGuild());
+						if (recommendedGuild != null)
+							this.recommendList.add(recommendedGuild);
+					}else if (guildAlliance.isAlly()){
+						Guild alliedGuild = Guild.getGuild(guildAlliance.getAllianceGuild());
+						if (alliedGuild != null)
+							this.allyList.add(alliedGuild);
+					}else{
+						Guild enemyGuild = Guild.getGuild(guildAlliance.getAllianceGuild());
+						if (enemyGuild != null)
+							this.enemyList.add(enemyGuild);
+					}
+
+				}
+			}catch(Exception e){
+				Logger.error(this.getObjectUUID() + e.getMessage());
+			}
+		}
+	}
+
+	/**
+	 * @return the motto
+	 */
+	public String getMotto() {
+		return motto;
+	}
+
+	public GuildState getGuildState() {
+		return guildState;
+	}
+
+	public void setGuildState(GuildState guildState) {
+		this.guildState = guildState;
+	}
+
+	/**
+	 * @return the realmsOwned
+	 */
+	public int getRealmsOwned() {
+		return realmsOwned;
+	}
+
+	/**
+	 * @param realmsOwned the realmsOwned to set
+	 */
+	public void setRealmsOwned(int realmsOwned) {
+		this.realmsOwned = realmsOwned;
+	}
+
+	public void removeSubGuild(Guild subGuild) {
+
+		// Update database
+
+		if (!DbManager.GuildQueries.UPDATE_PARENT(subGuild.getObjectUUID(), MBServerStatics.worldUUID))
+			Logger.debug("Failed to set Nation Guild for Guild with UID " + subGuild.getObjectUUID());
+
+		// Guild without any subs is no longer a nation
+
+		if (subGuild.getOwnedCity() == null) {
+			subGuild.nation = null;
+		}
+		else {
+			subGuild.nation = subGuild;
+		}
+
+		// Downgrade guild
+
+		subGuild.downgradeGuildState();
+
+		// Remove from collection
+
+        subGuildList.remove(subGuild);
+
+		GuildManager.updateAllGuildTags(subGuild);
+		GuildManager.updateAllGuildBinds(subGuild, subGuild.getOwnedCity());
+
+	}
+
+	public void setMineTime(int mineTime) {
+		this.mineTime = mineTime;
+	}
+
+	public ConcurrentHashMap<Integer,Condemned> getGuildCondemned() {
+		return guildCondemned;
+	}
+
+
+	public String getHash() {
+		return hash;
+	}
+
+	public void setHash() {
+		this.hash = DataWarehouse.hasher.encrypt(this.getObjectUUID());
+
+		DataWarehouse.writeHash(Enum.DataRecordType.GUILD, this.getObjectUUID());
+	}
+
+	public Enum.GuildType getGuildType(){
+		try{
+			return Enum.GuildType.values()[this.charter];
+		}catch(Exception e){
+			Logger.error(e);
+			return Enum.GuildType.NONE;
+		}
+
+	}
+
+	public ArrayList<Guild> getRecommendList() {
+		return recommendList;
+	}
+
+	public void setRecommendList(ArrayList<Guild> recommendList) {
+		this.recommendList = recommendList;
+	}
+
+	public synchronized boolean addGuildToAlliance(AllianceChangeMsg msg, final AllianceType allianceType, Guild toGuild, PlayerCharacter player){
+
+		Dispatch dispatch;
+
+		// Member variable assignment
+
+
+		if (toGuild == null)
+			return false;
+
+		if (!Guild.sameGuild(player.getGuild(), this)){
+			msg.setMsgType(AllianceChangeMsg.ERROR_NOT_SAME_GUILD);
+			dispatch = Dispatch.borrow(player, msg);
+			DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+			return false;
+		}
+
+		if (allianceType == AllianceType.Ally || allianceType == AllianceType.Enemy)
+			if (!GuildStatusController.isInnerCouncil(player.getGuildStatus()) && !GuildStatusController.isGuildLeader(player.getGuildStatus())){
+				msg.setMsgType(AllianceChangeMsg.ERROR_NOT_AUTHORIZED);
+				dispatch = Dispatch.borrow(player, msg);
+				DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+				return false;
+			}
+
+		if (allianceType == AllianceType.RecommendedAlly || allianceType == AllianceType.RecommendedEnemy){
+			if (!GuildStatusController.isFullMember(player.getGuildStatus())){
+				msg.setMsgType(AllianceChangeMsg.ERROR_NOT_AUTHORIZED);
+				dispatch = Dispatch.borrow(player, msg);
+				DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+				return false;
+			}
+		}
+
+		//		if (this.getGuildType() != toGuild.getGuildType()){
+		//			msg.setMsgType(AllianceChangeMsg.ERROR_NOT_SAME_FACTION);
+		//			dispatch = Dispatch.borrow(player, msg);
+		//			DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+		//			return false;
+		//		}
+
+
+
+
+
+
+		switch(allianceType){
+		case RecommendedAlly:
+            if (recommendList.size() == 10){
+				msg.setMsgType(AllianceChangeMsg.ERROR_TOO_MANY);
+				dispatch = Dispatch.borrow(player, msg);
+				DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+				return false;
+			}
+
+            if (recommendList.contains(toGuild)){
+				ErrorPopupMsg.sendErrorMsg(player, "This guild is already recommonded!");
+				msg.setMsgType((byte)15);
+				dispatch = Dispatch.borrow(player, msg);
+				DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+				return false;
+			}
+
+			if (!DbManager.GuildQueries.ADD_TO_ALLIANCE_LIST(this.getObjectUUID(), toGuild.getObjectUUID(), true, true, player.getFirstName())){
+				msg.setMsgType((byte)15);
+				dispatch = Dispatch.borrow(player, msg);
+				return false;
+			}
+
+			GuildAlliances guildAlliance = new GuildAlliances(this.getObjectUUID(), toGuild.getObjectUUID(), true, true, player.getFirstName());
+			this.guildAlliances.put(toGuild.getObjectUUID(), guildAlliance);
+			this.removeGuildFromEnemy(toGuild);
+			this.removeGuildFromAlliance(toGuild);
+			this.recommendList.add(toGuild);
+
+
+
+			return true;
+
+		case RecommendedEnemy:
+            if (recommendList.size() == 10){
+				msg.setMsgType(AllianceChangeMsg.ERROR_TOO_MANY);
+				dispatch = Dispatch.borrow(player, msg);
+				DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+				return false;
+			}
+
+            if (recommendList.contains(toGuild)){
+				ErrorPopupMsg.sendErrorMsg(player, "This guild is already recommonded!");
+				msg.setMsgType((byte)15);
+				dispatch = Dispatch.borrow(player, msg);
+				DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+				return false;
+			}
+
+			if (!DbManager.GuildQueries.ADD_TO_ALLIANCE_LIST(this.getObjectUUID(), toGuild.getObjectUUID(), true, false, player.getFirstName())){
+				msg.setMsgType((byte)15);
+				dispatch = Dispatch.borrow(player, msg);
+				return false;
+			}
+
+			GuildAlliances enemyAlliance = new GuildAlliances(this.getObjectUUID(), toGuild.getObjectUUID(), true, false, player.getFirstName());
+			this.guildAlliances.put(toGuild.getObjectUUID(), enemyAlliance);
+			this.removeGuildFromEnemy(toGuild);
+			this.removeGuildFromAlliance(toGuild);
+			this.recommendList.add(toGuild);
+
+			return true;
+
+		case Ally:
+            if (allyList.size() == 10){
+				msg.setMsgType(AllianceChangeMsg.ERROR_TOO_MANY);
+				dispatch = Dispatch.borrow(player, msg);
+				DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+				return false;
+			}
+
+            if (allyList.contains(toGuild)){
+				ErrorPopupMsg.sendErrorMsg(player, "This guild is already an Ally!");
+				msg.setMsgType((byte)15);
+				dispatch = Dispatch.borrow(player, msg);
+				DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+				return false;
+			}
+
+			if (!this.guildAlliances.containsKey(toGuild.getObjectUUID())){
+				ErrorPopupMsg.sendErrorMsg(player, "A Serious error has Occured. Please contact CCR!");
+				Logger.error(this.getObjectUUID() +  " Could not find alliance Guild");
+				msg.setMsgType((byte)15);
+				dispatch = Dispatch.borrow(player, msg);
+				DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+				return false;
+			}
+
+			GuildAlliances ally = this.guildAlliances.get(toGuild.getObjectUUID());
+			if (!ally.UpdateAlliance(AllianceType.Ally, this.recommendList.contains(toGuild))){
+				ErrorPopupMsg.sendErrorMsg(player, "A Serious error has Occured. Please contact CCR!");
+				Logger.error( this.getObjectUUID() +  " failed to update alliance Database");
+				msg.setMsgType((byte)15);
+				dispatch = Dispatch.borrow(player, msg);
+				DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+				return false;
+
+			}
+
+			this.removeGuildFromEnemy(toGuild);
+			this.removeGuildFromRecommended(toGuild);
+
+			this.allyList.add(toGuild);
+			Guild.UpdateClientAlliances(this);
+
+
+			break;
+		case Enemy:
+            if (enemyList.size() == 10){
+				msg.setMsgType(AllianceChangeMsg.ERROR_TOO_MANY);
+				dispatch = Dispatch.borrow(player, msg);
+				DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+				return false;
+			}
+
+            if (enemyList.contains(toGuild)){
+				ErrorPopupMsg.sendErrorMsg(player, "This guild is already an Enemy!");
+				msg.setMsgType((byte)15);
+				dispatch = Dispatch.borrow(player, msg);
+				DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+				return false;
+			}
+
+			if (!this.guildAlliances.containsKey(toGuild.getObjectUUID())){
+				ErrorPopupMsg.sendErrorMsg(player, "A Serious error has Occured. Please contact CCR!");
+				Logger.error( this.getObjectUUID() +  " Could not find alliance Guild");
+				msg.setMsgType((byte)15);
+				dispatch = Dispatch.borrow(player, msg);
+				DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+				return false;
+			}
+
+			GuildAlliances enemy = this.guildAlliances.get(toGuild.getObjectUUID());
+			if (!enemy.UpdateAlliance(AllianceType.Enemy, this.recommendList.contains(toGuild))){
+				ErrorPopupMsg.sendErrorMsg(player, "A Serious error has Occured. Please contact CCR!");
+				Logger.error(this.getObjectUUID() +  " failed to update alliance Database");
+				msg.setMsgType((byte)15);
+				dispatch = Dispatch.borrow(player, msg);
+				DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+				return false;
+
+			}
+
+			//remove from other allied lists.
+			this.removeGuildFromAlliance(toGuild);
+			this.removeGuildFromRecommended(toGuild);
+
+			this.enemyList.add(toGuild);
+
+			Guild.UpdateClientAlliances(this);
+			break;
+		}
+
+		// once here everything passed, send successMsg;
+		msg.setMsgType(AllianceChangeMsg.INFO_SUCCESS);
+		dispatch = Dispatch.borrow(player, msg);
+		DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+		return true;
+	}
+
+	public synchronized boolean removeGuildFromAlliance(Guild toRemove){
+		if (this.allyList.contains(toRemove)){
+			this.allyList.remove(toRemove);
+		}
+		return true;
+	}
+	public synchronized boolean removeGuildFromEnemy(Guild toRemove){
+		if (this.enemyList.contains(toRemove)){
+			this.enemyList.remove(toRemove);
+		}
+		return true;
+	}
+	public synchronized boolean removeGuildFromRecommended(Guild toRemove){
+		if (this.recommendList.contains(toRemove)){
+			this.recommendList.remove(toRemove);
+		}
+		return true;
+	}
+
+	public synchronized boolean removeGuildFromAllAlliances(Guild toRemove){
+
+		if (!this.guildAlliances.containsKey(toRemove.getObjectUUID())){
+			return false;
+		}
+
+		if (!DbManager.GuildQueries.REMOVE_FROM_ALLIANCE_LIST(this.getObjectUUID(), toRemove.getObjectUUID()))
+			return false;
+
+
+
+		this.guildAlliances.remove(toRemove.getObjectUUID());
+
+		this.removeGuildFromAlliance(toRemove);
+		this.removeGuildFromEnemy(toRemove);
+		this.removeGuildFromRecommended(toRemove);
+
+		Guild.UpdateClientAlliances(this);
+
+
+		return true;
+
+	}
+
+	public static void UpdateClientAlliances(Guild toUpdate){
+		UpdateClientAlliancesMsg ucam = new UpdateClientAlliancesMsg(toUpdate);
+
+
+
+		for (PlayerCharacter player : SessionManager.getAllActivePlayerCharacters()) {
+
+			if (Guild.sameGuild(player.getGuild(), toUpdate)){
+				Dispatch dispatch = Dispatch.borrow(player, ucam);
+				DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+			}
+
+
+		}
+	}
+
+	public static void UpdateClientAlliancesForPlayer(PlayerCharacter toUpdate){
+		UpdateClientAlliancesMsg ucam = new UpdateClientAlliancesMsg(toUpdate.getGuild());
+		Dispatch dispatch = Dispatch.borrow(toUpdate, ucam);
+		DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+
+
+	}
+	
+	public static Guild getFromCache(int id) {
+		return (Guild) DbManager.getFromCache(GameObjectType.Guild, id);
+	}
+	
+	public static ArrayList<PlayerCharacter> GuildRoster(Guild guild){
+		ArrayList<PlayerCharacter> roster = new ArrayList<>();
+		if (guild == null)
+			return roster;
+		
+		if (guild.isErrant())
+			return roster;
+		
+		if (DbManager.getList(GameObjectType.PlayerCharacter) == null)
+			return roster;
+		for (AbstractGameObject ago : DbManager.getList(GameObjectType.PlayerCharacter)){
+			PlayerCharacter toAdd = (PlayerCharacter)ago;
+			
+			if (!toAdd.getGuild().equals(guild))
+			continue;
+			
+			if (toAdd.isDeleted())
+				continue;
+			
+			roster.add(toAdd);
+			
+		}
+		return roster;
+	}
+
+
+
+}
diff --git a/src/engine/objects/GuildAlliances.java b/src/engine/objects/GuildAlliances.java
new file mode 100644
index 00000000..7c078729
--- /dev/null
+++ b/src/engine/objects/GuildAlliances.java
@@ -0,0 +1,102 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.objects;
+
+import engine.Enum.AllianceType;
+import engine.gameManager.DbManager;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+public class GuildAlliances  {
+
+	private int sourceGuild;
+	private int allianceGuild;
+	private boolean isRecommended;
+	private boolean isAlly;
+	private String recommender;
+
+	/**
+	 * ResultSet Constructor
+	 */
+
+	public GuildAlliances(ResultSet rs) throws SQLException {
+		this.sourceGuild = rs.getInt("GuildID");
+		this.allianceGuild = rs.getInt("OtherGuildID");
+		this.isRecommended = rs.getBoolean("isRecommended");
+		this.isAlly = rs.getBoolean("isAlliance");
+		this.recommender =rs.getString("recommender");
+	}
+
+	public GuildAlliances(int sourceGuild, int allianceGuild, boolean isRecommended, boolean isAlly,
+			String recommender) {
+		super();
+		this.sourceGuild = sourceGuild;
+		this.allianceGuild = allianceGuild;
+		this.isRecommended = isRecommended;
+		this.isAlly = isAlly;
+		this.recommender = recommender;
+	}
+
+	public int getSourceGuild() {
+		return sourceGuild;
+	}
+
+	public int getAllianceGuild() {
+		return allianceGuild;
+	}
+
+	public boolean isRecommended() {
+		return isRecommended;
+	}
+
+	public boolean isAlly() {
+		return isAlly;
+	}
+
+	public String getRecommender() {
+		return recommender;
+	}
+
+	public synchronized boolean UpdateAlliance(final AllianceType allianceType, boolean updateRecommended){
+		switch (allianceType){
+		case Ally:
+			if (updateRecommended){
+				if (!DbManager.GuildQueries.UPDATE_ALLIANCE_AND_RECOMMENDED(this.sourceGuild, this.allianceGuild, true))
+					return false;
+				this.isAlly = true;
+				this.isRecommended = false;
+			}else{
+				if (!DbManager.GuildQueries.UPDATE_ALLIANCE(this.sourceGuild, this.allianceGuild, true))
+					return false;
+				this.isAlly = true;
+				this.isRecommended = false;
+			}
+			break;
+		case Enemy:
+
+			if (updateRecommended){
+				if (!DbManager.GuildQueries.UPDATE_ALLIANCE_AND_RECOMMENDED(this.sourceGuild, this.allianceGuild, false))
+					return false;
+				this.isAlly = false;
+				this.isRecommended = false;
+			}else{
+				if (!DbManager.GuildQueries.UPDATE_ALLIANCE(this.sourceGuild, this.allianceGuild, false))
+					return false;
+				this.isAlly = false;
+				this.isRecommended = false;
+			}
+			break;
+
+		}
+		return true;
+	}
+
+}
diff --git a/src/engine/objects/GuildCondemn.java b/src/engine/objects/GuildCondemn.java
new file mode 100644
index 00000000..87a883d5
--- /dev/null
+++ b/src/engine/objects/GuildCondemn.java
@@ -0,0 +1,77 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.objects;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.HashMap;
+
+
+
+
+public class GuildCondemn  {
+
+	private int ID;
+	private int playerUID;
+	private int parentGuildUID;
+	private int guildUID;
+	private int friendType;
+	public static HashMap<Integer,ArrayList<GuildCondemn>> GetCondemnedFromGuildID = new HashMap<>();
+
+
+	/**
+	 * ResultSet Constructor
+	 */
+	public GuildCondemn(ResultSet rs) throws SQLException {
+		this.playerUID = rs.getInt("playerUID");
+		this.parentGuildUID = rs.getInt("buildingUID");
+		this.guildUID = rs.getInt("guildUID");
+		this.friendType = rs.getInt("friendType");
+	}
+	
+	
+
+
+	public GuildCondemn(int playerUID, int parentGuildUID, int guildUID, int friendType) {
+		super();
+		this.playerUID = playerUID;
+		this.parentGuildUID = parentGuildUID;
+		this.guildUID = guildUID;
+		this.friendType = friendType;
+	}
+
+
+
+
+	public int getPlayerUID() {
+		return playerUID;
+	}
+
+
+	public int getParentGuildUID() {
+		return parentGuildUID;
+	}
+
+
+	public int getGuildUID() {
+		return guildUID;
+	}
+
+
+	public int getFriendType() {
+		return friendType;
+	}
+
+
+
+	
+	
+}
diff --git a/src/engine/objects/GuildHistory.java b/src/engine/objects/GuildHistory.java
new file mode 100644
index 00000000..ad48e476
--- /dev/null
+++ b/src/engine/objects/GuildHistory.java
@@ -0,0 +1,94 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.objects;
+
+
+import engine.Enum.GameObjectType;
+import engine.Enum.GuildHistoryType;
+import engine.net.ByteBufferWriter;
+import org.joda.time.DateTime;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+
+public class GuildHistory {
+
+
+	private int guildID;
+	private String guildName;
+	private DateTime time;
+	private GuildHistoryType historyType;
+
+
+
+
+	public GuildHistory( int guildID, String guildName,
+			DateTime dateTime, GuildHistoryType historyType ) {
+		super();
+		this.guildID = guildID;
+		this.guildName = guildName;
+		this.time = dateTime;
+		this.historyType = historyType;
+
+	}
+
+	public GuildHistoryType getHistoryType() {
+		return historyType;
+	}
+
+	public GuildHistory(ResultSet rs) throws SQLException {
+		java.util.Date sqlDateTime;
+		this.guildID = rs.getInt("guildID");
+		Guild guild = Guild.getGuild(this.guildID);
+		if (guild != null)
+			this.guildName = guild.getName();
+		else
+			this.guildName = "Guild Not Found";
+
+		sqlDateTime = rs.getTimestamp("historyDate");
+		if (sqlDateTime != null)
+			this.time = new DateTime(sqlDateTime);
+		else
+			this.time = DateTime.now().minusYears(1);
+		this.historyType = GuildHistoryType.valueOf(rs.getString("historyType"));
+	}
+
+
+	public long getGuildID() {
+		return guildID;
+	}
+
+	public String getGuildName() {
+		return guildName;
+	}
+
+
+
+	public void _serialize(ByteBufferWriter writer) {
+		writer.putInt(this.historyType.getType());
+		writer.putInt(GameObjectType.Guild.ordinal());
+		writer.putInt(this.guildID);
+		writer.putString(guildName);
+		writer.putInt(0);	//Pad
+		writer.putDateTime(this.time);
+	}
+
+	public DateTime getTime() {
+		return time;
+	}
+
+	public void setTime(DateTime time) {
+		this.time = time;
+	}
+
+
+
+}
diff --git a/src/engine/objects/GuildStatusController.java b/src/engine/objects/GuildStatusController.java
new file mode 100644
index 00000000..ac76790b
--- /dev/null
+++ b/src/engine/objects/GuildStatusController.java
@@ -0,0 +1,131 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.objects;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+public class GuildStatusController {
+
+	/*
+	 * 	Status is stored in a single integer contained within the Character Table
+	 * 
+	 * 	This class is responsible for maintaining and interpreting that value.
+	 * 
+	 *  Byte 1 - All		: Title 			[0x000000FF]
+	 *  Byte 2 - Low		: isFullMember 		[0x00000F00]
+	 *  Byte 2 - High		: isTaxCollector	[0x0000F000]
+	 *  Byte 3 - Low		: isRecruiter		[0x000F0000]
+	 *  Byte 3 - High		: isInnerCouncil	[0x00F00000]
+	 *  Byte 4 - Low		: isGuildLeader		[0x0F000000]
+	 *  Byte 4 - High		: Empty				[0xF0000000]
+	 */
+
+	//Getters
+	public static boolean isGuildLeader(AtomicInteger status) {
+		return ((status.get() & GUILDLEADER)  > 0);
+	}
+	
+	public static boolean isInnerCouncil(AtomicInteger status) {
+		return ((status.get() & INNERCOUNCIL)  > 0);
+	}
+	
+	public static boolean isRecruiter(AtomicInteger status) {
+		return ((status.get() & RECRUITER)  > 0);
+	}
+	
+	public static boolean isTaxCollector(AtomicInteger status) {
+		return ((status.get() & TAXCOLLECTOR)  > 0);
+	}
+	
+	public static boolean isFullMember(AtomicInteger status) {
+		return ((status.get() & FULLMEMBER)  > 0);
+	}
+	
+	public static int getTitle(AtomicInteger status) {
+		return status.get() & TITLE;
+	}
+	
+	public static int getRank(AtomicInteger status) {
+		int value = status.get();
+		
+		//Guild Leader
+		if(value > 0x00FFFFFF) {
+			return 10;
+		} 
+		
+		//Inner Council
+		if(value > 0x000FFFFF) {
+			return 9;
+		}
+		
+		//Recruiter
+		if(value > 0x0000FFFF) {
+			return 8;
+		}
+		
+		//Tax Collector
+		if(value > 0x00000FFF) {
+			return 7;
+		}
+		
+		//Full Member
+		if(value > 0x000000FF) {
+			return 6;
+		}
+		
+		//Petitioner
+		return 5;
+	}
+	
+	//Setters
+	public static void setTitle(AtomicInteger current, int i) {
+		int value;
+		i &= TITLE;
+		do {
+			value = current.get();
+		}while(!current.compareAndSet(value, (value & ~TITLE) | i));
+	}
+	
+	
+	public static void setFullMember(AtomicInteger status, boolean newValue) {
+		setNibble(status, newValue, FULLMEMBER);
+	}
+	
+	public static void setTaxCollector(AtomicInteger status, boolean newValue) {
+		setNibble(status, newValue, TAXCOLLECTOR);
+	}
+	
+	public static void setRecruiter(AtomicInteger status, boolean newValue) {
+		setNibble(status, newValue, RECRUITER);
+	}
+	
+	public static void setInnerCouncil(AtomicInteger status, boolean newValue) {
+		setNibble(status, newValue, INNERCOUNCIL);
+	}
+	
+	public static void setGuildLeader	(AtomicInteger status, boolean newValue) {
+		setNibble(status, newValue, GUILDLEADER);
+	}
+	
+	private static void setNibble(AtomicInteger current, boolean newValue, int mask) {
+		int value, i = ((newValue)?mask & -1:0);
+		do {
+			value = current.get();
+		}while(!current.compareAndSet(value, (value & ~mask) | i));
+	}
+	
+	//Constants
+	private static final int TITLE = 0x000000FF; // 00, F0 and 0F had no effect
+	private static final int FULLMEMBER = 0x00000F00;
+	private static final int TAXCOLLECTOR = 0x0000F000;
+	private static final int RECRUITER = 0x000F0000;
+	private static final int INNERCOUNCIL = 0x00F00000;
+	private static final int GUILDLEADER = 0x0F000000;
+}
diff --git a/src/engine/objects/GuildTag.java b/src/engine/objects/GuildTag.java
new file mode 100644
index 00000000..014c0341
--- /dev/null
+++ b/src/engine/objects/GuildTag.java
@@ -0,0 +1,93 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.objects;
+
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+
+public class GuildTag {
+	public final int backgroundColor01;
+	public final int backgroundColor02;
+	public final int symbolColor;
+	public final int symbol;
+	public final int backgroundDesign;
+	public static final GuildTag ERRANT = new GuildTag(16,16,16,0,0);
+
+	public GuildTag(int backgroundColor01, int backgroundColor02,
+			int symbolColor, int symbol, int backgroundDesign) {
+		super();
+		this.backgroundColor01 = backgroundColor01;
+		this.backgroundColor02 = backgroundColor02;
+		this.symbolColor = symbolColor;
+		this.symbol = symbol;
+		this.backgroundDesign = backgroundDesign;
+	}
+
+	public GuildTag(ByteBufferReader reader, boolean forCreation) {
+		this.backgroundColor01 = reader.getInt();
+		this.backgroundColor02 = reader.getInt();
+		this.symbolColor = reader.getInt();
+		if(forCreation) {
+			this.symbol = reader.getInt();
+			this.backgroundDesign = reader.getInt();
+		} else {
+			this.backgroundDesign = reader.getInt();
+			this.symbol = reader.getInt();
+		}
+	}
+
+	public GuildTag(ByteBufferReader reader) {
+		this(reader, false);
+	}
+
+	public boolean isValid() {
+		if(this.backgroundColor01 < 0 || this.backgroundColor01 > 18)
+			return false;
+		if(this.backgroundColor02 < 0 || this.backgroundColor02 > 18)
+			return false;
+		if(this.symbolColor < 0 || this.symbolColor > 18)
+			return false;
+		if(this.symbol < 0 || this.symbol > 183)
+			return false;
+        return this.backgroundDesign >= 0 && this.backgroundDesign <= 14;
+    }
+
+	
+	public static void _serializeForGuildCreation(GuildTag guildTag, ByteBufferWriter writer) {
+		writer.putInt(guildTag.backgroundColor01);
+		writer.putInt(guildTag.backgroundColor02);
+		writer.putInt(guildTag.symbolColor);
+		writer.putInt(guildTag.symbol);
+		writer.putInt(guildTag.backgroundDesign);
+	}
+
+	public static void _serializeForDisplay(GuildTag guildTag, ByteBufferWriter writer) {
+		writer.putInt(guildTag.backgroundColor01);
+		writer.putInt(guildTag.backgroundColor02);
+		writer.putInt(guildTag.symbolColor);
+		writer.putInt(guildTag.backgroundDesign);
+		writer.putInt(guildTag.symbol);
+	}
+
+	public void serializeObject(ByteBufferWriter writer) {
+		writer.put((byte)this.backgroundColor01);
+		writer.put((byte)this.backgroundColor02);
+		writer.put((byte)this.symbolColor);
+		writer.put((byte)this.backgroundDesign);
+		writer.put((byte)this.symbol);
+	}
+
+	
+	
+	public String summarySentence() {
+		return "Bkgrnd: " + this.backgroundDesign + '(' + this.backgroundColor01 + '-' + this.backgroundColor02 + ')' +
+				"; Symbol: " + this.symbol + '(' + this.symbolColor + ')';
+	}
+}
diff --git a/src/engine/objects/Heraldry.java b/src/engine/objects/Heraldry.java
new file mode 100644
index 00000000..f7757a95
--- /dev/null
+++ b/src/engine/objects/Heraldry.java
@@ -0,0 +1,171 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.objects;
+
+import engine.Enum.GameObjectType;
+import engine.gameManager.DbManager;
+import org.pmw.tinylog.Logger;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.HashMap;
+
+public class Heraldry  {
+
+	public int playerUID;
+	public int characterUUID;
+	public int characterType;
+	
+	public static HashMap <Integer,HashMap<Integer,Integer>> HeraldyMap = new HashMap<>();
+
+	/**
+	 * ResultSet Constructor
+	 */
+
+	public Heraldry(ResultSet rs) throws SQLException {
+		this.playerUID = rs.getInt("playerUID");
+		this.characterUUID = rs.getInt("characterUID");
+		this.characterType = rs.getInt("characterType");
+		
+		//cache player friends.
+		//hashset already created, just add to set.
+		if (HeraldyMap.containsKey(playerUID)){
+			HashMap<Integer,Integer> playerHeraldySet = HeraldyMap.get(playerUID);
+			playerHeraldySet.put(characterUUID,characterType);
+			//hashset not yet created, create new set, and add to map.
+		}else{
+			HashMap<Integer,Integer> playerHeraldySet = new HashMap<>();
+			playerHeraldySet.put(characterUUID,characterType);
+			HeraldyMap.put(this.playerUID, playerHeraldySet);
+		}
+		
+	}
+
+	public Heraldry(int playerUID, int friendUID) {
+		super();
+		this.playerUID = playerUID;
+		this.characterUUID = friendUID;
+	}
+
+	public int getPlayerUID() {
+		return playerUID;
+	}
+	
+	public static boolean AddToHeraldy(int playerID, AbstractWorldObject character){
+		HashMap<Integer,Integer> characters = HeraldyMap.get(playerID);
+		
+		if (characters != null){
+			//already in friends list, don't do anything.
+			if (characters.containsKey(character.getObjectUUID()))
+				return false;
+			
+			DbManager.PlayerCharacterQueries.ADD_HERALDY(playerID, character);
+			characters.put(character.getObjectUUID(),character.getObjectType().ordinal());
+		}else{
+			characters = new HashMap<>();
+			DbManager.PlayerCharacterQueries.ADD_HERALDY(playerID, character);
+			characters.put(character.getObjectUUID(),character.getObjectType().ordinal());
+			HeraldyMap.put(playerID, characters);
+		}
+		return true;
+	}
+	
+	public static boolean RemoveFromHeraldy(int playerID, int characterID){
+		
+	if (!CanRemove(playerID, characterID))
+		return false;
+	
+	HashMap<Integer,Integer> characters = HeraldyMap.get(playerID);
+		
+		if (characters != null){
+			DbManager.PlayerCharacterQueries.REMOVE_HERALDY(playerID, characterID);
+			characters.remove(characterID);
+		}
+		return true;
+	}
+	
+	public static boolean CanRemove(int playerID, int toRemove){
+		if (HeraldyMap.get(playerID) == null)
+			return false;
+
+		if (HeraldyMap.get(playerID).isEmpty())
+			return false;
+
+		if (!HeraldyMap.get(playerID).containsKey(toRemove))
+			return false;
+
+		return true;
+	}
+
+	public static void AuditHeraldry() {
+
+		HashMap<Integer, Integer> characterMap;
+		ArrayList<Integer> purgeList = new ArrayList<>();
+
+		for (int playerID : Heraldry.HeraldyMap.keySet()) {
+
+			characterMap = Heraldry.HeraldyMap.get(playerID);
+
+			if (characterMap == null || characterMap.isEmpty())
+				continue;
+
+			// Loop through map adding deleted characters to our purge map
+
+			purgeList.clear();
+
+			for (int characterID : characterMap.keySet()) {
+
+				int characterType = characterMap.get(characterID);
+
+				if (characterType != GameObjectType.PlayerCharacter.ordinal())
+					continue;
+
+				// Player is deleted, add to purge list
+
+				if (PlayerCharacter.getFromCache(characterID) == null)
+					purgeList.add(characterID);
+
+			}
+
+			// Run purge
+
+			for (int uuid : purgeList) {
+
+				if (!Heraldry.RemoveFromHeraldy(playerID, uuid))
+					continue;
+
+				Logger.info("Removed Deleted Character ID " + uuid + " from PlayerID " + playerID + " heraldry.");
+
+			}
+		}
+	}
+
+	public static void ValidateHeraldry(int playerUUID) {
+		
+		HashMap<Integer,Integer> heraldryMap = Heraldry.HeraldyMap.get(playerUUID);
+		
+		if (heraldryMap == null || heraldryMap.isEmpty())
+			return;
+		
+		for (int characterID : heraldryMap.keySet()){
+			int characterType = heraldryMap.get(characterID);
+			
+			GameObjectType objectType = GameObjectType.values()[characterType];
+			
+			AbstractGameObject ago = DbManager.getFromCache(objectType, characterID);
+			
+			if (ago == null)
+				heraldryMap.remove(characterID);
+			
+		}
+	}
+	
+}
diff --git a/src/engine/objects/Item.java b/src/engine/objects/Item.java
new file mode 100644
index 00000000..64f139e3
--- /dev/null
+++ b/src/engine/objects/Item.java
@@ -0,0 +1,1466 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.objects;
+
+import engine.Enum;
+import engine.Enum.*;
+import engine.exception.SerializationException;
+import engine.gameManager.ConfigManager;
+import engine.gameManager.DbManager;
+import engine.gameManager.PowersManager;
+import engine.net.ByteBufferReader;
+import engine.net.ByteBufferWriter;
+import engine.net.Dispatch;
+import engine.net.DispatchMessage;
+import engine.net.client.ClientConnection;
+import engine.net.client.msg.DeleteItemMsg;
+import engine.powers.EffectsBase;
+import engine.powers.effectmodifiers.AbstractEffectModifier;
+import engine.powers.poweractions.AbstractPowerAction;
+import engine.server.MBServerStatics;
+import org.pmw.tinylog.Logger;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.locks.ReentrantLock;
+
+
+public class Item extends AbstractWorldObject {
+
+	private int ownerID;  //may be character, account, npc, mob
+	private int flags; //1 = isIDed
+	private int numberOfItems;
+	private short durabilityCurrent;
+	private final short durabilityMax;
+	private final byte chargesMax;
+	private byte chargesRemaining;
+	private byte equipSlot;
+	private boolean canDestroy;
+	private boolean rentable;
+	private boolean isRandom = false;
+
+	private int value;
+
+	public Enum.ItemContainerType containerType;
+
+	private OwnerType ownerType;
+	private int itemBaseID;
+	private AbstractWorldObject lastOwner;
+	private ArrayList<EnchantmentBase> enchants = new ArrayList<>();
+	private final ConcurrentHashMap<AbstractEffectModifier, Float> bonuses = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW);
+	private final ArrayList<String> effectNames = new ArrayList<>();
+	private static ConcurrentHashMap<String, Integer> enchantValues = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW);
+	private long dateToUpgrade;
+	public ReentrantLock lootLock = new ReentrantLock();
+	private String customName = "";
+	private int magicValue;
+
+	/**
+	 * No Id Constructor
+	 */
+	public Item( ItemBase itemBase, int ownerID,
+				OwnerType ownerType, byte chargesMax, byte chargesRemaining,
+				short durabilityCurrent, short durabilityMax, boolean canDestroy,
+				boolean rentable, Enum.ItemContainerType containerType, byte equipSlot,
+				ArrayList<EnchantmentBase> enchants, String name) {
+		super();
+		this.itemBaseID = itemBase.getUUID();
+		this.ownerID = ownerID;
+		this.ownerType = ownerType;
+
+		if (itemBase.getType().getValue() == 20){
+			this.chargesMax = chargesMax;
+			this.chargesRemaining = chargesRemaining;
+		}
+		else{
+			this.chargesMax = (byte) itemBase.getNumCharges();
+			this.chargesRemaining = (byte) itemBase.getNumCharges();
+		}
+
+		this.durabilityMax = (short) itemBase.getDurability();
+		this.durabilityCurrent = (short) itemBase.getDurability();
+		this.containerType = containerType;
+		this.canDestroy = canDestroy;
+		this.rentable = rentable;
+		this.equipSlot = equipSlot;
+		this.enchants = enchants;
+		this.flags = 1;
+        this.value = this.magicValue;
+		this.customName = name;
+
+		loadEnchantments();
+		bakeInStats();
+	}
+
+	public Item( ItemBase itemBase, int ownerID,
+			OwnerType ownerType, byte chargesMax, byte chargesRemaining,
+			short durabilityCurrent, short durabilityMax, boolean canDestroy,
+			boolean rentable, boolean inBank, boolean inVault,
+			boolean inInventory, boolean isEquipped, boolean isForge, byte equipSlot,
+			ArrayList<EnchantmentBase> enchants) {
+
+		super();
+		this.itemBaseID = itemBase.getUUID();
+		this.ownerID = ownerID;
+		this.ownerType = ownerType;
+
+		this.chargesMax = (byte) itemBase.getNumCharges();
+		this.chargesRemaining = (byte) itemBase.getNumCharges();
+
+		this.durabilityMax = (short) itemBase.getDurability();
+		this.durabilityCurrent = (short) itemBase.getDurability();
+
+		this.canDestroy = canDestroy;
+		this.rentable = rentable;
+
+		this.equipSlot = equipSlot;
+		this.enchants = enchants;
+		this.flags = 1;
+
+        this.value = this.magicValue;
+
+		loadEnchantments();
+		bakeInStats();
+	}
+
+	/**
+	 * Normal Constructor
+	 */
+	public Item(ItemBase itemBase, int ownerID,
+			OwnerType ownerType, byte chargesMax, byte chargesRemaining,
+			short durabilityCurrent, short durabilityMax, boolean canDestroy,
+			boolean rentable, boolean inBank, boolean inVault,
+			boolean inInventory, boolean isEquipped, byte equipSlot,
+			ArrayList<EnchantmentBase> enchants, int newUUID) {
+
+		super(newUUID);
+		this.itemBaseID = itemBase.getUUID();
+		this.ownerID = ownerID;
+		this.ownerType = ownerType;
+		this.customName = "";
+
+		this.chargesMax = (byte) itemBase.getNumCharges();
+		this.chargesRemaining = (byte) itemBase.getNumCharges();
+
+		this.durabilityMax = (short) itemBase.getDurability();
+		this.durabilityCurrent = (short) itemBase.getDurability();
+		this.canDestroy = canDestroy;
+		this.rentable = rentable;
+		this.equipSlot = equipSlot;
+		this.enchants = enchants;
+		this.flags = 1;
+        this.value = this.magicValue;
+
+		loadEnchantments();
+		bakeInStats();
+	}
+	/**
+	 * ResultSet Constructor
+	 */
+	public Item(ResultSet rs) throws SQLException {
+		super(rs);
+
+		this.itemBaseID = rs.getInt("item_itemBaseID");
+
+		// Set container enumeration
+
+		String container = rs.getString("item_container");
+
+		switch (container) {
+			case "inventory":
+				this.containerType = Enum.ItemContainerType.INVENTORY;
+				break;
+			case "bank":
+				this.containerType = Enum.ItemContainerType.BANK;
+				break;
+			case "vault":
+				this.containerType = Enum.ItemContainerType.VAULT;
+				break;
+			case "equip":
+				this.containerType = Enum.ItemContainerType.EQUIPPED;
+				break;
+			case "forge":
+				this.containerType = Enum.ItemContainerType.FORGE;
+				break;
+			case "warehouse":
+				this.containerType = Enum.ItemContainerType.FORGE;
+				break;
+		}
+
+		this.ownerID = rs.getInt("parent");
+
+		if (this.getItemBase() != null)
+			this.chargesMax = (byte) this.getItemBase().getNumCharges();
+		else
+			this.chargesMax = 0;
+
+		this.chargesRemaining = rs.getByte("item_chargesRemaining");
+
+		this.durabilityCurrent = rs.getShort("item_durabilityCurrent");
+		this.durabilityMax = rs.getShort("item_durabilityMax");
+
+		String ot = DbManager.ItemQueries.GET_OWNER(this.ownerID);
+
+		if (ot.equals("character"))
+			this.ownerType = OwnerType.PlayerCharacter;
+		else if (ot.equals("npc"))
+			this.ownerType = OwnerType.Npc;
+		else if (ot.equals("account"))
+			this.ownerType = OwnerType.Account;
+
+		this.canDestroy = true;
+
+		this.equipSlot = rs.getByte("item_equipSlot");
+
+		this.numberOfItems = rs.getInt("item_numberOfItems");
+
+		this.flags = rs.getInt("item_flags");
+		this.dateToUpgrade = rs.getLong("item_dateToUpgrade");
+		this.value = rs.getInt("item_value");
+		this.customName = rs.getString("item_name");
+
+
+	}
+
+	public String getCustomName() {
+		return customName;
+	}
+
+	public void setName(String name) {
+		this.customName = name;
+	}
+
+	public ItemBase getItemBase() {
+		return ItemBase.getItemBase(itemBaseID);
+	}
+
+	public int getItemBaseID() {
+		return this.itemBaseID;
+	}
+
+	public int getOwnerID() {
+		return ownerID;
+	}
+
+	public OwnerType getOwnerType() {
+		return ownerType;
+	}
+
+	public AbstractGameObject getOwner() {
+		if (this.ownerType == OwnerType.Npc)
+			return NPC.getFromCache(this.ownerID);
+		else if (this.ownerType == OwnerType.PlayerCharacter)
+			return PlayerCharacter.getFromCache(this.ownerID);
+		else if (this.ownerType == OwnerType.Mob)
+			return Mob.getFromCache(this.ownerID);
+		else if (this.ownerType == OwnerType.Account)
+			return DbManager.AccountQueries.GET_ACCOUNT(this.ownerID);
+		else
+			return null;
+	}
+
+	//Only to be used for trading
+	public void setOwnerID(int ownerID) {
+		this.ownerID = ownerID;
+	}
+
+	public boolean setOwner(AbstractGameObject owner) {
+		if (owner == null)
+			return false;
+		if (owner.getObjectType().equals(GameObjectType.NPC))
+			this.ownerType = OwnerType.Npc;
+		else if (owner.getObjectType().equals(GameObjectType.PlayerCharacter))
+			this.ownerType = OwnerType.PlayerCharacter;
+		else if (owner.getObjectType().equals(GameObjectType.Mob))
+			this.ownerType = OwnerType.Mob;
+		else if (owner.getObjectType().equals(GameObjectType.Account))
+			this.ownerType = OwnerType.Account;
+		else
+			return false;
+		this.ownerID = owner.getObjectUUID();
+		return true;
+	}
+
+	public boolean isOwnerNPC() {
+		return (ownerType == OwnerType.Npc);
+	}
+
+	public boolean isOwnerCharacter() {
+		return (ownerType == OwnerType.PlayerCharacter);
+	}
+
+	public boolean isOwnerAccount() {
+		return (ownerType == OwnerType.Account);
+	}
+
+	public byte getChargesMax() {
+		return chargesMax;
+	}
+
+	public byte getChargesRemaining() {
+		return chargesRemaining;
+	}
+
+	public short getDurabilityCurrent() {
+		return durabilityCurrent;
+	}
+
+	public short getDurabilityMax() {
+		return durabilityMax;
+	}
+
+	public void setDurabilityCurrent(short value) {
+		this.durabilityCurrent = value;
+	}
+
+	public boolean isCanDestroy() {
+		return canDestroy;
+	}
+
+	public boolean isRentable() {
+		return rentable;
+	}
+
+	public byte getEquipSlot() {
+		return equipSlot;
+	}
+
+	public ArrayList<EnchantmentBase> getEnchants() {
+		return enchants;
+	}
+
+	public int getNumOfItems() {
+		return this.numberOfItems;
+	}
+
+	public synchronized void setNumOfItems(int numberOfItems) {
+		this.numberOfItems = numberOfItems;
+	}
+
+	public ConcurrentHashMap<AbstractEffectModifier, Float> getBonuses() {
+		return this.bonuses;
+	}
+
+	public void clearBonuses() {
+		this.bonuses.clear();
+	}
+
+	
+	public float getBonus(ModType modType, SourceType sourceType) {
+		
+		int amount = 0;
+		for (AbstractEffectModifier modifier: this.getBonuses().keySet()){
+			if (modifier.getPercentMod() != 0)
+				continue;
+			if (modifier.modType.equals(modType) == false || modifier.sourceType.equals(sourceType)== false)
+				continue;
+			amount += this.bonuses.get(modifier);
+		}
+		return amount;
+	}
+	
+public float getBonusPercent(ModType modType, SourceType sourceType) {
+		
+		int amount = 0;
+		for (AbstractEffectModifier modifier: this.getBonuses().keySet()){
+			
+			if (modifier.getPercentMod() == 0)
+				continue;
+			if (modifier.modType.equals(modType) == false || modifier.sourceType.equals(sourceType)== false)
+				continue;
+			amount += this.bonuses.get(modifier);
+		}
+		return amount;
+	}
+
+	public boolean isID() {
+		return ((this.flags & 1) > 0);
+	}
+
+	public void setIsID(boolean value) {
+		if (value)
+			this.flags |= 1;
+		else
+			this.flags &= ~1;
+	}
+
+	public void setIsComplete(boolean value) {
+		if (value)
+			this.flags |= 2;
+		else
+			this.flags &= ~2;
+	}
+
+	public boolean isComplete() {
+        return this.dateToUpgrade < System.currentTimeMillis() + 1000;
+    }
+
+	public String getContainerInfo() {
+		String ret = "OwnerID: " + this.ownerID + ", container: ";
+		ret += this.containerType.toString();
+		ret += "Equip Slot: " + this.equipSlot;
+		return ret;
+	}
+
+	public int getFlags() {
+		return this.flags;
+	}
+
+	public void setFlags(int value) {
+		this.flags = value;
+	}
+
+	public void addBonus(AbstractEffectModifier key, float amount) {
+		if (this.bonuses.containsKey(key))
+			this.bonuses.put(key, (this.bonuses.get(key) + amount));
+		else
+			this.bonuses.put(key, amount);
+	}
+
+	public void multBonus(AbstractEffectModifier key, float amount) {
+		if (this.bonuses.containsKey(key))
+			this.bonuses.put(key, (this.bonuses.get(key) * amount));
+		else
+			this.bonuses.put(key, amount);
+	}
+
+	public synchronized void decrementChargesRemaining() {
+		this.chargesRemaining -= 1;
+		if (this.chargesRemaining < 0)
+			this.chargesRemaining = 0;
+		DbManager.ItemQueries.UPDATE_REMAINING_CHARGES(this);
+	}
+
+	protected void validateItemContainer() {
+
+        if (this.containerType == Enum.ItemContainerType.NONE)
+
+			if (this.ownerID != 0)
+				// Item has an owner, just somehow the flags got messed up.
+				// Default to bank.
+				// TODO NEED LOG EVENT HERE.
+				this.containerType = Enum.ItemContainerType.BANK;
+			else
+				// This item is on the ground. Nothing to worry about.
+				this.zeroItem();
+	}
+
+	// Removes all ownership of item and 'orphans' it.
+	protected synchronized void junk() {
+
+		DbManager.ItemQueries.UPDATE_OWNER(this, 0, false, false, false, ItemContainerType.NONE, 0);
+		this.zeroItem();
+
+		//TODO do we want to delete the item here?
+		this.lastOwner = null;
+		//cleanup item from server.
+		this.removeFromCache();
+	}
+
+	public synchronized void zeroItem() {
+		this.ownerID = 0;
+
+		this.ownerType = null;
+		this.containerType = Enum.ItemContainerType.NONE;
+		this.equipSlot = MBServerStatics.SLOT_UNEQUIPPED;
+	}
+
+	protected synchronized boolean moveItemToInventory(PlayerCharacter pc) {
+		if (!DbManager.ItemQueries.UPDATE_OWNER(this,
+				pc.getObjectUUID(), //tableID
+				false, //isNPC
+				true, //isPlayer
+				false, //isAccount
+				ItemContainerType.INVENTORY,
+				0)) //Slot
+
+			return false;
+
+		this.zeroItem();
+		this.ownerID = pc.getObjectUUID();
+		this.ownerType = OwnerType.PlayerCharacter;
+		this.containerType = ItemContainerType.INVENTORY;
+		return true;
+	}
+
+	protected synchronized boolean moveItemToInventory(NPC npc) {
+		if (npc.isStatic()) {
+			if (!DbManager.ItemQueries.UPDATE_OWNER(this, 0, false, false, false,ItemContainerType.INVENTORY, 0))
+				return false;
+		} else
+			if (!DbManager.ItemQueries.UPDATE_OWNER(this,
+					npc.getObjectUUID(), //UUID
+					true, //isNPC
+					false, //isPlayer
+					false, //isAccount
+					ItemContainerType.INVENTORY,
+					0)) //Slot
+
+				return false;
+		this.zeroItem();
+		this.ownerID = npc.getObjectUUID();
+		this.ownerType = OwnerType.Npc;
+		this.containerType = Enum.ItemContainerType.INVENTORY;
+		return true;
+	}
+
+	protected synchronized boolean moveItemToInventory(Corpse corpse) {
+		if (!DbManager.ItemQueries.UPDATE_OWNER(this,
+				0, //no ID for corpse
+				false, //isNPC
+				true, //isPlayer
+				false, //isAccount
+				ItemContainerType.INVENTORY,
+				0)) //Slot
+
+			return false;
+		this.zeroItem();
+		this.ownerID = 0;
+		this.ownerType = null;
+		this.containerType = Enum.ItemContainerType.INVENTORY;
+		return true;
+	}
+
+	protected synchronized boolean moveItemToBank(PlayerCharacter pc) {
+		if (!DbManager.ItemQueries.UPDATE_OWNER(this,
+				pc.getObjectUUID(), //UUID
+				false, //isNPC
+				true, //isPlayer
+				false, //isAccount
+				ItemContainerType.BANK,
+				0)) //Slot
+
+			return false;
+		this.zeroItem();
+		this.ownerID = pc.getObjectUUID();
+		this.ownerType = OwnerType.PlayerCharacter;
+		this.containerType = Enum.ItemContainerType.BANK;
+		return true;
+	}
+
+	protected synchronized boolean moveItemToBank(NPC npc) {
+		if (!DbManager.ItemQueries.UPDATE_OWNER(this,
+				npc.getObjectUUID(), //UUID
+				true, //isNPC
+				false, //isPlayer
+				false, //isAccount
+				ItemContainerType.BANK,
+				0)) //Slot
+
+			return false;
+		this.zeroItem();
+		this.ownerID = npc.getObjectUUID();
+		this.ownerType = OwnerType.Npc;
+		this.containerType = Enum.ItemContainerType.BANK;
+		return true;
+	}
+
+	protected synchronized boolean moveItemToVault(Account a) {
+		if (!DbManager.ItemQueries.UPDATE_OWNER(this,
+				a.getObjectUUID(), //UUID
+				false, //isNPC
+				false, //isPlayer
+				true, //isAccount
+				ItemContainerType.VAULT,
+				0)) //Slot
+
+			return false;
+		this.zeroItem();
+		this.ownerID = a.getObjectUUID();
+		this.ownerType = OwnerType.Account;
+		this.containerType = Enum.ItemContainerType.VAULT;
+		return true;
+	}
+
+	protected synchronized boolean equipItem(PlayerCharacter pc, byte slot) {
+
+		if (!DbManager.ItemQueries.UPDATE_OWNER(this,
+				pc.getObjectUUID(), //tableID
+				false, //isNPC
+				true, //isPlayer
+				false, //isAccount
+				ItemContainerType.EQUIPPED,
+				slot)) //Slot
+
+			return false;
+		this.zeroItem();
+		this.ownerID = pc.getObjectUUID();
+		this.ownerType = OwnerType.PlayerCharacter;
+		this.containerType = Enum.ItemContainerType.EQUIPPED;
+		this.equipSlot = slot;
+		return true;
+	}
+
+	protected synchronized boolean equipItem(NPC npc, byte slot) {
+		if (!DbManager.ItemQueries.UPDATE_OWNER(this,
+				npc.getObjectUUID(), //UUID
+				true, //isNPC
+				false, //isPlayer
+				false, //isAccount
+				ItemContainerType.EQUIPPED,
+				slot)) //Slot
+
+			return false;
+		this.zeroItem();
+		this.ownerID = npc.getObjectUUID();
+		this.ownerType = OwnerType.Npc;
+		this.containerType = Enum.ItemContainerType.EQUIPPED;
+		this.equipSlot = slot;
+		return true;
+	}
+
+	protected synchronized boolean equipItem(Mob npc, byte slot) {
+
+		this.zeroItem();
+		this.ownerID = npc.getObjectUUID();
+		this.ownerType = OwnerType.Mob;
+		this.containerType = Enum.ItemContainerType.EQUIPPED;
+		this.equipSlot = slot;
+		return true;
+	}
+
+	
+	public static void _serializeForClientMsg(Item item, ByteBufferWriter writer)
+			throws SerializationException {
+		Item._serializeForClientMsg(item, writer, true);
+	}
+
+	public static void serializeForClientMsgWithoutSlot(Item item, ByteBufferWriter writer) {
+		Item._serializeForClientMsg(item, writer, false);
+	}
+
+	public static void serializeForClientMsgForVendor(Item item, ByteBufferWriter writer, float percent) {
+		Item._serializeForClientMsg(item, writer, true);
+        int baseValue = item.magicValue;
+		writer.putInt(baseValue);
+		writer.putInt((int) (baseValue * percent));
+	}
+
+	public static void serializeForClientMsgForVendorWithoutSlot(Item item,ByteBufferWriter writer, float percent) {
+		Item._serializeForClientMsg(item, writer, false);
+		writer.putInt(item.getValue());
+		writer.putInt(item.getValue());
+	}
+
+	public static void _serializeForClientMsg(Item item,ByteBufferWriter writer,
+			boolean includeSlot) {
+		if (includeSlot)
+			writer.putInt(item.equipSlot);
+		writer.putInt(0); // Pad
+		writer.putInt(item.getItemBase().getUUID());
+
+		writer.putInt(item.getObjectType().ordinal());
+		writer.putInt(item.getObjectUUID());
+
+		// Unknown statics
+		for (int i = 0; i < 3; i++) {
+			writer.putInt(0); // Pad
+		}
+		for (int i = 0; i < 4; i++) {
+			writer.putInt(0x3F800000); // Static
+		}
+		for (int i = 0; i < 5; i++) {
+			writer.putInt(0); // Pad
+		}
+		for (int i = 0; i < 2; i++) {
+			writer.putInt(0xFFFFFFFF); // Static
+		}
+
+		// Handle Hair / Beard / horns Color.
+		boolean isHair = (item.equipSlot == (byte) MBServerStatics.SLOT_HAIRSTYLE);
+		boolean isBeard = (item.equipSlot == (byte) MBServerStatics.SLOT_BEARDSTYLE);
+		int itemColor = 0;
+		if (isHair || isBeard) {
+            PlayerCharacter pc = PlayerCharacter.getFromCache(item.ownerID);
+			if (pc != null)
+				if (isHair)
+					itemColor = pc.getHairColor();
+				else if (isBeard)
+					itemColor = pc.getBeardColor();
+		}
+		writer.putInt(itemColor);
+
+		writer.put((byte) 1); // End Datablock byte
+		if (item.customName.isEmpty() || item.customName.isEmpty()){
+			writer.putInt(0);
+		}
+
+		else
+			writer.putString(item.customName); // Unknown. pad?
+		writer.put((byte) 1); // End Datablock byte
+
+		writer.putFloat((float)item.durabilityMax);
+		writer.putFloat((float)item.durabilityCurrent);
+
+		writer.put((byte) 1); // End Datablock byte
+
+		writer.putInt(0); // Pad
+		writer.putInt(0); // Pad
+
+		if (item.getItemBase().equals(ItemBase.GOLD_ITEM_BASE)){
+			
+			if (item.getOwner() != null && item.getOwner().getObjectType() == GameObjectType.PlayerCharacter){
+			PlayerCharacter player = (PlayerCharacter)item.getOwner();
+			int tradingAmount = player.getCharItemManager().getGoldTrading();
+			writer.putInt(item.numberOfItems - tradingAmount);
+			}else
+			writer.putInt(item.numberOfItems); // Amount of gold
+		}
+			
+		else
+			writer.putInt(item.getItemBase().getBaseValue());
+
+		writer.putInt(item.getValue());
+
+		int effectsSize = item.effects.size();
+		ArrayList<Effect> effs = null;
+		Effect nextE = null;
+		if (effectsSize > 0 && item.isID()) {
+			effs = new ArrayList<>(item.effects.values());
+
+			//Don't send effects that have a token of 1
+			Iterator<Effect> efi = effs.iterator();
+			while (efi.hasNext()) {
+				nextE = efi.next();
+				if (nextE.getEffectToken() == 1 || nextE.bakedInStat())
+					efi.remove();
+			}
+		} else
+			effs = new ArrayList<>();
+
+		int effectsToSendSize = effs.size();
+		writer.putInt(effectsToSendSize);
+		for (int i = 0; i < effectsToSendSize; i++) {
+			effs.get(i).serializeForItem(writer, item);
+		}
+		writer.putInt(0x00000000);
+
+
+		if (effectsSize > 0)
+			if (item.isID())
+				writer.putInt(36); //Magical, blue name
+			else
+				writer.putInt(40); //Magical, unidentified
+		else if (item.getItemBase().getBakedInStats().size() > 0)
+			writer.putInt(36); //Magical, blue name
+		else
+			writer.putInt(4); //Non-Magical, grey name
+		writer.putInt(item.chargesRemaining);
+		writer.putInt(0); // Pad
+		writer.putInt(item.numberOfItems);
+		writer.put((byte)0);
+
+
+		if (item.getItemBase().getType().getValue() != 20){
+			writer.putShort((short)0);
+			return;
+		}
+		writer.put((byte)1); //
+		writer.putInt(0);
+		writer.putInt(0);
+		if (item.chargesRemaining == 0)
+			writer.putInt(1);
+		else
+			writer.putInt(item.chargesRemaining);
+		writer.put((byte) 0);
+	}
+	
+	public static void SerializeTradingGold(PlayerCharacter player,ByteBufferWriter writer) {
+		
+		writer.putInt(0); // Pad
+		writer.putInt(7);
+
+		writer.putInt(GameObjectType.Item.ordinal());
+		writer.putInt(player.getObjectUUID());
+
+		// Unknown statics
+		for (int i = 0; i < 3; i++) {
+			writer.putInt(0); // Pad
+		}
+		for (int i = 0; i < 4; i++) {
+			writer.putInt(0x3F800000); // Static
+		}
+		for (int i = 0; i < 5; i++) {
+			writer.putInt(0); // Pad
+		}
+		for (int i = 0; i < 2; i++) {
+			writer.putInt(0xFFFFFFFF); // Static
+		}
+
+		// Handle Hair / Beard / horns Color.
+	
+		int itemColor = 0;
+		writer.putInt(itemColor);
+
+		writer.put((byte) 1); // End Datablock byte
+			writer.putInt(0);
+		writer.put((byte) 1); // End Datablock byte
+
+		writer.putFloat((float)1);
+		writer.putFloat((float)1);
+
+		writer.put((byte) 1); // End Datablock byte
+
+		writer.putInt(0); // Pad
+		writer.putInt(0); // Pad
+
+		
+			writer.putInt(player.getCharItemManager().getGoldTrading()); // Amount of gold
+	
+
+		writer.putInt(0);
+
+		
+		writer.putInt(0);
+	
+		writer.putInt(0x00000000);
+
+			writer.putInt(4); //Non-Magical, grey name
+		writer.putInt(1);
+		writer.putInt(0); // Pad
+		writer.putInt(player.getCharItemManager().getGoldTrading());
+		writer.put((byte)0);
+
+			writer.putShort((short)0);
+
+	}
+
+	public static boolean MakeItemForPlayer(ItemBase toCreate, PlayerCharacter reciever, int amount) {
+
+		boolean itemWorked = false;
+
+		Item item = new Item( toCreate, reciever.getObjectUUID(), OwnerType.PlayerCharacter, (byte) 0, (byte) 0,
+				(short) 1, (short) 1, true, false,  Enum.ItemContainerType.INVENTORY, (byte) 0,
+                new ArrayList<>(),"");
+
+		synchronized (item) {
+			item.numberOfItems = amount;
+		}
+		item.containerType = Enum.ItemContainerType.INVENTORY;
+
+		try {
+			item = DbManager.ItemQueries.ADD_ITEM(item);
+			itemWorked = true;
+		} catch (Exception e) {
+			Logger.error(e);
+		}
+
+		if (!itemWorked)
+			return false;
+
+		reciever.getCharItemManager().addItemToInventory(item);
+		reciever.getCharItemManager().updateInventory();
+
+		return true;
+	}
+
+	public static Item deserializeFromClientMsg(ByteBufferReader reader,
+			boolean includeSlot) {
+		if (includeSlot)
+			reader.getInt();
+		reader.getInt();
+		int itemBase = reader.getInt(); //itemBase
+		int objectType = reader.getInt(); //object type;
+		int UUID = reader.getInt();
+		for (int i = 0; i < 14; i++) {
+			reader.getInt(); // Pads and statics
+		}
+		int unknown = reader.getInt();
+		
+		byte readString = reader.get();
+		if (readString == 1)
+		reader.getString();
+		byte readDurability = reader.get();
+		if (readDurability == 1){
+			reader.getInt();
+			reader.getInt();
+		}
+		
+		byte readEnchants = reader.get();
+		if (readEnchants == 1){
+			reader.getInt();
+			reader.getInt();
+			reader.getInt();
+			reader.getInt();
+			int enchantSize = reader.getInt();
+			for (int i = 0; i < enchantSize; i++) {
+				reader.getInt(); //effect token
+				reader.getInt(); //trains
+				int type = reader.getInt();
+				reader.get();
+				if (type == 1)
+					reader.getLong(); //item comp
+				else
+					reader.getInt(); //power token
+				reader.getString(); //name
+				reader.getFloat(); //duration
+			}
+			for (int i = 0; i < 5; i++) {
+				reader.getInt();
+			}
+		}
+		
+		reader.get();
+		byte isContract = reader.get();
+		if (isContract == 1){
+			reader.getInt();
+			reader.getInt();
+			reader.getInt();
+		}
+		reader.get();
+
+		if (UUID == 0 || objectType == 0)
+			return null;
+		if (objectType == GameObjectType.MobLoot.ordinal())
+			return MobLoot.getFromCache(UUID);
+		return Item.getFromCache(UUID);
+	}
+
+	public final int getMagicValue() {
+		return this.magicValue;
+	}
+
+	public int getBaseValue() {
+		if (this.getItemBase() != null)
+			return this.getItemBase().getBaseValue();
+		return 0;
+	}
+
+	public static void putListForVendor(ByteBufferWriter writer, ArrayList<Item> list, NPC vendor) {
+		putList(writer, list, false, vendor.getObjectUUID(), true, vendor);
+	}
+
+	public static void putList(ByteBufferWriter writer, ArrayList<Item> list, boolean includeSlot, int ownerID) {
+		putList(writer, list, includeSlot, ownerID, false, null);
+	}
+
+	private static void putList(ByteBufferWriter writer, ArrayList<Item> list, boolean includeSlot, int ownerID, boolean forVendor, NPC vendor) {
+		int indexPosition = writer.position();
+		//reserve 4 bytes for index.
+		writer.putInt(0);
+
+		int serialized = 0;
+		for (Item item : list) {
+
+            if (item.getItemBase().getType().equals(ItemType.GOLD))
+				if (item.numberOfItems == 0)
+					continue;
+			try {
+				if (includeSlot && !forVendor)
+					Item._serializeForClientMsg(item,writer);
+				else if (!includeSlot && !forVendor)
+					Item.serializeForClientMsgWithoutSlot(item,writer);
+
+				if (!includeSlot && forVendor) //TODO separate for sell/buy percent
+
+					Item.serializeForClientMsgForVendorWithoutSlot(item,writer, vendor.getSellPercent());
+
+				if (includeSlot && forVendor) //TODO separate for sell/buy percent
+
+					Item.serializeForClientMsgForVendor(item,writer, vendor.getSellPercent());
+
+			} catch (SerializationException se) {
+				continue;
+			}
+			++serialized;
+		}
+
+		writer.putIntAt(serialized, indexPosition);
+	}
+	
+	public static void putTradingList(PlayerCharacter player, ByteBufferWriter writer, ArrayList<Item> list, boolean includeSlot, int ownerID, boolean forVendor, NPC vendor) {
+		int indexPosition = writer.position();
+		//reserve 4 bytes for index.
+		writer.putInt(0);
+
+		int serialized = 0;
+		for (Item item : list) {
+
+            if (item.getItemBase().getType().equals(ItemType.GOLD))
+				if (item.numberOfItems == 0)
+					continue;
+			try {
+				if (includeSlot && !forVendor)
+					Item._serializeForClientMsg(item,writer);
+				else if (!includeSlot && !forVendor)
+					Item.serializeForClientMsgWithoutSlot(item,writer);
+
+				if (!includeSlot && forVendor) //TODO separate for sell/buy percent
+
+					Item.serializeForClientMsgForVendorWithoutSlot(item,writer, vendor.getSellPercent());
+
+				if (includeSlot && forVendor) //TODO separate for sell/buy percent
+
+					Item.serializeForClientMsgForVendor(item,writer, vendor.getSellPercent());
+
+			} catch (SerializationException se) {
+				continue;
+			}
+			++serialized;
+		}
+		if (player.getCharItemManager().getGoldTrading() > 0){
+			Item.SerializeTradingGold(player, writer);
+			++serialized;
+		}
+		
+
+		writer.putIntAt(serialized, indexPosition);
+	}
+
+	public AbstractWorldObject getLastOwner() {
+		return this.lastOwner;
+	}
+
+	public void setLastOwner(AbstractWorldObject value) {
+		this.lastOwner = value;
+	}
+
+
+	@Override
+	public String getName() {
+		if (this.customName.isEmpty())
+			if (this.getItemBase() != null)
+				return this.getItemBase().getName();
+		return this.customName;
+	}
+
+
+
+	private void bakeInStats() {
+
+		EffectsBase effect;
+
+		if (ConfigManager.serverType.equals(Enum.ServerType.LOGINSERVER))
+			return;
+
+		if (this.getItemBase() != null)
+
+			for (Integer token : this.getItemBase().getBakedInStats().keySet()) {
+
+				effect = PowersManager.getEffectByToken(token);
+
+				if (effect == null) {
+					Logger.error("missing effect of token " + token);
+					continue;
+				}
+				AbstractPowerAction apa = PowersManager.getPowerActionByIDString(effect.getIDString());
+				apa.applyBakedInStatsForItem(this, this.getItemBase().getBakedInStats().get(token));
+			}
+	}
+
+	public final void loadEnchantments() {
+		//dont load mobloot enchantments, they arent in db.
+		if (this.getObjectType().equals(GameObjectType.MobLoot)){
+			this.magicValue =  this.getItemBase().getBaseValue() + calcMagicValue();
+			return;
+		}
+		
+
+		ConcurrentHashMap<String, Integer> enchantList = DbManager.EnchantmentQueries.GET_ENCHANTMENTS_FOR_ITEM(this.getObjectUUID());
+
+		for (String enchant : enchantList.keySet()) {
+			AbstractPowerAction apa = PowersManager.getPowerActionByIDString(enchant);
+			if (apa != null) {
+				apa.applyEffectForItem(this, enchantList.get(enchant));
+				this.effectNames.add(enchant);
+			}
+		}
+		
+		this.magicValue =  this.getItemBase().getBaseValue() + calcMagicValue();
+	}
+
+	public HashMap<Integer, Integer> getBakedInStats() {
+		if (this.getItemBase() != null)
+			return this.getItemBase().getBakedInStats();
+		return null;
+	}
+
+	public void clearEnchantments() {
+
+		//Clear permanent enchantment out of database
+		DbManager.EnchantmentQueries.CLEAR_ENCHANTMENTS((long) this.getObjectUUID());
+
+		for (String name : this.getEffects().keySet()) {
+			Effect eff = this.getEffects().get(name);
+			if (!eff.bakedInStat())
+				this.endEffect(name);
+		}
+		this.effectNames.clear();
+	}
+
+	public void addPermanentEnchantment(String enchantID, int rank) {
+		AbstractPowerAction apa = PowersManager.getPowerActionByIDString(enchantID);
+		if (apa == null)
+			return;
+
+		DbManager.EnchantmentQueries.CREATE_ENCHANTMENT_FOR_ITEM((long) this.getObjectUUID(), enchantID, rank);
+		apa.applyEffectForItem(this, rank);
+		this.effectNames.add(enchantID);
+	}
+
+	public void addPermanentEnchantmentForDev(String enchantID, int rank) {
+		AbstractPowerAction apa = PowersManager.getPowerActionByIDString(enchantID);
+		if (apa == null)
+			return;
+
+		DbManager.EnchantmentQueries.CREATE_ENCHANTMENT_FOR_ITEM((long) this.getObjectUUID(), enchantID, rank);
+		apa.applyEffectForItem(this, rank);
+		this.effectNames.add(enchantID);
+	}
+
+	protected int calcMagicValue() {
+		int ret = 0;
+		for (String enchant : this.effectNames) {
+			ret += Item.getEnchantValue(enchant+ 'A');
+		}
+		return ret;
+	}
+
+	public static Item createItemForPlayer(PlayerCharacter pc, ItemBase ib) {
+		Item item = null;
+		byte charges = 0;
+
+		charges = (byte) ib.getNumCharges();
+
+		short durability = (short) ib.getDurability();
+
+		Item temp = new Item( ib, pc.getObjectUUID(),
+				OwnerType.PlayerCharacter, charges, charges, durability, durability,
+				true, false,  Enum.ItemContainerType.INVENTORY, (byte) 0,
+                new ArrayList<>(),"");
+		try {
+			item = DbManager.ItemQueries.ADD_ITEM(temp);
+		} catch (Exception e) {
+			Logger.error(e);
+		}
+		return item;
+	}
+
+	public static Item createItemForPlayerBank(PlayerCharacter pc, ItemBase ib) {
+		Item item = null;
+		byte charges = 0;
+
+		charges = (byte) ib.getNumCharges();
+
+		short durability = (short) ib.getDurability();
+
+		Item temp = new Item( ib, pc.getObjectUUID(),
+				OwnerType.PlayerCharacter, charges, charges, durability, durability,
+				true, false, Enum.ItemContainerType.BANK, (byte) 0,
+                new ArrayList<>(),"");
+		try {
+			item = DbManager.ItemQueries.ADD_ITEM(temp);
+		} catch (Exception e) {
+		}
+		return item;
+	}
+
+	public static Item createItemForMob(Mob mob, ItemBase ib) {
+		Item item = null;
+		byte charges = 0;
+
+		charges = (byte) ib.getNumCharges();
+		short durability = (short) ib.getDurability();
+
+		Item temp = new Item( ib, mob.getObjectUUID(),
+				OwnerType.Mob, charges, charges, durability, durability,
+				true, false, Enum.ItemContainerType.INVENTORY, (byte) 0,
+                new ArrayList<>(),"");
+		try {
+			item = DbManager.ItemQueries.ADD_ITEM(temp);
+		} catch (Exception e) {
+			Logger.error(e);
+		}
+		return item;
+	}
+
+	public static Item getFromCache(int id) {
+		return (Item) DbManager.getFromCache(GameObjectType.Item, id);
+	}
+
+
+
+	public void addToCache() {
+		DbManager.addToCache(this);
+	}
+
+	public static Item newGoldItem(AbstractWorldObject awo, ItemBase ib, Enum.ItemContainerType containerType) {
+		return newGoldItem(awo, ib, containerType, true);
+	}
+
+	//used for vault!
+	public static Item newGoldItem(int accountID,ItemBase ib, Enum.ItemContainerType containerType) {
+		return newGoldItem(accountID, ib, containerType, true);
+	}
+
+	private static Item newGoldItem(int accountID, ItemBase ib, Enum.ItemContainerType containerType, boolean persist) {
+
+		int ownerID;
+		OwnerType ownerType;
+
+		ownerID = accountID;
+		ownerType = OwnerType.Account;
+
+
+		Item newGold = new Item( ib, ownerID, ownerType,
+				(byte) 0, (byte) 0, (short) 0, (short) 0, true, false,  containerType, (byte) 0,
+                new ArrayList<>(),"");
+
+		synchronized (newGold) {
+			newGold.numberOfItems = 0;
+		}
+
+		if (persist) {
+			try {
+				newGold = DbManager.ItemQueries.ADD_ITEM(newGold);
+				if (newGold != null) {
+					synchronized (newGold) {
+						newGold.numberOfItems = 0;
+					}
+				}
+			} catch (Exception e) {
+				Logger.error(e);
+			}
+			DbManager.ItemQueries.ZERO_ITEM_STACK(newGold);
+		}
+
+		return newGold;
+	}
+
+	private static Item newGoldItem(AbstractWorldObject awo, ItemBase ib, Enum.ItemContainerType containerType,boolean persist) {
+
+		int ownerID;
+		OwnerType ownerType;
+
+		if (awo.getObjectType().equals(GameObjectType.Mob))
+			return null;
+
+		if (containerType == Enum.ItemContainerType.VAULT) {
+			if (!(awo.getObjectType().equals(GameObjectType.PlayerCharacter))) {
+				Logger.error("AWO is not a PlayerCharacter");
+				return null;
+			}
+			ownerID = ((PlayerCharacter) awo).getAccount().getObjectUUID();
+			ownerType = OwnerType.Account;
+		} else {
+
+			ownerID = awo.getObjectUUID();
+
+			switch (awo.getObjectType()) {
+
+			case NPC:
+				ownerType = OwnerType.Npc;
+				break;
+			case PlayerCharacter:
+				ownerType = OwnerType.PlayerCharacter;
+				break;
+			case Mob:
+				ownerType = OwnerType.Mob;
+				break;
+			default:
+				Logger.error("Unsupported AWO object type.");
+				return null;
+			}
+		}
+
+		Item newGold = new Item( ib, ownerID, ownerType,
+				(byte) 0, (byte) 0, (short) 0, (short) 0, true, false, containerType, (byte) 0,
+                new ArrayList<>(),"");
+
+		synchronized (newGold) {
+			newGold.numberOfItems = 0;
+		}
+
+		if (persist) {
+			try {
+				newGold = DbManager.ItemQueries.ADD_ITEM(newGold);
+				if (newGold != null) {
+					synchronized (newGold) {
+						newGold.numberOfItems = 0;
+					}
+				}
+			} catch (Exception e) {
+				Logger.error(e);
+			}
+			DbManager.ItemQueries.ZERO_ITEM_STACK(newGold);
+		}
+		newGold.containerType = containerType;
+
+		return newGold;
+	}
+
+	// This is to be used for trades - the new item is not stored in the database
+	public static Item newGoldItemTemp(AbstractWorldObject awo, ItemBase ib) {
+		return Item.newGoldItem(awo, ib, Enum.ItemContainerType.NONE,false);
+	}
+
+	public static Item getItem(int UUID) {
+		if (UUID == 0)
+			return null;
+
+		Item item  = (Item) DbManager.getFromCache(GameObjectType.Item, UUID);
+		if (item != null)
+			return item;
+		return DbManager.ItemQueries.GET_ITEM(UUID);
+	}
+
+	@Override
+	public void updateDatabase() {
+		//DbManager.ItemQueries.updateDatabase(this);
+	}
+
+	public static void addEnchantValue(String enchant, int value) {
+		Item.enchantValues.put(enchant, value);
+	}
+
+	public static int getEnchantValue(String enchant) {
+		if (Item.enchantValues.containsKey(enchant))
+			return Item.enchantValues.get(enchant);
+		return 0;
+	}
+
+	@Override
+	public void runAfterLoad() {
+		loadEnchantments();
+		bakeInStats();
+	}
+
+
+	public ArrayList<String> getEffectNames() {
+		return effectNames;
+	}
+
+	public boolean validForItem(long flags) {
+		if (this.getItemBase() == null)
+			return false;
+		return this.getItemBase().validSlotFlag(flags);
+	}
+
+	public boolean validForInventory(ClientConnection origin, PlayerCharacter pc, CharacterItemManager charItemMan) {
+
+		if (origin == null || pc == null || charItemMan == null)
+			return false;
+
+        if (ownerID != pc.getObjectUUID()) {
+			Logger.warn("Inventory Item " + this.getObjectUUID() + " not owned by Character " + charItemMan.getOwner().getObjectUUID());
+			charItemMan.updateInventory();
+			return false;
+		}
+
+		if (!charItemMan.inventoryContains(this)){
+			charItemMan.updateInventory();
+			return false;
+		}
+		return true;
+	}
+
+	public boolean validForBank(ClientConnection origin, PlayerCharacter pc, CharacterItemManager charItemMan) {
+		if (origin == null || pc == null || charItemMan == null)
+			return false;
+
+        if (!charItemMan.bankContains(this))
+			return false;
+		else if (ownerID != pc.getObjectUUID()) {
+			Logger.warn("Bank Item " + this.getObjectUUID() + " not owned by Character " + charItemMan.getOwner().getObjectUUID());
+			return false;
+		}
+		return true;
+	}
+
+	public boolean validForEquip(ClientConnection origin, PlayerCharacter pc, CharacterItemManager charItemMan) {
+		if (origin == null || pc == null || charItemMan == null)
+			return false;
+
+        if (!charItemMan.equippedContains(this))
+			return false;
+		else if (ownerID != pc.getObjectUUID()) {
+			//duped item, cleanup
+			Logger.warn("Duped item id "
+					+ this.getObjectUUID() + " removed from PC " + pc.getObjectUUID() + '.');
+			DeleteItemMsg deleteItemMsg = new DeleteItemMsg(this.getObjectType().ordinal(), this.getObjectUUID());
+			charItemMan.cleanupDupe(this);
+			Dispatch dispatch = Dispatch.borrow(pc, deleteItemMsg);
+			DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+
+			return false;
+		}
+		return true;
+	}
+
+	public boolean validForVault(ClientConnection origin, PlayerCharacter pc, CharacterItemManager charItemMan) {
+		if (origin == null || pc == null || charItemMan == null)
+			return false;
+
+		if (pc.getAccount() == null)
+			return false;
+
+        if (!pc.getAccount().getVault().contains(this))
+			return false;
+		else if (ownerID != pc.getAccount().getObjectUUID()) {
+			//duped item, cleanup
+			Logger.warn("Duped item id "
+					+ this.getObjectUUID() + " removed from PC " + pc.getObjectUUID() + '.');
+			DeleteItemMsg deleteItemMsg = new DeleteItemMsg(this.getObjectType().ordinal(), this.getObjectUUID());
+			charItemMan.cleanupDupe(this);
+			Dispatch dispatch = Dispatch.borrow(pc, deleteItemMsg);
+			DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+			return false;
+		}
+		return true;
+	}
+
+	public long getDateToUpgrade() {
+		return dateToUpgrade;
+	}
+
+	public void setDateToUpgrade(long dateToUpgrade) {
+		this.dateToUpgrade = dateToUpgrade;
+	}
+
+	/**
+	 * @return the value
+	 */
+	public int getValue() {
+
+		if (this.value == 0)
+			if (this.isID()) {
+                return this.getMagicValue();
+            }
+			else
+				return this.getBaseValue();
+
+		return this.value;
+	}
+
+	/**
+	 * @param value the value to set
+	 */
+	public void setValue(int value) {
+		this.value = value;
+	}
+
+	public boolean isRandom() {
+		return isRandom;
+	}
+
+	public void setRandom(boolean isRandom) {
+		this.isRandom = isRandom;
+	}
+	
+	public boolean isCustomValue(){
+		if (this.value == 0)
+			return false;
+		return true;
+	}
+}
diff --git a/src/engine/objects/ItemBase.java b/src/engine/objects/ItemBase.java
new file mode 100644
index 00000000..f6b0dee4
--- /dev/null
+++ b/src/engine/objects/ItemBase.java
@@ -0,0 +1,918 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+
+
+
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.objects;
+
+import engine.Enum.DamageType;
+import engine.Enum.GameObjectType;
+import engine.Enum.ItemType;
+import engine.gameManager.DbManager;
+import engine.server.MBServerStatics;
+import org.pmw.tinylog.Logger;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class ItemBase {
+
+	public static final byte GOLD_BASE_TYPE = 4;
+	public static ItemBase GOLD_ITEM_BASE = null;
+	public static int GOLD_BASE_ID = 7;
+	public static ArrayList<Integer> AnniverseryGifts = new ArrayList<>();
+	// Internal cache
+	private static HashMap<Integer, Integer> itemHashIDMap = new HashMap<>();
+	private static HashMap<String, Integer> _IDsByNames = new HashMap<>();
+	public static HashMap<Integer, ItemBase> _itemBaseByUUID = new HashMap<>();
+	private static ArrayList<ItemBase> _resourceList = new ArrayList<>();
+	private final int uuid;
+	private final String name;
+	private float durability;
+	private int value;
+	private short weight;
+	private short color;
+	private ItemType type;
+	private int vendorType;
+	private int modTable;
+	private int useID;
+	private int hashID;
+	private byte useAmount;
+	// Armor and weapon related values
+	private int equipFlag;
+	private int restrictFlag;
+	private String skillRequired;
+	private short percentRequired;
+	private float slashResist;
+	private float crushResist;
+	private float pierceResist;
+	private float blockMod;
+	private short defense;
+	private float dexPenalty;
+	private float speed;
+	private float range;
+	private short minDamage;
+	private short maxDamage;
+	private String mastery;
+	private engine.Enum.DamageType damageType;
+	private boolean twoHanded;
+	private boolean isConsumable;
+	private boolean isStackable;
+	private int numCharges;
+	// Item stat modifiers
+	private HashMap<Integer, Integer> bakedInStats = new HashMap<>();
+	private HashMap<Integer, Integer> usedStats = new HashMap<>();
+	private float parryBonus;
+	private boolean isStrBased;
+	private ArrayList<Integer> animations = new ArrayList<>();
+	private ArrayList<Integer> offHandAnimations = new ArrayList<>();
+	private boolean autoID = false;
+	public static HashMap<engine.Enum.ItemType, HashSet<ItemBase>> ItemBaseTypeMap = new HashMap<>();
+	/**
+	 * ResultSet Constructor
+	 */
+	public ItemBase(ResultSet rs) throws SQLException {
+
+		this.uuid = rs.getInt("ID");
+		this.name = rs.getString("name");
+		this.durability = rs.getInt("durability");
+		this.value = rs.getInt("value");
+		this.weight = rs.getShort("weight");
+		this.color = rs.getShort("color");
+		this.type = ItemType.valueOf(rs.getString("Type"));
+		this.useID = rs.getInt("useID");
+		this.vendorType = rs.getInt("vendorType");
+		this.useAmount = rs.getByte("useAmount");
+		this.modTable = rs.getInt("modTable");
+		this.hashID = rs.getInt("itemHashID");
+
+		this.isConsumable = false;
+		this.isStackable = false;
+		this.numCharges = rs.getShort("numCharges");
+
+		this.equipFlag = rs.getInt("equipFlag");
+		this.restrictFlag = rs.getInt("restrictFlag");
+		this.skillRequired = rs.getString("skillRequired");
+		this.percentRequired = rs.getShort("percentRequired");
+		this.slashResist = rs.getFloat("slashResist");
+		this.crushResist = rs.getFloat("crushResist");
+		this.pierceResist = rs.getFloat("pierceResist");
+		this.blockMod = rs.getFloat("blockMod");
+		this.defense = rs.getShort("defense");
+		this.dexPenalty = rs.getFloat("dexPenalty");
+		this.parryBonus = rs.getFloat("parryBonus");
+		this.isStrBased = (rs.getInt("isStrBased") == 1);
+		this.speed = rs.getFloat("speed");
+		this.range = rs.getFloat("range");
+		this.minDamage = rs.getShort("minDamage");
+		this.maxDamage = rs.getShort("maxDamage");
+
+		this.mastery = rs.getString("mastery");
+		damageType = DamageType.valueOf(rs.getString("damageType"));
+
+		this.twoHanded = (rs.getInt("twoHanded") == 1);
+
+		switch (this.type) {
+		case RUNE:
+		case SCROLL:
+		case COMMANDROD:
+		case POTION:
+		case TEARS:
+		case GUILDCHARTER:
+		case DEED:
+		case CONTRACT:
+		case WATERBUCKET:
+		case REALMCHARTER:
+		case GIFT:
+			this.isConsumable = true;
+			break;
+		case OFFERING:
+			this.isConsumable = true;
+			Boon.HandleBoonListsForItemBase(uuid);
+			break;
+		case RESOURCE:
+			this.isStackable = true;
+			break;
+
+		}
+
+		this.autoIDItemsCheck();
+
+		try{
+			DbManager.ItemBaseQueries.LOAD_ANIMATIONS(this);
+		}catch(Exception e){
+			Logger.error( e.getMessage());
+		}
+		initBakedInStats();
+		initializeHashes();
+
+	}
+
+	public static void addToCache(ItemBase itemBase) {
+
+		_itemBaseByUUID.put(itemBase.uuid, itemBase);
+
+		if (itemBase.type.equals(ItemType.RESOURCE))
+			_resourceList.add(itemBase);
+
+		_IDsByNames.put(itemBase.name.toLowerCase().replace(" ", "_"), itemBase.uuid);
+	}
+
+	public static HashMap<Integer, Integer> getItemHashIDMap() {
+		return itemHashIDMap;
+	}
+
+	/*
+	 * Database
+	 */
+	public static ItemBase getItemBase(int uuid) {
+
+		return _itemBaseByUUID.get(uuid);
+	}
+
+	/**
+	 * Get the ItemBase instance for Gold.
+	 *
+	 * @return ItemBase for Gold
+	 */
+	public static ItemBase getGoldItemBase() {
+		if (ItemBase.GOLD_ITEM_BASE == null)
+			ItemBase.GOLD_ITEM_BASE = getItemBase(7);
+		return ItemBase.GOLD_ITEM_BASE;
+	}
+
+	public static int getIDByName(String name) {
+		if (ItemBase._IDsByNames.containsKey(name))
+			return ItemBase._IDsByNames.get(name);
+		return 0;
+	}
+
+	/**
+	 * @return the _itemBaseByUUID
+	 */
+	public static HashMap<Integer, ItemBase> getUUIDCache() {
+		return _itemBaseByUUID;
+	}
+
+	/**
+	 * @return the _resourceList
+	 */
+	public static ArrayList<ItemBase> getResourceList() {
+		return _resourceList;
+	}
+
+	public static void loadAllItemBases() {
+		DbManager.ItemBaseQueries.LOAD_ALL_ITEMBASES();
+		AnniverseryGifts.add(971000);
+		AnniverseryGifts.add(971001);
+		AnniverseryGifts.add(971002);
+		AnniverseryGifts.add(971003);
+		AnniverseryGifts.add(971004);
+		AnniverseryGifts.add(971005);
+		AnniverseryGifts.add(971006);
+		AnniverseryGifts.add(971007);
+		AnniverseryGifts.add(971008);
+		AnniverseryGifts.add(971009);
+		AnniverseryGifts.add(971010);
+		AnniverseryGifts.add(5101000);
+		AnniverseryGifts.add(5101020);
+		AnniverseryGifts.add(5101100);
+		AnniverseryGifts.add(5101120);
+		AnniverseryGifts.add(5101040);
+		AnniverseryGifts.add(5101140);
+		AnniverseryGifts.add(5101060);
+		AnniverseryGifts.add(5101080);
+
+
+	}
+
+	/*
+	 * Getters
+	 */
+	public String getName() {
+		return this.name;
+	}
+
+	public float getDurability() {
+		return this.durability;
+	}
+
+	private void initBakedInStats() {
+		DbManager.ItemBaseQueries.LOAD_BAKEDINSTATS(this);
+	}
+
+	//TODO fix this later. Shouldn't be gotten from item base
+	public int getMagicValue() {
+		return this.value;
+	}
+
+	public int getBaseValue() {
+		return this.value;
+	}
+
+	public short getWeight() {
+		return this.weight;
+	}
+
+	public int getColor() {
+		return this.color;
+	}
+
+	public boolean isConsumable() {
+		return this.isConsumable;
+	}
+
+	public boolean isStackable() {
+		return this.isStackable;
+	}
+
+	public int getNumCharges() {
+
+		return this.numCharges;
+
+	}
+
+	public int getEquipFlag() {
+
+		if ((this.type == ItemType.ARMOR)
+				|| (this.type == ItemType.WEAPON)
+				|| (this.type == ItemType.JEWELRY))
+			return this.equipFlag;
+		else
+			return 0;
+	}
+
+	public boolean isRune() {
+		int ID = uuid;
+		if (ID > 2499 && ID < 3050) //class, discipline runes
+			return true;
+		else return ID > 249999 && ID < 252137;
+	}
+
+	public boolean isStatRune() {
+		int ID = uuid;
+		return ID > 249999 && ID < 250045;
+	}
+
+	public boolean isGlass() {
+		int ID = uuid;
+		return ID > 7000099 && ID < 7000281;
+	}
+
+
+	public boolean isMasteryRune() {
+		int ID = uuid;
+		if (ID > 250114 && ID < 252128)
+			switch (ID) {
+			case 250115:
+			case 250118:
+			case 250119:
+			case 250120:
+			case 250121:
+			case 250122:
+			case 252123:
+			case 252124:
+			case 252125:
+			case 252126:
+			case 252127:
+				return true;
+			default:
+				return false;
+			}
+		return false;
+	}
+
+	//returns powers tokens baked in to item
+	public HashMap<Integer, Integer> getBakedInStats() {
+		return this.bakedInStats;
+	}
+
+	//returns power tokens granted when using item, such as scrolls and potions
+	public HashMap<Integer, Integer> getUsedStats() {
+		return this.usedStats;
+	}
+
+	public final void initializeHashes() {
+		itemHashIDMap.put(this.hashID, uuid);
+
+	}
+
+	public ItemType getType() {
+		return this.type;
+	}
+
+	public int getUseID() {
+		return this.useID;
+	}
+
+	public byte getUseAmount() {
+		return this.useAmount;
+	}
+
+	public int getModTable() {
+		return modTable;
+	}
+
+	public int getVendorType() {
+		return vendorType;
+	}
+
+	public void setVendorType(int vendorType) {
+		this.vendorType = vendorType;
+	}
+
+	public int getHashID() {
+		return hashID;
+	}
+
+	public void setHashID(int hashID) {
+		this.hashID = hashID;
+	}
+
+	private void autoIDItemsCheck(){
+		//AUto ID Vorg and Glass
+		switch (uuid){
+
+		case 27550:
+		case 27560:
+		case 27580:
+		case 27590:
+		case 188500:
+		case 188510:
+		case 188520:
+		case 188530:
+		case 188540:
+		case 188550:
+		case 189100:
+		case 189110:
+		case 189120:
+		case 189130:
+		case 189140:
+		case 189150:
+		case 189510:
+		case 27600:
+		case 181840:
+		case 188700:
+		case 188720:
+		case 189550:
+		case 189560:
+		case 7000100:
+		case 7000110:
+		case 7000120:
+		case 7000130:
+		case 7000140:
+		case 7000150:
+		case 7000160:
+		case 7000170:
+		case 7000180:
+		case 7000190:
+		case 7000200:
+		case 7000210:
+		case 7000220:
+		case 7000230:
+		case 7000240:
+		case 7000250:
+		case 7000270:
+		case 7000280:
+			this.autoID = true;
+			break;
+		default:
+			this.autoID = false;
+		}
+	}
+
+	public boolean validForSkills(ConcurrentHashMap<String, CharacterSkill> skills) {
+
+		CharacterSkill characterSkill;
+
+		if (this.skillRequired.isEmpty())
+			return true;
+
+		characterSkill = skills.get(this.skillRequired);
+
+		if (characterSkill == null)
+			return false;
+		
+		return !(this.percentRequired > characterSkill.getModifiedAmountBeforeMods());
+	}
+
+	public boolean canEquip(int slot, CharacterItemManager itemManager, AbstractCharacter abstractCharacter, Item item) {
+
+		if (itemManager == null || abstractCharacter == null)
+			return false;
+
+		if (abstractCharacter.getObjectType().equals(GameObjectType.PlayerCharacter)) {
+
+			if (!validForSlot(slot, itemManager.getEquipped(), item))
+				return false;
+
+			if (!validForSkills(abstractCharacter.getSkills()))
+				return false;
+
+			return item.getItemBase().value != 0 || Kit.IsNoobGear(item.getItemBase().uuid);
+			//players can't wear 0 value items.
+
+		}
+
+		return true; //Mobiles and NPC's don't need to check equip
+	}
+
+	public int getValidSlot() {
+		int slotValue = 0;
+
+		switch (this.type) {
+		case WEAPON:
+			if ((this.equipFlag & 1) != 0)
+				slotValue = MBServerStatics.SLOT_MAINHAND;
+			else if ((this.equipFlag & 2) != 0)
+				slotValue = MBServerStatics.SLOT_OFFHAND;
+			break;
+		case ARMOR:
+			if ((this.equipFlag & 2) != 0)
+				slotValue = MBServerStatics.SLOT_OFFHAND;
+			else if ((this.equipFlag & 4) != 0)
+				slotValue = MBServerStatics.SLOT_HELMET;
+			else if ((this.equipFlag & 8) != 0)
+				slotValue = MBServerStatics.SLOT_CHEST;
+			else if ((this.equipFlag & 16) != 0)
+				slotValue = MBServerStatics.SLOT_ARMS;
+			else if ((this.equipFlag & 32) != 0)
+				slotValue = MBServerStatics.SLOT_GLOVES;
+			else if ((this.equipFlag & 64) != 0)
+				slotValue = MBServerStatics.SLOT_RING2;
+			else if ((this.equipFlag & 128) != 0)
+				slotValue = MBServerStatics.SLOT_RING1;
+			else if ((this.equipFlag & 256) != 0)
+				slotValue = MBServerStatics.SLOT_NECKLACE;
+			else if ((this.equipFlag & 512) != 0)
+				slotValue = MBServerStatics.SLOT_LEGGINGS;
+			else if ((this.equipFlag & 1024) != 0)
+				slotValue = MBServerStatics.SLOT_FEET;
+			break;
+
+		case HAIR:
+			if (this.equipFlag == 131072)
+				slotValue = MBServerStatics.SLOT_HAIRSTYLE;
+			else if(this.equipFlag == 65536)
+				slotValue = MBServerStatics.SLOT_BEARDSTYLE;
+			break;
+
+		}
+		return slotValue;
+
+	}
+
+	public boolean validSlotFlag(long flags) {
+
+		boolean validSlot = false;
+
+		switch (this.type) {
+		case WEAPON:
+			if (this.isMelee())
+				validSlot = ((flags & 1) != 0);
+			else if (this.isThrowing())
+				validSlot = ((flags & 2) != 0);
+			else if (this.isArchery())
+				validSlot = ((flags & 4) != 0);
+			else if (this.isScepter())
+				validSlot = ((flags & 8) != 0);
+			else if (this.isStaff())
+				validSlot = ((flags & 16) != 0);
+			break;
+		case JEWELRY:
+			if (this.isNecklace())
+				validSlot = ((flags & 2147483648L) != 0L);
+			else
+				validSlot = ((flags & 4294967296L) != 0L);
+			break;
+		case ARMOR:
+
+			if (this.isShield()) {
+				validSlot = ((flags & 32) != 0);
+				break;
+			}
+
+			if (this.isClothArmor()) {
+
+				if (this.getEquipFlag() == 4) //hood
+					validSlot = ((flags & 64) != 0);
+				else if (this.getEquipFlag() == 8) {
+					if ((restrictFlag & 512) != 0) //Robe
+						validSlot = ((flags & 128) != 0);
+					else
+						validSlot = ((flags & 1024) != 0); //Tunic/Shirt
+
+					break;
+				} else if (this.getEquipFlag() == 16) //Sleeves
+					validSlot = ((flags & 2048) != 0);
+				else if (this.getEquipFlag() == 32) //Gloves
+					validSlot = ((flags & 512) != 0);
+				else if (this.getEquipFlag() == 512) //Pants
+					validSlot = ((flags & 4096) != 0);
+				else if (this.getEquipFlag() == 1024) //Boots
+					validSlot = ((flags & 256) != 0);
+
+				break;
+			}
+
+			if (this.isLightArmor()) {
+				if (this.getEquipFlag() == 4) //helm
+					validSlot = ((flags & 8192) != 0);
+				else if (this.getEquipFlag() == 8) //Chest
+					validSlot = ((flags & 16384) != 0);
+				else if (this.getEquipFlag() == 16) //Sleeves
+					validSlot = ((flags & 32768) != 0);
+				else if (this.getEquipFlag() == 32) //Gloves
+					validSlot = ((flags & 65536) != 0);
+				else if (this.getEquipFlag() == 512) //Pants
+					validSlot = ((flags & 131072) != 0);
+				else if (this.getEquipFlag() == 1024) //Boots
+					validSlot = ((flags & 262144) != 0);
+
+				break;
+			}
+
+			if (this.isMediumArmor()) {
+				if (this.getEquipFlag() == 4) //helm
+					validSlot = ((flags & 524288) != 0);
+				else if (this.getEquipFlag() == 8) //Chest
+					validSlot = ((flags & 1048576) != 0);
+				else if (this.getEquipFlag() == 16) //Sleeves
+					validSlot = ((flags & 2097152) != 0);
+				else if (this.getEquipFlag() == 32) //Gloves
+					validSlot = ((flags & 4194304) != 0);
+				else if (this.getEquipFlag() == 512) //Pants
+					validSlot = ((flags & 8388608) != 0);
+				else if (this.getEquipFlag() == 1024) //Boots
+					validSlot = ((flags & 16777216) != 0);
+
+				break;
+			}
+
+			if (this.isHeavyArmor())
+				if (this.getEquipFlag() == 4) //helm
+					validSlot = ((flags & 33554432) != 0);
+				else if (this.getEquipFlag() == 8) //Chest
+					validSlot = ((flags & 67108864) != 0);
+				else if (this.getEquipFlag() == 16) //Sleeves
+					validSlot = ((flags & 134217728) != 0);
+				else if (this.getEquipFlag() == 32) //Gloves
+					validSlot = ((flags & 268435456) != 0);
+				else if (this.getEquipFlag() == 512) //Pants
+					validSlot = ((flags & 536870912) != 0);
+				else if (this.getEquipFlag() == 1024) //Boots
+					validSlot = ((flags & 1073741824) != 0);
+			break;
+		}
+		return validSlot;
+	}
+
+	public boolean validForSlot(int slot, ConcurrentHashMap<Integer, Item> equipped, Item item) {
+
+		boolean validSlot = false;
+
+		if (equipped == null)
+			return validSlot;
+
+		// Cannot equip an item in a slot already taken
+		if (equipped.get(slot) != null && equipped.get(slot).equals(item) == false)
+			return validSlot;
+
+		switch (item.getItemBase().type) {
+		case WEAPON:
+
+			// Only two slots available for weapons
+			if ((slot != MBServerStatics.SLOT_MAINHAND) && (slot != MBServerStatics.SLOT_OFFHAND))
+				break;
+
+			//make sure weapon is valid for slot
+			if ((slot & this.equipFlag) == 0)
+				break;
+
+			// Two handed weapons take up two slots
+			if ((this.twoHanded == true) &&
+					((slot == MBServerStatics.SLOT_OFFHAND && equipped.get(MBServerStatics.SLOT_MAINHAND) != null) ||
+							(slot == MBServerStatics.SLOT_MAINHAND && equipped.get(MBServerStatics.SLOT_OFFHAND) != null)))
+				break;
+
+			// Validation passed, must be a valid weapon
+
+			validSlot = true;
+			break;
+		case JEWELRY:
+			// Not a valid slot for ring
+
+			if (this.isRing() &&
+					((slot != MBServerStatics.SLOT_RING1) && (slot != MBServerStatics.SLOT_RING2)))
+				break;
+
+			// Not a valid slot for necklace
+
+			if (this.isNecklace() && slot != MBServerStatics.SLOT_NECKLACE)
+				break;
+
+			// Passed validation, must be valid bling bling
+
+			validSlot = true;
+			break;
+		case ARMOR:
+
+			// Invalid slot for armor?
+			if (slot == MBServerStatics.SLOT_OFFHAND && ((2 & this.equipFlag) == 0))
+				break;
+			if (slot == MBServerStatics.SLOT_HELMET && ((4 & this.equipFlag) == 0))
+				break;
+			if (slot == MBServerStatics.SLOT_CHEST && ((8 & this.equipFlag) == 0))
+				break;
+			if (slot == MBServerStatics.SLOT_ARMS && ((16 & this.equipFlag) == 0))
+				break;
+			if (slot == MBServerStatics.SLOT_GLOVES && ((32 & this.equipFlag) == 0))
+				break;
+			if (slot == MBServerStatics.SLOT_LEGGINGS && ((512 & this.equipFlag) == 0))
+				break;
+			if (slot == MBServerStatics.SLOT_FEET && ((1024 & this.equipFlag) == 0))
+				break;
+
+			// Is slot for this piece already taken?
+			if (((this.restrictFlag & 2) != 0) && (equipped.get(MBServerStatics.SLOT_OFFHAND) != null) && slot != MBServerStatics.SLOT_OFFHAND)
+				break;
+			if (((this.restrictFlag & 4) != 0) && (equipped.get(MBServerStatics.SLOT_HELMET) != null) && slot != MBServerStatics.SLOT_HELMET)
+				break;
+			if (((this.restrictFlag & 8) != 0) && (equipped.get(MBServerStatics.SLOT_CHEST) != null) && slot != MBServerStatics.SLOT_CHEST)
+				break;
+			if (((this.restrictFlag & 16) != 0) && (equipped.get(MBServerStatics.SLOT_ARMS) != null) && slot != MBServerStatics.SLOT_ARMS)
+				break;
+			if (((this.restrictFlag & 32) != 0) && (equipped.get(MBServerStatics.SLOT_GLOVES) != null) && slot != MBServerStatics.SLOT_GLOVES)
+				break;
+			if (((this.restrictFlag & 512) != 0) && (equipped.get(MBServerStatics.SLOT_LEGGINGS) != null) && slot != MBServerStatics.SLOT_LEGGINGS)
+				break;
+			if (((this.restrictFlag & 1024) != 0) && (equipped.get(MBServerStatics.SLOT_FEET) != null) && slot != MBServerStatics.SLOT_FEET)
+				break;
+
+			// Passed validation.  Is a valid armor piece
+
+			validSlot = true;
+			break;
+		}
+		return validSlot;
+	}
+
+	/**
+	 * @return the uuid
+	 */
+	public final int getUUID() {
+		return uuid;
+	}
+
+	public boolean isRing() {
+		return ((this.equipFlag & (64 | 128 | 192)) != 0);
+	}
+
+	public boolean isNecklace() {
+		return (this.equipFlag == 256);
+	}
+
+	public boolean isShield() {
+		return this.type.equals(ItemType.ARMOR) && this.equipFlag == 2;
+	}
+
+	public boolean isLightArmor() {
+		return this.skillRequired.equals("Wear Armor, Light");
+	}
+
+	public boolean isMediumArmor() {
+		return this.skillRequired.equals("Wear Armor, Medium");
+	}
+
+	public boolean isHeavyArmor() {
+		return this.skillRequired.equals("Wear Armor, Heavy");
+	}
+
+	public boolean isClothArmor() {
+		return this.skillRequired.isEmpty();
+	}
+
+	public boolean isThrowing() {
+		return this.mastery.equals("Throwing") ? true : false;
+	}
+
+	public boolean isStaff() {
+		return this.mastery.equals("Staff") ? true : false;
+	}
+
+	public boolean isScepter() {
+		return this.mastery.equals("Benediction") ? true : false;
+	}
+
+	public boolean isArchery() {
+		return this.mastery.equals("Archery") ? true : false;
+	}
+
+	public boolean isMelee() {
+		return (this.isThrowing() == false && this.isStaff() == false && this.isScepter() == false && this.isArchery() == false);
+	}
+
+	public boolean isTwoHanded() {
+		return this.twoHanded;
+	}
+
+	/**
+	 * @return the restrictFlag
+	 */
+	public int getRestrictFlag() {
+		return restrictFlag;
+	}
+
+	/**
+	 * @return the slashResist
+	 */
+	public float getSlashResist() {
+		return slashResist;
+	}
+
+	/**
+	 * @return the crushResist
+	 */
+	public float getCrushResist() {
+		return crushResist;
+	}
+
+	/**
+	 * @return the pierceResist
+	 */
+	public float getPierceResist() {
+		return pierceResist;
+	}
+
+	/**
+	 * @return the skillRequired
+	 */
+	public String getSkillRequired() {
+		return skillRequired;
+	}
+
+	/**
+	 * @return the mastery
+	 */
+	public String getMastery() {
+		return mastery;
+	}
+
+	/**
+	 * @return the blockMod
+	 */
+	public float getBlockMod() {
+		return blockMod;
+	}
+
+	/**
+	 * @return the defense
+	 */
+	public short getDefense() {
+		return defense;
+	}
+
+	/**
+	 * @return the dexPenalty
+	 */
+	public float getDexPenalty() {
+		return dexPenalty;
+	}
+
+	/**
+	 * @return the speed
+	 */
+	public float getSpeed() {
+		return speed;
+	}
+
+	/**
+	 * @return the range
+	 */
+	public float getRange() {
+		return range;
+	}
+
+	/**
+	 * @return the isStrBased
+	 */
+	public boolean isStrBased() {
+		return isStrBased;
+	}
+
+	/**
+	 * @return the parryBonus
+	 */
+	public float getParryBonus() {
+		return parryBonus;
+	}
+
+	/**
+	 * @return the maxDamage
+	 */
+	public short getMaxDamage() {
+		return maxDamage;
+	}
+
+	/**
+	 * @return the minDamage
+	 */
+	public short getMinDamage() {
+		return minDamage;
+	}
+
+	/**
+	 * @return the damageType
+	 */
+	public engine.Enum.DamageType getDamageType() {
+		return damageType;
+	}
+
+	public short getPercentRequired() {
+		return percentRequired;
+	}
+
+	public ArrayList<Integer> getAnimations() {
+		return animations;
+	}
+
+	public void setAnimations(ArrayList<Integer> animations) {
+		this.animations = animations;
+	}
+
+	public ArrayList<Integer> getOffHandAnimations() {
+		return offHandAnimations;
+	}
+
+	public void setOffHandAnimations(ArrayList<Integer> offHandAnimations) {
+		this.offHandAnimations = offHandAnimations;
+	}
+
+	public boolean isAutoID() {
+		return autoID;
+	}
+
+	public void setAutoID(boolean autoID) {
+		this.autoID = autoID;
+	}
+}
diff --git a/src/engine/objects/ItemContainer.java b/src/engine/objects/ItemContainer.java
new file mode 100644
index 00000000..558fb3f5
--- /dev/null
+++ b/src/engine/objects/ItemContainer.java
@@ -0,0 +1,102 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+ package engine.objects;
+
+import engine.Enum.ContainerType;
+import engine.server.MBServerStatics;
+import org.pmw.tinylog.Logger;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.concurrent.ConcurrentHashMap;
+
+
+public class ItemContainer extends AbstractGameObject {
+
+	private AbstractWorldObject owner;
+	private ConcurrentHashMap<Long, Item>itemMap = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW);
+
+	private ContainerType containerType;
+
+	/**
+	 * No Table ID Constructor
+	 */
+	public ItemContainer(AbstractWorldObject owner, ContainerType containerType) {
+		super();
+		this.owner = owner;
+		this.containerType = containerType;
+	}
+
+	/**
+	 * Normal Constructor
+	 */
+	public ItemContainer(AbstractWorldObject owner, ContainerType containerType, int newUUID) {
+		super(newUUID);
+		this.owner = owner;
+		this.containerType = containerType;
+	}
+
+	/**
+	 * ResultSet Constructor
+	 */
+	public ItemContainer(ResultSet rs) throws SQLException {
+		super(rs);
+
+		//get owner
+		long ownerID = rs.getLong("parent");
+		this.owner = (AbstractWorldObject)AbstractGameObject.getFromTypeAndID(ownerID);
+
+		//get ContainerType
+		String ct = rs.getString("container_type");
+		try {
+			this.containerType = ContainerType.valueOf(ct.toUpperCase());
+		} catch (Exception e) {
+			this.containerType = ContainerType.INVENTORY;
+			Logger.error( "invalid containerType");
+		}
+	}
+
+	/*
+	 * Getters
+	 */
+	public AbstractWorldObject getOwner() {
+		return this.owner;
+	}
+
+	public ConcurrentHashMap<Long, Item> getItemMap() {
+		return this.itemMap;
+	}
+
+	public ContainerType getContainerType() {
+		return this.containerType;
+	}
+
+	public boolean isBank() {
+		return (this.containerType == ContainerType.BANK);
+	}
+
+	public boolean isInventory() {
+		return (this.containerType == ContainerType.INVENTORY);
+	}
+
+	public boolean isVault() {
+		return (this.containerType == ContainerType.VAULT);
+	}
+
+	public boolean containsItem(long itemID) {
+		return this.itemMap.containsKey(itemID);
+	}
+
+	@Override
+	public void updateDatabase() {
+		// TODO Create update logic.
+	}
+
+}
diff --git a/src/engine/objects/ItemFactory.java b/src/engine/objects/ItemFactory.java
new file mode 100644
index 00000000..92491684
--- /dev/null
+++ b/src/engine/objects/ItemFactory.java
@@ -0,0 +1,1202 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.objects;
+
+import engine.Enum;
+import engine.Enum.ItemContainerType;
+import engine.Enum.ItemType;
+import engine.Enum.OwnerType;
+import engine.gameManager.BuildingManager;
+import engine.gameManager.ChatManager;
+import engine.gameManager.DbManager;
+import engine.gameManager.PowersManager;
+import engine.net.ItemProductionManager;
+import engine.net.ItemQueue;
+import engine.net.client.ClientConnection;
+import engine.net.client.msg.ErrorPopupMsg;
+import engine.powers.EffectsBase;
+import engine.server.MBServerStatics;
+import org.joda.time.DateTime;
+import org.pmw.tinylog.Logger;
+
+import java.util.ArrayList;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ThreadLocalRandom;
+
+public class ItemFactory {
+
+	public static void fillInventory(PlayerCharacter pc, int objectID, int count) {
+
+		if(pc == null)
+			return;
+
+		int max = 20;
+		CharacterItemManager itemManager = pc.getCharItemManager();
+		ItemBase ib = ItemBase.getItemBase(objectID);
+		if (count > max)
+			count = max;
+
+		ClientConnection cc = pc.getClientConnection();
+
+		if(itemManager == null || ib == null || cc == null)
+			return;
+
+		boolean worked;
+		for (int i = 0; i < count; i++) {
+			worked = false;
+
+			if(!itemManager.hasRoomInventory(ib.getWeight())) {
+				if (pc != null)
+					ChatManager.chatSystemInfo(pc, "You can not carry any more of that item.");
+				break;
+			}
+
+			Item item = new Item(ib, pc.getObjectUUID(), OwnerType.PlayerCharacter, (byte) 0, (byte) 0,
+					(short) 1, (short) 1, true, false, ItemContainerType.INVENTORY, (byte) 0,
+                    new ArrayList<>(),"");
+			try {
+				item = DbManager.ItemQueries.ADD_ITEM(item);
+				worked = true;
+			} catch (Exception e) {
+				Logger.error(e);
+			}
+			if (worked) {
+				itemManager.addItemToInventory(item);
+			}
+		}
+		itemManager.updateInventory();
+	}
+	public static Item fillForge(NPC npc, PlayerCharacter pc,int itemsToRoll, int itemID, int pToken, int sToken, String customName) {
+
+		String prefixString = "";
+		String suffixString = "";
+		if(npc == null)
+			return null;
+
+		boolean useWarehouse = false;
+
+		ItemBase ib = ItemBase.getItemBase(itemID);
+
+		if (ib == null)
+			return null;
+
+		Building forge = npc.getBuilding();
+
+		if (forge == null)
+			return null;
+
+
+
+		if (!npc.getCharItemManager().hasRoomInventory(ib.getWeight())){
+			if (pc!= null)
+				ErrorPopupMsg.sendErrorPopup(pc, 21);
+			return null;
+		}
+
+		Zone zone = npc.getBuilding().getParentZone();
+
+		if (zone == null)
+			return null;
+
+		City city = City.getCity(zone.getPlayerCityUUID());
+
+		if (city == null)
+			return null;
+		MobLoot ml = null;
+		city.transactionLock.writeLock().lock();
+
+		try{
+		Warehouse cityWarehouse = city.getWarehouse();
+
+		if (cityWarehouse != null && forge.assetIsProtected())
+			useWarehouse = true;
+		// ROLL BANE SCROLL.
+
+		if (ib.getUUID() > 910010 && ib.getUUID() < 910019){
+			ConcurrentHashMap<ItemBase, Integer> resources = cityWarehouse.getResources();
+
+
+
+			int buildingWithdraw = BuildingManager.GetWithdrawAmountForRolling(forge, ib.getBaseValue());
+			int overdraft = BuildingManager.GetOverdraft(forge, ib.getBaseValue());
+
+			if (overdraft > 0 && !useWarehouse){
+				if (pc != null)
+					ErrorPopupMsg.sendErrorMsg(pc, "Not enough gold in building strongbox." + " " + ib.getName());
+				return null;
+			}
+
+			if (overdraft > 0 && cityWarehouse.isResourceLocked(ItemBase.GOLD_ITEM_BASE)){
+				if (pc != null)
+					ErrorPopupMsg.sendErrorMsg(pc, "Warehouse gold is barred! Overdraft cannot be withdrawn from warehouse." + " " + ib.getName());
+				return null;
+			}
+
+			if (overdraft > resources.get(ItemBase.GOLD_ITEM_BASE)){
+				if (pc != null)
+					ErrorPopupMsg.sendErrorMsg(pc, "Not enough Gold in Warehouse for overdraft." + " " + ib.getName());
+				return null;
+			}
+
+			//All checks passed, lets withdraw from building first.
+
+			//			if (pc != null){
+			//				ChatManager.chatGuildInfo(pc.getGuild(), "Building withdraw = " + buildingWithdraw);
+			//				ChatManager.chatGuildInfo(pc.getGuild(), "Warehouse overdraft withdraw = " + overdraft);
+			//
+			//				ChatManager.chatGuildInfo(pc.getGuild(), "total withdraw = " + (overdraft + buildingWithdraw));
+			//			}
+
+			if (!forge.transferGold(-buildingWithdraw,false)){
+				overdraft += buildingWithdraw;
+				
+				if (!useWarehouse){
+					ErrorPopupMsg.sendErrorMsg(pc, "Building does not have enough gold to produce this item."+ ib.getName());
+					return null;
+				}else{
+					if (overdraft > resources.get(ItemBase.GOLD_ITEM_BASE)){
+						ErrorPopupMsg.sendErrorMsg(pc, "Not enough Gold in Warehouse to produce this item."+ ib.getName());
+						return null;
+					}
+				}
+			}
+
+			if (overdraft > 0)
+				if(!cityWarehouse.withdraw(npc, ItemBase.GOLD_ITEM_BASE, overdraft, false,true)){
+					//ChatManager.chatGuildError(pc, "Failed to create Item");
+					Logger.error( "Warehouse With UID of " + cityWarehouse.getUID()  + " Failed to Create Item."+ ib.getName());
+					return null;
+				}
+
+
+
+
+
+				 ml = new MobLoot(npc, ib, false);
+			
+
+			ml.containerType = Enum.ItemContainerType.FORGE;
+			ml.setValue(0);
+			ml.loadEnchantments();
+
+			float time;
+			float rank = npc.getBuilding().getRank() - 1;
+			float rate = (float) (2.5 * rank);
+			time = (20 - rate);
+            time *= MBServerStatics.ONE_MINUTE;
+
+			if (ml.getItemBase().getUUID() > 910010 && ml.getItemBase().getUUID() < 910019){
+				rank = ml.getItemBaseID() - 910010;
+				time = rank * 60 * 60 * 3 * 1000;
+
+			}
+
+			// No job is submitted, as object's upgradetime field
+			// is used to determin whether or not an object has
+			// compelted rolling.  The game object exists previously
+			// to this, not when 'compelte' is pressed.
+			long upgradeTime =   System.currentTimeMillis() + (long)(time * MBServerStatics.PRODUCTION_TIME_MULTIPLIER) ;
+
+			DateTime dateTime = new DateTime();
+			dateTime = dateTime.withMillis(upgradeTime);
+			ml.setDateToUpgrade(upgradeTime);
+
+			npc.addItemToForge(ml);
+			
+			int playerID = 0;
+			
+			if (pc != null)
+				playerID = pc.getObjectUUID();
+			DbManager.NPCQueries.ADD_TO_PRODUCTION_LIST(ml.getObjectUUID(),npc.getObjectUUID(), ml.getItemBaseID(), dateTime, "", "", "", false,playerID);
+			ProducedItem pi = new ProducedItem(ml.getObjectUUID(),npc.getObjectUUID(),ml.getItemBaseID(),dateTime,false,"", "", "",playerID);
+			pi.setProducedItemID(ml.getObjectUUID());
+			pi.setAmount(itemsToRoll);
+			pi.setRandom(false);
+
+				ItemQueue produced = ItemQueue.borrow(pi, (long) (time * MBServerStatics.PRODUCTION_TIME_MULTIPLIER));
+				ItemProductionManager.send(produced);
+
+			return ml;
+		}
+
+		
+	
+		int galvorAmount = 0;
+		int wormwoodAmount = 0;
+		int prefixCost = 0;
+		int suffixCost = 0;
+
+
+		if (ib.getType() == ItemType.WEAPON && ib.getPercentRequired() == 110){
+			switch (ib.getSkillRequired()){
+			case "Bow":
+			case "Crossbow":
+			case "Spear":
+			case "Pole Arm":
+			case "Staff":
+				wormwoodAmount = 20;
+				break;
+			case "Axe":
+			case "Dagger":
+			case "Sword":
+			case "Hammer":
+			case "Unarmed Combat":
+
+				if (ib.isTwoHanded())
+					galvorAmount = 20;
+				else
+					galvorAmount = 10;
+				break;
+			}
+		}
+
+		ItemBase galvor = ItemBase.getItemBase(1580017);
+		ItemBase wormwood = ItemBase.getItemBase(1580018);
+
+		if (galvorAmount > 0 || wormwoodAmount > 0)
+			if (!useWarehouse){
+				if (pc != null)
+					ErrorPopupMsg.sendErrorMsg(pc, "This item requires resources to roll! Please make sure the forge is protected to access the warehouse."+ ib.getName());
+				return null;
+
+			}
+
+		if (galvorAmount > 0){
+			if (cityWarehouse.isResourceLocked(galvor)){
+				if (pc != null)
+					ErrorPopupMsg.sendErrorMsg(pc, "Galvor is locked."+ ib.getName());
+				return null;
+			}
+
+			if (cityWarehouse.getResources().get(galvor) < galvorAmount){
+				if (pc != null)
+					ErrorPopupMsg.sendErrorMsg(pc, "Not enough Galvor in warehouse to roll this item."+ ib.getName());
+				return null;
+			}
+		}
+
+		if (wormwoodAmount > 0){
+			if (cityWarehouse.isResourceLocked(wormwood)){
+				if (pc != null)
+					ErrorPopupMsg.sendErrorMsg(pc, "Wormwood is locked."+ ib.getName());
+				return null;
+			}
+
+			if (cityWarehouse.getResources().get(wormwood) < wormwoodAmount){
+				if (pc != null)
+					ErrorPopupMsg.sendErrorMsg(pc, "Not enough Wormwood in warehouse to roll this item."+ ib.getName());
+				return null;
+			}
+		}
+		ConcurrentHashMap<ItemBase, Integer> suffixResourceCosts = null;
+		ConcurrentHashMap<ItemBase, Integer> prefixResourceCosts = null;
+		EffectsBase prefix = null;
+		if (pToken != 0){
+
+			if (!useWarehouse){
+				if (pc != null)
+					ErrorPopupMsg.sendErrorMsg(pc, "Forge cannot access warehouse! Check to make sure forge is protected."+ ib.getName());
+				return null;
+			}
+			prefix = PowersManager.getEffectByToken(pToken);
+			if (prefix == null)
+				return null;
+			EffectsBase prefixValue = PowersManager.getEffectByIDString(prefix.getIDString() + 'A');
+			if (prefixValue == null)
+				return null;
+
+			int baseCost = ib.getBaseValue();
+			int effectCost = (int) prefixValue.getValue();
+			int total = baseCost * 10 + effectCost;
+
+			prefixCost = effectCost;
+			int buildingWithdraw = BuildingManager.GetWithdrawAmountForRolling(forge, total);
+			int overdraft = BuildingManager.GetOverdraft(forge, total);
+
+			if (overdraft > 0 && !useWarehouse){
+				if (pc != null)
+					ErrorPopupMsg.sendErrorMsg(pc, "Not enough gold in building strongbox."+ ib.getName());
+				return null;
+			}
+
+			if (overdraft > 0 && cityWarehouse.isResourceLocked(ItemBase.GOLD_ITEM_BASE)){
+				if (pc != null)
+					ErrorPopupMsg.sendErrorMsg(pc, "Warehouse gold is barred! Overdraft cannot be withdrawn from warehouse."+ ib.getName());
+				return null;
+			}
+
+			if (overdraft > cityWarehouse.getResources().get(ItemBase.GOLD_ITEM_BASE)){
+				if (pc != null)
+					ErrorPopupMsg.sendErrorMsg(pc, "Not enough Gold in Warehouse for overdraft."+ ib.getName());
+				return null;
+			}
+			prefixResourceCosts = prefix.getResourcesForEffect();
+			for (ItemBase ibResources: prefixResourceCosts.keySet()){
+				int warehouseAmount = cityWarehouse.getResources().get(ibResources);
+				int creationAmount = prefixResourceCosts.get(ibResources);
+				//ChatManager.chatInfoError(pc, "Prefix : " + ibResources.getName() + " / " + creationAmount);
+				if (warehouseAmount < creationAmount){
+					//ChatManager.chatInfoError(pc, "You need at least " + creationAmount + " " + ibResources.getName() + " to Create this item.");
+					return null;
+				}
+
+			}
+		}
+
+		EffectsBase suffix = null;
+		if (sToken != 0){
+
+			if (!useWarehouse){
+				if (pc != null)
+					ErrorPopupMsg.sendErrorMsg(pc, "Forge cannot access warehouse! Check to make sure forge is protected."+ ib.getName());
+				return null;
+			}
+			suffix = PowersManager.getEffectByToken(sToken);
+			if (suffix == null)
+				return null;
+			EffectsBase suffixValue = PowersManager.getEffectByIDString(suffix.getIDString() + 'A');
+			if (suffixValue == null)
+				return null;
+			suffixResourceCosts = suffix.getResourcesForEffect();
+			int baseCost = ib.getBaseValue();
+			int effectCost = (int) suffixValue.getValue();
+			suffixCost = effectCost;
+			int total = baseCost * 10 + effectCost;
+
+
+
+			//	int buildingWithdraw = Building.GetWithdrawAmountForRolling(forge, total);
+			int overdraft = BuildingManager.GetOverdraft(forge, total);
+
+			if (overdraft > 0 && !useWarehouse){
+				if (pc != null)
+					ErrorPopupMsg.sendErrorMsg(pc, "Not enough gold in building strongbox."+ ib.getName());
+				return null;
+			}
+
+			if (overdraft > 0 && cityWarehouse.isResourceLocked(ItemBase.GOLD_ITEM_BASE)){
+				if (pc != null)
+					ErrorPopupMsg.sendErrorMsg(pc, "Warehouse gold is barred! Overdraft cannot be withdrawn from warehouse."+ ib.getName());
+				return null;
+			}
+
+			if (overdraft > cityWarehouse.getResources().get(ItemBase.GOLD_ITEM_BASE)){
+				if (pc != null)
+					ErrorPopupMsg.sendErrorMsg(pc, "Not enough Gold in Warehouse for overdraft."+ ib.getName());
+				return null;
+			}
+
+
+			for (ItemBase ibResources: suffixResourceCosts.keySet()){
+				int warehouseAmount = cityWarehouse.getResources().get(ibResources);
+				int creationAmount = suffixResourceCosts.get(ibResources);
+				if (warehouseAmount < creationAmount){
+					//					if (pc != null)
+					//						ChatManager.chatInfoError(pc, "You need at least " + creationAmount + " " + ibResources.getName() + " to Create this item.");
+					return null;
+				}
+
+
+			}
+
+		}
+
+
+		//Check if Total suffix and prefix costs + itemCost can be withdrawn.
+		int costToCreate = suffixCost + prefixCost + (ib.getBaseValue());
+		int buildingWithdraw = BuildingManager.GetWithdrawAmountForRolling(forge, costToCreate);
+
+		int overdraft = BuildingManager.GetOverdraft(forge, costToCreate);
+
+		if (overdraft > 0 && !useWarehouse){
+			if (pc != null)
+				ErrorPopupMsg.sendErrorMsg(pc, "Not enough gold in building strongbox."+ ib.getName());
+			return null;
+		}
+
+		if (overdraft > 0 && useWarehouse && cityWarehouse.isResourceLocked(ItemBase.GOLD_ITEM_BASE)){
+			if (pc != null)
+				ErrorPopupMsg.sendErrorMsg(pc, "Warehouse gold is barred! Overdraft cannot be withdrawn from warehouse."+ ib.getName());
+			return null;
+		}
+
+		if (useWarehouse && overdraft > cityWarehouse.getResources().get(ItemBase.GOLD_ITEM_BASE)){
+			if (pc != null)
+				ErrorPopupMsg.sendErrorMsg(pc, "Not enough Gold in Warehouse for overdraft."+ ib.getName());
+			return null;
+		}
+
+		//		if (pc != null){
+		//			ChatManager.chatGuildInfo(pc.getGuild(), "Building withdraw = " + buildingWithdraw);
+		//			ChatManager.chatGuildInfo(pc.getGuild(), "Warehouse overdraft withdraw = " + overdraft);
+		//
+		//			ChatManager.chatGuildInfo(pc.getGuild(), "total withdraw = " + (overdraft + buildingWithdraw));
+		//		}
+
+		if (!forge.transferGold(-buildingWithdraw,false)){
+			overdraft += buildingWithdraw;
+			
+			if (!useWarehouse){
+				ErrorPopupMsg.sendErrorMsg(pc, "Building does not have enough gold to produce this item."+ ib.getName());
+				return null;
+			}else{
+				if (overdraft > cityWarehouse.getResources().get(ItemBase.GOLD_ITEM_BASE)){
+					ErrorPopupMsg.sendErrorMsg(pc, "Not enough Gold in Warehouse to produce this item."+ ib.getName());
+					return null;
+				}
+			}
+		}
+
+		if (overdraft > 0 && useWarehouse)
+			if(!cityWarehouse.withdraw(npc, ItemBase.GOLD_ITEM_BASE, overdraft, false,true)){
+				//ChatManager.chatGuildError(pc, "Failed to create Item");
+				Logger.error("Warehouse With UID of " + cityWarehouse.getUID()  + " Failed to Create Item."+ ib.getName());
+				return null;
+			}
+
+
+		if (prefix != null){
+
+			if (!useWarehouse){
+				ErrorPopupMsg.sendErrorMsg(pc, "Cannot Resource Roll without access to the warehouse! Make sure the forge is currently protected."+ ib.getName());
+				return null;
+			}
+
+
+			for (ItemBase ibResources: prefixResourceCosts.keySet()){
+
+				int creationAmount = prefixResourceCosts.get(ibResources);
+
+				if (cityWarehouse.isResourceLocked(ibResources) == true)
+					return null;
+
+				int oldAmount = cityWarehouse.getResources().get(ibResources);
+				int amount = creationAmount;
+
+				if (oldAmount < amount)
+					amount = oldAmount;
+
+				if(!cityWarehouse.withdraw(npc, ibResources, amount, false,true)){
+					//ChatManager.chatGuildError(pc, "Failed to create Item");
+					Logger.error("Warehouse With UID of " + cityWarehouse.getUID()  + " Failed to Create Item."+ ib.getName());
+					return null;
+				}
+			}
+		}
+
+		if (suffix != null) {
+
+			for (ItemBase ibResources: suffixResourceCosts.keySet()){
+				int creationAmount = suffixResourceCosts.get(ibResources);
+
+				if (cityWarehouse.isResourceLocked(ibResources) == true) {
+					ChatManager.chatSystemError(pc, ibResources.getName() + " is locked!"+ ib.getName());
+					return null;
+				}
+
+				int oldAmount = cityWarehouse.getResources().get(ibResources);
+				int amount = creationAmount;
+				if (oldAmount < amount)
+					amount = oldAmount;
+				if(!cityWarehouse.withdraw(npc, ibResources, amount, false,true)){
+					//ChatManager.chatGuildError(pc, "Failed to create Item");
+					Logger.error( "Warehouse With UID of " + cityWarehouse.getUID()  + " Failed to Create Item."+ ib.getName());
+					return null;
+				}
+			}
+		}
+
+		if (prefix == null && suffix == null){
+
+			int baseCost =  ib.getBaseValue();
+			int total = (int) (baseCost + baseCost *(float).10);
+
+			buildingWithdraw = BuildingManager.GetWithdrawAmountForRolling(forge, total);
+
+			overdraft = BuildingManager.GetOverdraft(forge, total);
+
+			if (overdraft > 0 && !useWarehouse){
+
+				if (pc != null)
+					ErrorPopupMsg.sendErrorMsg(pc, "Not enough gold in building strongbox."+ ib.getName());
+				return null;
+			}
+
+			if (overdraft > 0 && cityWarehouse.isResourceLocked(ItemBase.GOLD_ITEM_BASE)){
+
+				if (pc != null)
+					ErrorPopupMsg.sendErrorMsg(pc, "Warehouse gold is barred! Overdraft cannot be withdrawn from warehouse."+ ib.getName());
+				return null;
+			}
+
+			if (useWarehouse && overdraft > cityWarehouse.getResources().get(ItemBase.GOLD_ITEM_BASE)){
+
+				if (pc != null)
+					ErrorPopupMsg.sendErrorMsg(pc, "Not enough Gold in Warehouse for overdraft."+ ib.getName());
+				return null;
+			} }
+
+		if (!forge.transferGold(-buildingWithdraw,false)){
+			overdraft += buildingWithdraw;
+			
+			if (!useWarehouse){
+				ErrorPopupMsg.sendErrorMsg(pc, "Building does not have enough gold to produce this item."+ ib.getName());
+				return null;
+			}else{
+				if (overdraft > cityWarehouse.getResources().get(ItemBase.GOLD_ITEM_BASE)){
+					ErrorPopupMsg.sendErrorMsg(pc, "Not enough Gold in Warehouse to produce this item."+ ib.getName());
+					return null;
+				}
+			}
+		}
+
+			if (overdraft > 0)
+				if(!cityWarehouse.withdraw(npc, ItemBase.GOLD_ITEM_BASE, overdraft, false,true)){
+					//ChatManager.chatGuildError(pc, "Failed to create Item");
+					Logger.error( "Warehouse With UID of " + cityWarehouse.getUID()  + " Failed to Create Item."+ ib.getName());
+					return null;
+				}
+
+			//	ChatManager.chatGuildInfo(pc, "Gold Cost = " + total);
+
+		if (galvorAmount > 0){
+			if (!cityWarehouse.withdraw(npc, galvor, galvorAmount, false,true)){
+				ErrorPopupMsg.sendErrorMsg(pc, "Failed to withdraw Galvor from warehouse!"+ ib.getName());
+				Logger.error( "Warehouse with UID of" + cityWarehouse.getObjectUUID()+ "Failed to Withdrawl ");
+				return null;
+			}
+		}
+
+		if (wormwoodAmount > 0){
+			if (!cityWarehouse.withdraw(npc, wormwood, wormwoodAmount, false,true)){
+				ErrorPopupMsg.sendErrorMsg(pc, "Failed to withdraw Wormwood from warehouse!"+ ib.getName());
+				Logger.error("Warehouse with UID of" + cityWarehouse.getObjectUUID()+ "Failed to Withdrawl ");
+				return null;
+			}
+		}
+
+			 ml = new MobLoot(npc, ib, false);
+	
+		ml.containerType = Enum.ItemContainerType.FORGE;
+		ml.setName(customName);
+
+		if (prefix != null){
+			ml.addPermanentEnchantment(prefix.getIDString(), 0, 0, true);
+			ml.setPrefix(prefix.getIDString());
+			prefixString = prefix.getIDString();
+		}
+
+		if (suffix != null){
+			ml.addPermanentEnchantment(suffix.getIDString(), 0, 0, false);
+			ml.setSuffix(suffix.getIDString());
+			suffixString = suffix.getIDString();
+		}
+		
+		ml.loadEnchantments();
+		//set value to 0 so magicvalue can be recalculated in getValue.
+		ml.setValue(0);
+
+
+		float time;
+		float rank = npc.getBuilding().getRank() - 1;
+		float rate = (float) (2.5 * rank);
+		time = (20 - rate);
+        time *= MBServerStatics.ONE_MINUTE;
+
+		if (ml.getItemBase().getUUID() > 910010 && ml.getItemBase().getUUID() < 910019){
+			rank = ml.getItemBaseID() - 910010;
+			time = rank * 60 * 60 * 3 * 1000;
+		}
+
+
+		// No job is submitted, as object's upgradetime field
+		// is used to determin whether or not an object has
+		// compelted rolling.  The game object exists previously
+		// to this, not when 'compelte' is pressed.
+		long upgradeTime =  System.currentTimeMillis() + (long)(time * MBServerStatics.PRODUCTION_TIME_MULTIPLIER) ;
+
+		DateTime dateTime = new DateTime();
+		dateTime = dateTime.withMillis(upgradeTime);
+		ml.setDateToUpgrade(upgradeTime);
+
+		npc.addItemToForge(ml);
+		int playerID = 0;
+		
+		if (pc != null)
+			playerID = pc.getObjectUUID();
+
+		DbManager.NPCQueries.ADD_TO_PRODUCTION_LIST(ml.getObjectUUID(),npc.getObjectUUID(), ml.getItemBaseID(), dateTime, prefixString, suffixString, ml.getCustomName(), false,playerID);
+		ProducedItem pi = new ProducedItem(npc.getRolling().size(),npc.getObjectUUID(),ml.getItemBaseID(),dateTime,false,prefixString, suffixString, ml.getCustomName(),playerID);
+		pi.setProducedItemID(ml.getObjectUUID());
+		pi.setAmount(itemsToRoll);
+	
+			ItemQueue produced = ItemQueue.borrow(pi, (long) (time * MBServerStatics.PRODUCTION_TIME_MULTIPLIER));
+			ItemProductionManager.send(produced);
+		}catch(Exception e){
+			Logger.error(e);
+		}finally{
+			city.transactionLock.writeLock().unlock();
+		}
+
+		
+
+		//		npc.addItemToForge(item);
+		return ml;
+
+	}
+
+
+
+	public static Item randomRoll( NPC vendor, PlayerCharacter pc, int itemsToRoll, int itemID){
+		byte itemModTable;
+		int prefixMod = 0;
+		int suffixMod = 0;
+		LootTable prefixLootTable;
+		LootTable suffixLootTable;
+		String suffix = "";
+		String prefix = "";
+		MobLoot toRoll;
+
+		ItemBase ib = ItemBase.getItemBase(itemID);
+
+		if (ib == null)
+			return null;
+
+		if (!vendor.getCharItemManager().hasRoomInventory(ib.getWeight())){
+			if (pc != null)
+				ChatManager.chatSystemInfo(pc, vendor.getName() + " " +vendor.getContract().getName() + " Inventory is full." );
+			return null;
+		}
+
+		float calculatedMobLevel;
+		calculatedMobLevel = vendor.getLevel();
+
+		if (calculatedMobLevel < 16)
+			calculatedMobLevel = 16;
+
+		if (calculatedMobLevel > 49)
+			calculatedMobLevel = 49;
+
+		itemModTable = (byte) ib.getModTable();
+
+		if (!vendor.getItemModTable().contains(itemModTable)){
+			if (pc != null)
+				ErrorPopupMsg.sendErrorPopup(pc, 59);
+			return null;
+		}
+
+		for (byte temp: vendor.getItemModTable()){
+			if (itemModTable != temp)
+				continue;
+			prefixMod = vendor.getModTypeTable().get(vendor.getItemModTable().indexOf(temp));
+			suffixMod = vendor.getModSuffixTable().get(vendor.getItemModTable().indexOf(temp));
+		}
+
+		if (prefixMod == 0 && suffixMod == 0){
+			Logger.info( "Failed to find modTables for item " + ib.getName());
+			return null;
+		}
+
+		prefixLootTable = LootTable.getModGroup(prefixMod);
+		suffixLootTable = LootTable.getModGroup(suffixMod);
+
+		if (prefixLootTable == null || suffixLootTable == null)
+			return null;
+
+		int rollPrefix = ThreadLocalRandom.current().nextInt(100);
+
+		if (rollPrefix < 80){
+			int randomPrefix = ThreadLocalRandom.current().nextInt(100) + 1;
+			LootRow prefixLootRow = prefixLootTable.getLootRow(randomPrefix);
+
+				if (prefixLootRow != null){
+					LootTable prefixTypeTable = LootTable.getModTable(prefixLootRow.getValueOne());
+
+					int minRoll =  (int) ((calculatedMobLevel - 5) * 5);
+					int maxRoll = (int) ((calculatedMobLevel + 15) * 5);
+
+					if (minRoll < (int)prefixTypeTable.minRoll)
+						minRoll = (int)prefixTypeTable.minRoll;
+
+					if (maxRoll < minRoll)
+						maxRoll = minRoll;
+
+					if (maxRoll > prefixTypeTable.maxRoll)
+						maxRoll = (int) prefixTypeTable.maxRoll;
+
+					if (maxRoll > 320)
+						maxRoll = 320;
+
+					int randomPrefix1 = (int) ThreadLocalRandom.current().nextDouble(minRoll, maxRoll + 1); //Does not return Max, but does return min?
+
+					if (randomPrefix1 < prefixTypeTable.minRoll)
+						randomPrefix1 = (int) prefixTypeTable.minRoll;
+
+					if (randomPrefix1 > prefixTypeTable.maxRoll)
+						randomPrefix1 = (int) prefixTypeTable.maxRoll;
+
+					LootRow prefixTypelootRow = prefixTypeTable.getLootRow(randomPrefix1);
+
+					if (prefixTypelootRow == null)
+						prefixTypelootRow = prefixTypeTable.getLootRow((int) ((prefixTypeTable.maxRoll + prefixTypeTable.minRoll) * .05f));
+
+					if (prefixTypelootRow != null){
+						prefix = prefixTypelootRow.getAction();
+				}
+			}
+		}
+
+		int rollSuffix = ThreadLocalRandom.current().nextInt(100);
+
+		if (rollSuffix < 80){
+
+			int randomSuffix = ThreadLocalRandom.current().nextInt(100) + 1;
+			LootRow suffixLootRow = suffixLootTable.getLootRow(randomSuffix);
+			
+				if (suffixLootRow != null){
+
+					LootTable suffixTypeTable = LootTable.getModTable(suffixLootRow.getValueOne());
+
+					if (suffixTypeTable != null){
+						int minRoll =  (int) ((calculatedMobLevel - 5) * 5);
+						int maxRoll = (int) ((calculatedMobLevel + 15) * 5);
+
+						if (minRoll < (int)suffixTypeTable.minRoll)
+							minRoll = (int)suffixTypeTable.minRoll;
+
+						if (maxRoll < minRoll)
+							maxRoll = minRoll;
+
+						if (maxRoll > suffixTypeTable.maxRoll)
+							maxRoll = (int) suffixTypeTable.maxRoll;
+
+						if (maxRoll > 320)
+							maxRoll = 320;
+
+						int randomSuffix1 = (int) ThreadLocalRandom.current().nextDouble(minRoll, maxRoll + 1); //Does not return Max, but does return min?
+
+						if (randomSuffix1 < suffixTypeTable.minRoll)
+							randomSuffix1 = (int) suffixTypeTable.minRoll;
+
+						if (randomSuffix1 > suffixTypeTable.maxRoll)
+							randomSuffix1 = (int) suffixTypeTable.maxRoll;
+
+						LootRow suffixTypelootRow = suffixTypeTable.getLootRow(randomSuffix1);
+
+						if (suffixTypelootRow != null){
+							suffix = suffixTypelootRow.getAction();
+						}
+					}
+				}
+		}
+
+		if (prefix.isEmpty() && suffix.isEmpty()){
+
+			rollPrefix = ThreadLocalRandom.current().nextInt(100);
+
+			if (rollPrefix < 50){
+
+				int randomPrefix = ThreadLocalRandom.current().nextInt(100) + 1;
+				LootRow prefixLootRow = prefixLootTable.getLootRow(randomPrefix);
+
+				if (prefixLootRow != null){
+
+					LootTable prefixTypeTable = LootTable.getModTable(prefixLootRow.getValueOne());
+
+					int minRoll =  (int) ((calculatedMobLevel) * 5);
+					int maxRoll = (int) ((calculatedMobLevel + 15) * 5);
+
+					if (minRoll < (int)prefixTypeTable.minRoll)
+						minRoll = (int)prefixTypeTable.minRoll;
+
+					if (maxRoll < minRoll)
+						maxRoll = minRoll;
+
+					if (maxRoll > prefixTypeTable.maxRoll)
+						maxRoll = (int) prefixTypeTable.maxRoll;
+
+					if (maxRoll > 320)
+						maxRoll = 320;
+
+					int randomPrefix1 = (int) ThreadLocalRandom.current().nextDouble(minRoll, maxRoll + 1); //Does not return Max, but does return min?
+
+					if (randomPrefix1 < prefixTypeTable.minRoll)
+						randomPrefix1 = (int) prefixTypeTable.minRoll;
+
+					if (randomPrefix1 > prefixTypeTable.maxRoll)
+						randomPrefix1 = (int) prefixTypeTable.maxRoll;
+
+					LootRow prefixTypelootRow = prefixTypeTable.getLootRow(randomPrefix1);
+
+					if (prefixTypelootRow == null)
+						prefixTypelootRow = prefixTypeTable.getLootRow((int) ((prefixTypeTable.maxRoll + prefixTypeTable.minRoll) * .05f));
+
+					if (prefixTypelootRow != null){
+						prefix = prefixTypelootRow.getAction();
+					}
+				}
+			}else{
+				int randomSuffix = ThreadLocalRandom.current().nextInt(100) + 1;
+				LootRow suffixLootRow = suffixLootTable.getLootRow(randomSuffix);
+
+					if (suffixLootRow != null){
+
+						LootTable suffixTypeTable = LootTable.getModTable(suffixLootRow.getValueOne());
+
+						if (suffixTypeTable != null){
+
+							int minRoll =  (int) ((calculatedMobLevel) * 5);
+							int maxRoll = (int) ((calculatedMobLevel + 15) * 5);
+
+							if (minRoll < (int)suffixTypeTable.minRoll)
+								minRoll = (int)suffixTypeTable.minRoll;
+
+							if (maxRoll < minRoll)
+								maxRoll = minRoll;
+
+							if (maxRoll > suffixTypeTable.maxRoll)
+								maxRoll = (int) suffixTypeTable.maxRoll;
+
+							if (maxRoll > 320)
+								maxRoll = 320;
+
+							int randomSuffix1 = (int) ThreadLocalRandom.current().nextDouble(minRoll, maxRoll + 1); //Does not return Max, but does return min?
+
+							if (randomSuffix1 < suffixTypeTable.minRoll)
+								randomSuffix1 = (int) suffixTypeTable.minRoll;
+
+							if (randomSuffix1 > suffixTypeTable.maxRoll)
+								randomSuffix1 = (int) suffixTypeTable.maxRoll;
+
+							LootRow suffixTypelootRow = suffixTypeTable.getLootRow(randomSuffix1);
+
+							if (suffixTypelootRow != null)
+								suffix = suffixTypelootRow.getAction();
+						}
+					}
+			}
+		}
+
+		toRoll =ItemFactory.produceRandomRoll(vendor, pc,prefix,suffix, itemID);
+
+		if (toRoll == null)
+			return null;
+
+		toRoll.setValue(0);
+
+		float time;
+		float rank = vendor.getBuilding().getRank() - 1;
+		float rate = (float) (2.5 * rank);
+		time = (20 - rate);
+        time *= MBServerStatics.ONE_MINUTE;
+
+		if (toRoll.getItemBase().getUUID() > 910010 && toRoll.getItemBase().getUUID() < 910019){
+			rank = toRoll.getItemBaseID() - 910010;
+			time = rank * 60 * 60 * 3 * 1000;
+		}
+
+		// No job is submitted, as object's upgradetime field
+		// is used to determin whether or not an object has
+		// compelted rolling.  The game object exists previously
+		// to this, not when 'compelte' is pressed.
+		long upgradeTime =   System.currentTimeMillis() + (long)(time * MBServerStatics.PRODUCTION_TIME_MULTIPLIER) ;
+
+		DateTime dateTime = new DateTime();
+		dateTime = dateTime.withMillis(upgradeTime);
+		toRoll.setDateToUpgrade(upgradeTime);
+		
+		int playerID = 0;
+		
+		if (pc != null)
+			playerID = pc.getObjectUUID();
+		DbManager.NPCQueries.ADD_TO_PRODUCTION_LIST(toRoll.getObjectUUID(),vendor.getObjectUUID(), toRoll.getItemBaseID(), dateTime, prefix, suffix, toRoll.getCustomName(), true,playerID);
+		ProducedItem pi = new ProducedItem(toRoll.getObjectUUID(),vendor.getObjectUUID(),toRoll.getItemBaseID(),dateTime,true,prefix, suffix, toRoll.getCustomName(),playerID);
+		pi.setProducedItemID(toRoll.getObjectUUID());
+		pi.setAmount(itemsToRoll);
+		ItemQueue produced = ItemQueue.borrow(pi, (long) (time * MBServerStatics.PRODUCTION_TIME_MULTIPLIER));
+		ItemProductionManager.send(produced);
+		return toRoll;
+	}
+
+	public static MobLoot produceRandomRoll(NPC npc,PlayerCharacter pc,String prefixString, String suffixString, int itemID) {
+
+		boolean useWarehouse = false;
+
+		if(npc == null)
+			return null;
+
+		ItemBase ib = ItemBase.getItemBase(itemID);
+
+		if (ib == null)
+			return null;
+
+		Building forge = npc.getBuilding();
+
+		if (forge == null)
+			return null;
+
+		Zone zone = npc.getBuilding().getParentZone();
+
+		if (zone == null)
+			return null;
+
+		City city = City.getCity(zone.getPlayerCityUUID());
+
+		if (city == null)
+			return null;
+
+		MobLoot ml = null;
+		city.transactionLock.writeLock().lock();
+		
+		try{
+			
+		
+
+		Warehouse cityWarehouse = city.getWarehouse();
+
+		if (cityWarehouse != null && forge.assetIsProtected())
+			useWarehouse = true;
+
+		ConcurrentHashMap<ItemBase, Integer> resources = null;
+		
+		if (useWarehouse)
+		resources = cityWarehouse.getResources();
+
+		int galvorAmount = 0;
+		int wormwoodAmount = 0;
+
+		if (ib.getType() == ItemType.WEAPON && ib.getPercentRequired() == 110){
+			switch (ib.getSkillRequired()){
+			case "Bow":
+			case "Crossbow":
+			case "Spear":
+			case "Pole Arm":
+			case "Staff":
+				wormwoodAmount = 22;
+				break;
+			case "Axe":
+			case "Dagger":
+			case "Sword":
+			case "Hammer":
+			case "Unarmed Combat":
+
+				if (ib.isTwoHanded())
+					galvorAmount = 22;
+				else
+					galvorAmount = 11;
+				break;
+			}
+		}
+
+		ItemBase galvor = ItemBase.getItemBase(1580017);
+		ItemBase wormwood = ItemBase.getItemBase(1580018);
+
+		//Cant roll 110% weapons that require resources if not allowed to use warehouse.
+		if (galvorAmount > 0 || wormwoodAmount > 0)
+			if (!useWarehouse)
+				return null;
+
+		if (galvorAmount > 0){
+			if (cityWarehouse.isResourceLocked(galvor)){
+				ErrorPopupMsg.sendErrorMsg(pc, "Galvor is locked."+ ib.getName());
+				return null;
+			}
+
+			if (cityWarehouse.getResources().get(galvor) < galvorAmount){
+				ErrorPopupMsg.sendErrorMsg(pc, "Not enough Galvor in warehouse to roll this item."+ ib.getName());
+				return null;
+			}
+		}
+
+		if (wormwoodAmount > 0){
+			if (cityWarehouse.isResourceLocked(wormwood)){
+				ErrorPopupMsg.sendErrorMsg(pc, "Galvor is locked."+ ib.getName());
+				return null;
+			}
+
+			if (cityWarehouse.getResources().get(wormwood) < wormwoodAmount){
+				ErrorPopupMsg.sendErrorMsg(pc, "Not enough Galvor in warehouse to roll this item."+ ib.getName());
+				return null;
+			}
+		}
+
+		EffectsBase prefix = null;
+
+		if (!prefixString.isEmpty()){
+			prefix = PowersManager.getEffectByIDString(prefixString);
+			if (prefix == null)
+				return null;
+		}
+
+		ItemBase goldIB = ItemBase.getGoldItemBase();
+
+		int baseCost = ib.getBaseValue();
+		int total = (int) (baseCost + baseCost * .10);
+
+		EffectsBase suffix = null;
+
+		if (!suffixString.isEmpty()){
+			suffix = PowersManager.getEffectByIDString(suffixString);
+
+			if (suffix == null)
+				return null;
+		}
+
+		//calculate gold costs and remove from the warehouse
+		if (prefix != null || suffix != null){
+			int costToCreate =    (int) (ib.getBaseValue() + ib.getBaseValue() *.10f);
+			int buildingWithdraw = BuildingManager.GetWithdrawAmountForRolling(forge, costToCreate);
+			int overdraft = BuildingManager.GetOverdraft(forge, costToCreate);
+
+			if (overdraft > 0 && !useWarehouse){
+				ErrorPopupMsg.sendErrorMsg(pc, "Not enough gold in building strongbox."+ ib.getName());
+				return null;
+			}
+
+			if (useWarehouse && overdraft > 0 && cityWarehouse.isResourceLocked(ItemBase.GOLD_ITEM_BASE)){
+				if (pc != null)
+					ErrorPopupMsg.sendErrorMsg(pc, "Warehouse gold is barred! Overdraft cannot be withdrawn from warehouse."+ ib.getName());
+				return null;
+			}
+
+			if (useWarehouse && overdraft > resources.get(goldIB)){
+				ErrorPopupMsg.sendErrorMsg(pc, "Not enough Gold in Warehouse for overdraft."+ ib.getName());
+				return null;
+			}
+
+			if (!forge.transferGold(-buildingWithdraw,false)){
+				overdraft += buildingWithdraw;
+				
+				if (!useWarehouse){
+					ErrorPopupMsg.sendErrorMsg(pc, "Building does not have enough gold to produce this item."+ ib.getName());
+					return null;
+				}else{
+					if (overdraft > resources.get(goldIB)){
+						ErrorPopupMsg.sendErrorMsg(pc, "Not enough Gold in Warehouse to produce this item."+ ib.getName());
+						return null;
+					}
+				}
+			}
+
+			// there was an overdraft, withdraw the rest from warehouse.
+			if (overdraft > 0){
+				if (pc != null){
+					if (!cityWarehouse.withdraw(pc, ItemBase.GOLD_ITEM_BASE, overdraft, false,true)){
+						Logger.error("Warehouse with UID of" + cityWarehouse.getObjectUUID()+ "Failed to Withdrawl ");
+						return null;
+					}
+				}else{
+					if (!cityWarehouse.withdraw(npc,ItemBase.GOLD_ITEM_BASE, overdraft, false,true)){
+						Logger.error("Warehouse with UID of" + cityWarehouse.getObjectUUID()+ "Failed to Withdrawl ");
+						return null;
+					}
+				}
+			}
+		}
+
+		if (prefix == null && suffix == null){
+
+			int buildingWithdraw = BuildingManager.GetWithdrawAmountForRolling(forge, total);
+			int overdraft = BuildingManager.GetOverdraft(forge, total);
+
+			if (overdraft > 0 && !useWarehouse){
+				ErrorPopupMsg.sendErrorMsg(pc, "Not enough gold in building strongbox."+ ib.getName());
+				return null;
+			}
+
+			if (useWarehouse && overdraft > 0 && cityWarehouse.isResourceLocked(ItemBase.GOLD_ITEM_BASE)){
+				if (pc != null)
+					ErrorPopupMsg.sendErrorMsg(pc, "Warehouse gold is barred! Overdraft cannot be withdrawn from warehouse."+ ib.getName());
+				return null;
+			}
+
+			if (useWarehouse && overdraft > resources.get(goldIB)){
+				ErrorPopupMsg.sendErrorMsg(pc, "Not enough Gold in Warehouse for overdraft."+ ib.getName());
+				return null;
+			}
+
+			if (!forge.transferGold(-buildingWithdraw,false)){
+				overdraft += buildingWithdraw;
+				
+				if (!useWarehouse){
+					ErrorPopupMsg.sendErrorMsg(pc, "Building does not have enough gold to produce this item."+ ib.getName());
+					return null;
+				}else{
+					if (overdraft > resources.get(goldIB)){
+						ErrorPopupMsg.sendErrorMsg(pc, "Not enough Gold in Warehouse to produce this item."+ ib.getName());
+						return null;
+					}
+				}
+			}
+
+			if (overdraft > 0 && useWarehouse){
+
+				if (pc != null){
+					if (!cityWarehouse.withdraw(pc, ItemBase.GOLD_ITEM_BASE, overdraft, false,true)){
+						Logger.error("Warehouse with UID of" + cityWarehouse.getObjectUUID()+ "Failed to Withdrawl ");
+						return null;
+					}
+				}else{
+					if (!cityWarehouse.withdraw(npc, ItemBase.GOLD_ITEM_BASE, overdraft, false,true)){
+						Logger.error( "Warehouse with UID of" + cityWarehouse.getObjectUUID()+ "Failed to Withdrawl ");
+						return null;
+					}
+				}
+			}
+		}
+
+		if (galvorAmount > 0 && useWarehouse){
+			//ChatManager.chatGuildInfo(pc, "Withdrawing " + galvorAmount + " galvor from warehouse");
+			if (!cityWarehouse.withdraw(npc, galvor, galvorAmount, false,true)){
+				ErrorPopupMsg.sendErrorMsg(pc, "Failed to withdraw Galvor from warehouse!"+ ib.getName());
+				Logger.error( "Warehouse with UID of" + cityWarehouse.getObjectUUID()+ "Failed to Withdrawl ");
+				return null;
+			}
+		}
+
+		if (wormwoodAmount > 0 && useWarehouse){
+			//ChatManager.chatGuildInfo(pc, "Withdrawing " + wormwoodAmount + " wormwood from warehouse");
+			if (!cityWarehouse.withdraw(npc, wormwood, wormwoodAmount, false,true)){
+				ErrorPopupMsg.sendErrorMsg(pc, "Failed to withdraw Wormwood from warehouse for " + ib.getName());
+				Logger.error("Warehouse with UID of" + cityWarehouse.getObjectUUID()+ "Failed to Withdrawl ");
+				
+				return null;
+			}
+		}
+
+		 ml = new MobLoot(npc, ib, false);
+
+		ml.containerType = Enum.ItemContainerType.FORGE;
+
+		if (prefix != null){
+			ml.addPermanentEnchantment(prefix.getIDString(), 0, 0, true);
+			ml.setPrefix(prefix.getIDString());
+		}
+
+		if (suffix != null){
+			ml.addPermanentEnchantment(suffix.getIDString(), 0, 0, false);
+			ml.setSuffix(suffix.getIDString());
+		}
+		
+		ml.loadEnchantments();
+		
+
+		ml.setValue(0);
+		ml.setRandom(true);
+		npc.addItemToForge(ml);
+		}catch(Exception e){
+			Logger.error(e);
+		}finally{
+			city.transactionLock.writeLock().unlock();
+		}
+		return ml;
+
+	}
+}
diff --git a/src/engine/objects/Kit.java b/src/engine/objects/Kit.java
new file mode 100644
index 00000000..d2642b80
--- /dev/null
+++ b/src/engine/objects/Kit.java
@@ -0,0 +1,429 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.objects;
+
+import engine.Enum.ItemContainerType;
+import engine.Enum.OwnerType;
+import engine.gameManager.DbManager;
+import engine.server.MBServerStatics;
+import org.pmw.tinylog.Logger;
+
+import java.net.UnknownHostException;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.HashMap;
+
+
+public class Kit extends AbstractGameObject {
+
+	private final int raceBaseClassID;
+	private final byte kitNumber;
+	private final int legs;
+	private final int chest;
+	private final int feet;
+	private final int offhand;
+	private final int weapon;
+	public static HashMap<Integer,Boolean> NoobGearIDS = new HashMap<>();
+	public static HashMap<Integer, ArrayList<Kit>> RaceClassIDMap = new HashMap<>();
+
+
+	/**
+	 * No Table ID Constructor
+	 */
+	public Kit(int raceBaseClassID, byte kitNumber, int legs, int chest,
+			int feet, int offhand, int weapon) {
+		super();
+		this.raceBaseClassID = raceBaseClassID;
+		this.kitNumber = kitNumber;
+		this.legs = legs;
+		this.chest = chest;
+		this.feet = feet;
+		this.offhand = offhand;
+		this.weapon = weapon;
+	}
+
+	/**
+	 * Normal Constructor
+	 */
+	public Kit(int raceBaseClassID, byte kitNumber, int legs, int chest,
+			int feet, int offhand, int weapon, int newUUID) {
+		super(newUUID);
+		this.raceBaseClassID = raceBaseClassID;
+		this.kitNumber = kitNumber;
+		this.legs = legs;
+		this.chest = chest;
+		this.feet = feet;
+		this.offhand = offhand;
+		this.weapon = weapon;
+	}
+
+	/**
+	 * RecordSet Constructor
+	 */
+	public Kit(ResultSet rs) throws SQLException, UnknownHostException {
+		super(rs);
+
+		this.raceBaseClassID = rs.getInt("RaceBaseClassesID");
+		this.kitNumber = rs.getByte("kitNumber");
+
+		this.legs = rs.getInt("legs");
+		this.chest = rs.getInt("chest");
+		this.feet = rs.getInt("feet");
+		this.offhand = rs.getInt("offhand");
+		this.weapon = rs.getInt("weapon");
+		if (Kit.RaceClassIDMap.containsKey(this.raceBaseClassID)){
+			Kit.RaceClassIDMap.get(this.raceBaseClassID).add(this);
+		}else{
+			ArrayList<Kit> tempList = new ArrayList<>();
+			tempList.add(this);
+			Kit.RaceClassIDMap.put(this.raceBaseClassID, tempList);
+
+		}
+
+		if (this.legs != 0)
+			Kit.NoobGearIDS.put(this.legs, true);
+		if (this.chest != 0)
+			Kit.NoobGearIDS.put(this.chest, true);
+		if (this.feet != 0)
+			Kit.NoobGearIDS.put(this.feet, true);
+		if (this.offhand != 0)
+			Kit.NoobGearIDS.put(this.offhand, true);
+		if (this.weapon != 0)
+			Kit.NoobGearIDS.put(this.weapon, true);
+
+	}
+
+	public static boolean IsNoobGear(int itemID){
+
+        return Kit.NoobGearIDS.containsKey(itemID);
+
+    }
+
+	/*
+	 * Getters
+	 */
+
+	public int getRaceBaseClassID() {
+		return raceBaseClassID;
+	}
+
+	public byte getKitNumber() {
+		return kitNumber;
+	}
+
+	public int getLegs() {
+		return legs;
+	}
+
+	public int getChest() {
+		return chest;
+	}
+
+	public int getFeet() {
+		return feet;
+	}
+
+	public int getOffhand() {
+		return offhand;
+	}
+
+	public int getWeapon() {
+		return weapon;
+	}
+
+	public void equipPCwithKit(PlayerCharacter pc) {
+		if (weapon != 0)
+			kitItemCreator(pc, weapon, MBServerStatics.SLOT_MAINHAND);
+		if (offhand != 0)
+			kitItemCreator(pc, offhand, MBServerStatics.SLOT_OFFHAND);
+		if (chest != 0)
+			kitItemCreator(pc, chest, MBServerStatics.SLOT_CHEST);
+		if (legs != 0)
+			kitItemCreator(pc, legs, MBServerStatics.SLOT_LEGGINGS);
+		if (feet != 0)
+			kitItemCreator(pc, feet, MBServerStatics.SLOT_FEET);
+	}
+
+	private static boolean kitItemCreator(PlayerCharacter pc, int itemBase, int slot)
+			 {
+		ItemBase i = ItemBase.getItemBase(itemBase);
+
+		Item temp = new Item( i, pc.getObjectUUID(),
+				OwnerType.PlayerCharacter, (byte) 0, (byte) 0, (short) 0, (short) 0,
+				false, false,ItemContainerType.EQUIPPED, (byte) slot,
+                new ArrayList<>(),"");
+
+		try {
+			temp = DbManager.ItemQueries.ADD_ITEM(temp);
+		} catch (Exception e) {
+		Logger.error(e);
+		}
+
+		if (temp == null) {
+			Logger.info("Ungoof this goof, something is wrong with our kit.");
+		}
+		return true;
+	}
+
+	public static int GetKitIDByRaceClass(final int raceID, final int classID){
+		switch (raceID){
+		case 2000:
+			switch(classID){
+			case 2500:
+				return 2;
+			case 2501:
+				return 3;
+			case 2502:
+				return 4;
+			case 2503:
+				return 5;
+			}
+		case 2001:
+
+			switch(classID){
+			case 2500:
+				return 6;
+			case 2501:
+				return 7;
+			case 2502:
+				return 8;
+			case 2503:
+				return 9;
+			}
+		case 2002:
+			switch(classID){
+			case 2500:
+				return 10;
+			case 2501:
+				return 11;
+			case 2502:
+				return 12;
+
+			}
+		case 2003:
+			switch(classID){
+			case 2500:
+				return 13;
+			case 2501:
+				return 14;
+			case 2502:
+				return 15;
+			}
+		case 2004:
+
+			switch(classID){
+			case 2500:
+				return 16;
+			case 2501:
+				return 17;
+			}
+		case 2005:
+			switch(classID){
+			case 2500:
+				return 18;
+			case 2501:
+				return 19;
+			}
+		case 2006:
+
+			switch(classID){
+			case 2500:
+				return 20;
+			case 2501:
+				return 21;
+
+			}
+		case 2008:
+
+			switch(classID){
+			case 2500:
+				return 22;
+			case 2501:
+				return 23;
+			case 2502:
+				return 24;
+			case 2503:
+				return 25;
+			}
+
+		case 2009:
+
+			switch(classID){
+			case 2500:
+				return 26;
+			case 2501:
+				return 27;
+			case 2502:
+				return 28;
+			case 2503:
+				return 29;
+			}
+		case 2010:
+
+			switch(classID){
+			case 2500:
+				return 30;
+			}
+
+		case 2011:
+
+			switch(classID){
+			case 2500:
+				return 31;
+			case 2501:
+				return 32;
+			case 2502:
+				return 33;
+			case 2503:
+				return 34;
+			}
+
+		case 2012:
+
+			switch(classID){
+			case 2500:
+				return 35;
+			case 2501:
+				return 36;
+			case 2502:
+				return 37;
+			case 2503:
+				return 38;
+			}
+
+		case 2013:
+
+			switch(classID){
+			case 2500:
+				return 39;
+			case 2501:
+				return 40;
+			case 2502:
+				return 41;
+			case 2503:
+				return 42;
+			}
+
+		case 2014:
+
+			switch(classID){
+			case 2500:
+				return 43;
+			case 2501:
+				return 44;
+			case 2502:
+				return 45;
+			case 2503:
+				return 46;
+			}
+
+		case 2015:
+
+			switch(classID){
+			case 2500:
+				return 47;
+			case 2502:
+				return 48;
+			case 2503:
+				return 49;
+
+			}
+
+		case 2016:
+
+			switch(classID){
+			case 2500:
+				return 50;
+			case 2502:
+				return 51;
+			case 2503:
+				return 52;
+
+			}
+
+		case 2017:
+
+			switch(classID){
+			case 2500:
+				return 53;
+			case 2501:
+				return 54;
+
+			}
+
+		case 2025:
+
+			switch(classID){
+			case 2500:
+				return 55;
+			case 2501:
+				return 56;
+			case 2502:
+				return 57;
+			case 2503:
+				return 58;
+			}
+
+		case 2026:
+
+			switch(classID){
+			case 2500:
+				return 59;
+			case 2501:
+				return 60;
+			case 2502:
+				return 61;
+			case 2503:
+				return 62;
+			}
+
+		case 2027:
+
+			switch(classID){
+			case 2500:
+				return 63;
+			}
+
+		case 2028:
+
+			switch(classID){
+			case 2500:
+				return 64;
+
+			case 2502:
+				return 65;
+			case 2503:
+				return 66;
+			}
+
+		case 2029:
+
+			switch(classID){
+			case 2500:
+				return 67;
+
+			case 2502:
+				return 68;
+			case 2503:
+				return 69;
+			}
+
+
+
+		}
+		return -1;
+	}
+
+	@Override
+	public void updateDatabase() {
+		// TODO Create update logic.
+	}
+}
diff --git a/src/engine/objects/LevelDefault.java b/src/engine/objects/LevelDefault.java
new file mode 100644
index 00000000..c66d6db3
--- /dev/null
+++ b/src/engine/objects/LevelDefault.java
@@ -0,0 +1,72 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+ package engine.objects;
+
+import engine.server.MBServerStatics;
+import org.pmw.tinylog.Logger;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class LevelDefault {
+
+	public final int level;
+	public final float health;
+	public final float mana;
+	public final float stamina;
+	public final float atr;
+	public final float def;
+	public final float minDamage;
+	public final float maxDamage;
+	public final int goldMin;
+	public final int goldMax;
+
+	public static ConcurrentHashMap<Byte, LevelDefault> defaults = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW);
+
+	/**
+	 * ResultSet Constructor
+	 */
+	public LevelDefault(ResultSet rs) throws SQLException {
+		super();
+		this.level = rs.getInt("level");
+		this.health = rs.getFloat("health");
+		this.mana = (float)rs.getInt("mana");
+		this.stamina = (float)rs.getInt("stamina");
+		this.atr = (float)rs.getInt("atr");
+		this.def = (float)rs.getInt("def");
+		this.minDamage = (float)rs.getInt("minDamage");
+		this.maxDamage = (float)rs.getInt("maxDamage");
+		this.goldMin = rs.getInt("goldMin");
+		this.goldMax = rs.getInt("goldMax");
+	}
+
+	public static LevelDefault getLevelDefault(byte level) {
+		LevelDefault ret = null;
+		if (LevelDefault.defaults.containsKey(level))
+			return LevelDefault.defaults.get(level);
+
+		PreparedStatementShared ps = null;
+		try {
+			ps = new PreparedStatementShared("SELECT * FROM `static_npc_level_defaults` WHERE level = ?;");
+			ps.setInt(1, (int)level);
+			ResultSet rs = ps.executeQuery();
+			if (rs.next()) {
+				ret = new LevelDefault(rs);
+				LevelDefault.defaults.put(level, ret);
+			}
+		} catch (SQLException e) {
+			Logger.error("SQL Error number: " + e.getErrorCode() + ' ' + e.getMessage());
+		} finally {
+			ps.release();
+		}
+		return ret;
+	}
+}
\ No newline at end of file
diff --git a/src/engine/objects/LootRow.java b/src/engine/objects/LootRow.java
new file mode 100644
index 00000000..3a74c269
--- /dev/null
+++ b/src/engine/objects/LootRow.java
@@ -0,0 +1,63 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.objects;
+
+public class LootRow {
+
+	private int valueOne;
+	private int valueTwo;
+	private int valueThree;
+	private String action;
+
+
+	/**
+	 * Generic Constructor
+	 */
+	public LootRow(int valueOne, int valueTwo, int valueThree, String action) {
+		this.valueOne = valueOne;
+		this.valueTwo = valueTwo;
+		this.valueThree = valueThree;
+		this.action = action;
+	
+	}
+
+	public int getValueOne() {
+		return this.valueOne;
+	}
+
+	public int getValueTwo() {
+		return this.valueTwo;
+	}
+
+	public int getValueThree() {
+		return this.valueThree;
+	}
+
+	public String getAction() {
+		return this.action;
+	}
+
+	public void setValueOne(int value) {
+		this.valueOne = value;
+	}
+
+	public void setValueTwo(int value) {
+		this.valueTwo = value;
+	}
+
+	public void setValueThree(int value) {
+		this.valueThree = value;
+	}
+
+	public void setAction(String value) {
+		this.action = value;
+	}
+
+}
diff --git a/src/engine/objects/LootTable.java b/src/engine/objects/LootTable.java
new file mode 100644
index 00000000..16a9523a
--- /dev/null
+++ b/src/engine/objects/LootTable.java
@@ -0,0 +1,1381 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.objects;
+
+import engine.Enum.ItemContainerType;
+import engine.Enum.ItemType;
+import engine.Enum.OwnerType;
+import engine.gameManager.DbManager;
+import engine.gameManager.ZoneManager;
+import engine.server.MBServerStatics;
+import org.pmw.tinylog.Logger;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Random;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ThreadLocalRandom;
+
+public class LootTable {
+
+	private static final ConcurrentHashMap<Integer, LootTable> lootGroups = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW);
+	private static final ConcurrentHashMap<Integer, LootTable> lootTables = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW);
+	private static final ConcurrentHashMap<Integer, LootTable> modTables = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW);
+	private static final ConcurrentHashMap<Integer, LootTable> modGroups = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW);
+	private static final ConcurrentHashMap<Integer, Integer> statRuneChances = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW);
+	private final ConcurrentHashMap<Integer, LootRow> lootTable = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW);
+
+
+
+	private static final int oneDrop = 95;
+	private static final int twoDrop = 100;
+	private static final int noDropHotZone = 79;
+	private static final int oneDropHotZone = 98;
+
+	public float minRoll = 320;
+	public float maxRoll = 1;
+	public static boolean initialized = false;
+
+	public int lootTableID = 0;
+
+	public static HashMap<ItemBase,Integer> itemsDroppedMap = new HashMap<>();
+	public static HashMap<ItemBase,Integer> resourceDroppedMap = new HashMap<>();
+	public static HashMap<ItemBase,Integer> runeDroppedMap = new HashMap<>();
+	public static HashMap<ItemBase,Integer> contractDroppedMap = new HashMap<>();
+	public static HashMap<ItemBase,Integer> glassDroppedMap = new HashMap<>();
+
+	public static int rollCount = 0;
+	public static int dropCount = 0;
+	public static int runeCount = 0;
+	public static int contractCount = 0;
+	public static int resourceCount = 0;
+	public static int glassCount = 0;
+
+
+	/**
+	 * Generic Constructor
+	 */
+	public LootTable(int lootTableID) {
+		this.lootTableID = lootTableID;
+	}
+
+	public void addRow(float min, float max, int valueOne, int valueTwo, int valueThree, String action) {
+
+		//hackey way to set the minimum roll for SHIAT!
+		if (min < this.minRoll)
+			this.minRoll = min;
+
+		if (max > this.maxRoll)
+			this.maxRoll = max;
+
+		int minInt = (int) min;
+		int maxInt = (int) max;
+
+		//Round up min
+		if (minInt != min){
+			min = minInt + 1;
+		}
+
+		//Round down max;
+		if (maxInt != max)
+			max = maxInt;
+
+
+
+		LootRow lootRow = new LootRow(valueOne, valueTwo, valueThree, action);
+		for (int i = (int) min; i <= max; i++) {
+			lootTable.put(i, lootRow);
+		}
+	}
+
+	public static LootTable getLootGroup(int UUID) {
+
+		if (lootGroups.containsKey(UUID))
+			return lootGroups.get(UUID);
+
+		LootTable lootGroup = new LootTable(UUID);
+		lootGroups.put(UUID, lootGroup);
+		return lootGroup;
+	}
+
+	public static LootTable getLootTable(int UUID) {
+
+		if (lootTables.containsKey(UUID))
+			return lootTables.get(UUID);
+
+		LootTable lootTable = new LootTable(UUID);
+		lootTables.put(UUID, lootTable);
+		return lootTable;
+	}
+
+	/**
+	 * @return the lootGroups
+	 */
+	public static ConcurrentHashMap<Integer, LootTable> getLootGroups() {
+		return lootGroups;
+	}
+
+	/**
+	 * @return the lootTables
+	 */
+	public static ConcurrentHashMap<Integer, LootTable> getLootTables() {
+		return lootTables;
+	}
+
+	/**
+	 * @return the modTables
+	 */
+	public static ConcurrentHashMap<Integer, LootTable> getModTables() {
+		return modTables;
+	}
+
+	/**
+	 * @return the modGroups
+	 */
+	public static ConcurrentHashMap<Integer, LootTable> getModGroups() {
+		return modGroups;
+	}
+
+
+	public static LootTable getModGroup(int UUID) {
+		if (modGroups.containsKey(UUID))
+			return modGroups.get(UUID);
+		LootTable modTable = new LootTable(UUID);
+		modGroups.put(UUID, modTable);
+		return modTable;
+	}
+
+	public static LootTable getModTable(int UUID) {
+		if (modTables.containsKey(UUID))
+			return modTables.get(UUID);
+		LootTable modTypeTable = new LootTable(UUID);
+		modTables.put(UUID, modTypeTable);
+		return modTypeTable;
+	}
+
+
+	public LootRow getLootRow(int probability) {
+		if (lootTable.containsKey(probability))
+			return lootTable.get(probability);
+		return null;
+	}
+
+	//call this on server startup to populate the tables
+	public static void populateLootTables() {
+		DbManager.LootQueries.populateLootGroups();
+		DbManager.LootQueries.populateLootTables();
+		DbManager.LootQueries.populateModTables();
+		DbManager.LootQueries.populateModGroups();
+
+		//preset chances for rune drops
+		populateStatRuneChances();
+	}
+
+	//Returns a list of random loot for a mob based on level, lootTable and hotzone
+	public static ArrayList<MobLoot> getMobLoot(Mob mob, int mobLevel, int lootTable, boolean hotzone) {
+
+		// Member variable declaration
+		ArrayList<MobLoot> loot;
+		int calculatedLootTable;
+		int roll;
+
+		// Member variable assignment
+		loot = new ArrayList<>();
+
+		// Setup default loot table if none exists
+		calculatedLootTable = lootTable;
+
+		LootTable.rollCount++;
+		if (MobLootBase.MobLootSet.get(mob.getMobBase().getLoadID()).isEmpty()){
+
+
+			roll = ThreadLocalRandom.current().nextInt(100);
+			if (roll > 90)
+				if (roll > LootTable.oneDropHotZone)
+					addMobLoot(mob, loot, mobLevel, calculatedLootTable, 1, true);
+				else
+					addMobLoot(mob, loot, mobLevel, calculatedLootTable, 1, true);
+		}else{
+			for (MobLootBase mlb:MobLootBase.MobLootSet.get(mob.getMobBase().getLoadID())){
+
+
+				float chance = mlb.getChance() *.01f;
+
+				chance *= MBServerStatics.DROP_RATE_MOD;
+
+				calculatedLootTable = mlb.getLootTableID();
+
+
+				if (ThreadLocalRandom.current().nextFloat() > chance)
+					continue;
+
+				addMobLoot(mob, loot, mobLevel, calculatedLootTable, 1, false);
+
+
+			}
+		}
+
+		//calculatedLootTable = lootTable;
+
+		if (calculatedLootTable <= 1)
+			calculatedLootTable = 1300;  // GENERIC WORLD
+
+
+
+
+		//handle hotzone random loot
+
+		if (hotzone) {
+
+			LootTable.rollCount++;
+
+			if (MobLootBase.MobLootSet.get(mob.getMobBase().getLoadID()).isEmpty()){
+
+
+				roll = ThreadLocalRandom.current().nextInt(100);
+				if (roll > 90)
+					if (roll > LootTable.oneDropHotZone)
+						addMobLoot(mob, loot, mobLevel, calculatedLootTable + 1, 1, true);
+					else
+						addMobLoot(mob, loot, mobLevel, calculatedLootTable + 1, 1, true);
+			}else{
+				for (MobLootBase mlb:MobLootBase.MobLootSet.get(mob.getMobBase().getLoadID())){
+					if (!LootTable.lootGroups.containsKey(mlb.getLootTableID() + 1))
+						continue;
+					calculatedLootTable = mlb.getLootTableID();
+					break;
+				}
+				roll = ThreadLocalRandom.current().nextInt(100);
+				if (roll > 90)
+					if (roll > LootTable.oneDropHotZone)
+						addMobLoot(mob, loot, mobLevel, (calculatedLootTable + 1), 1, true);
+					else
+						addMobLoot(mob, loot, mobLevel, (calculatedLootTable + 1), 1, true);
+
+			}
+
+		}
+
+
+
+
+		//handle mob specific special loot
+		handleSpecialLoot(loot, mob, false);
+
+		return loot;
+	}
+
+	public static ArrayList<MobLoot> getMobLootDeath(Mob mob, int mobLevel, int lootTable) {
+		ArrayList<MobLoot> loot = new ArrayList<>();
+
+		if (mob == null)
+			return loot;
+
+		//handle hotzone random loot
+		boolean hotzone = ZoneManager.inHotZone(mob.getLoc());
+		if (hotzone) {
+
+			if (MobLootBase.MobLootSet.get(mob.getMobBase().getLoadID()).isEmpty()){
+				lootTable += 1;
+
+				if (lootTable <= 1)
+					lootTable = 1301;  // GENERIC WORLD
+				int roll = ThreadLocalRandom.current().nextInt(100);
+				if (roll > 90)
+					if (roll > LootTable.oneDropHotZone)
+						addMobLoot(mob, loot, mobLevel, lootTable, 1, true);
+					else
+						addMobLoot(mob, loot, mobLevel, lootTable, 1, true);
+			}else{
+				for (MobLootBase mlb:MobLootBase.MobLootSet.get(mob.getMobBase().getLoadID())){
+					lootTable = mlb.getLootTableID() + 1;
+					if (!LootTable.lootGroups.containsKey(lootTable))
+						continue;
+
+					int roll = ThreadLocalRandom.current().nextInt(100);
+					if (roll > 90)
+						if (roll > LootTable.oneDropHotZone)
+							addMobLoot(mob, loot, mobLevel, (lootTable), 1, true);
+						else
+							addMobLoot(mob, loot, mobLevel, (lootTable), 1, true);
+
+					break;
+				}
+			}
+
+
+			if (loot.isEmpty()){
+
+				LootTable.rollCount++; //add another rollCount here.
+				int resourceRoll = ThreadLocalRandom.current().nextInt(100);
+				if (resourceRoll <=5)
+					addMobLootResources(mob, loot, mobLevel, (lootTable), 1, true);
+			}
+
+		}
+
+
+		//handle mob specific special loot on death
+		handleSpecialLoot(loot, mob, true);
+
+		return loot;
+	}
+
+	private static void handleSpecialLoot(ArrayList<MobLoot> loot, Mob mob, boolean onDeath) {
+
+		if (SpecialLoot.LootMap.containsKey(mob.getLootSet())) {
+			ArrayList<SpecialLoot> specialLoot = SpecialLoot.LootMap.get(mob.getLootSet());
+			for (SpecialLoot sl : specialLoot) {
+				if ((onDeath && sl.dropOnDeath()) || (!onDeath && !sl.dropOnDeath()))
+					if (ThreadLocalRandom.current().nextInt(100) < sl.getDropChance()) {
+						ItemBase ib = ItemBase.getItemBase(sl.getItemID());
+						if (ib != null) {
+
+							switch (ib.getUUID()){
+							case 19290:
+								continue;
+							case 19291:
+								continue;
+							case 19292:
+								continue;
+							case 27530:
+								continue;
+							case 973000:
+								continue;
+							case 973200:
+								continue;
+							case 26360:
+								continue;
+							}
+							MobLoot ml = new MobLoot(mob, ib, sl.noSteal());
+							loot.add(ml);
+
+							
+
+						}
+					}
+			}
+		}
+	}
+
+
+
+	//called by getMobLoot to add the actual loot
+	private static void addMobLoot(Mob mob, ArrayList<MobLoot> loot, int mobLevel, int lootTableID, int cnt, boolean hotzone) {
+
+		// Member variable declaration
+		float calculatedMobLevel;
+		int minSpawn;
+		int maxSpawn;
+		int spawnQuanity = 0;
+		int prefixValue = 0;
+		int suffixValue = 0;
+		int subTableID;
+		String modifierPrefix = "";
+		String modifierSuffix = "";
+
+		// Lookup Table Variables
+		LootTable lootTable;
+		LootRow lootRow;
+		LootTable lootGroup;
+		LootRow groupRow = null;
+		LootTable modTable;
+		LootTable modGroup;
+		LootRow modRow = null;
+
+		// Used for actual generation of items
+		int itemBaseUUID;
+		ItemBase itemBase = null;
+		MobLoot mobLoot;
+
+		Zone zone = mob.getParentZone();
+		// Member variable assignment
+		if (!LootTable.lootGroups.containsKey(lootTableID))
+			return;
+
+		lootGroup = LootTable.lootGroups.get(lootTableID);
+
+
+
+
+
+		calculatedMobLevel = mobLevel;
+
+		if (calculatedMobLevel > 49)
+			calculatedMobLevel = 49;
+
+
+		int roll = 0;
+		for (int i = 0; i < cnt; i++) {
+
+
+			Random random = new Random();
+
+
+			roll = random.nextInt(100) + 1; //random roll between 1 and 100
+			groupRow = lootGroup.getLootRow(roll);
+
+
+
+
+
+			if (groupRow == null)
+				return;
+
+			//get loot table for this group
+			if (!LootTable.lootTables.containsKey(groupRow.getValueOne()))
+				return;
+
+
+			lootTable = LootTable.lootTables.get(groupRow.getValueOne());
+
+			//get item ID //FUCK THIS RETARDED SHIT
+			//			roll = gaussianLevel(calculatedMobLevel);
+
+
+
+
+			int minRoll =  (int) ((calculatedMobLevel - 5) * 5);
+			int maxRoll = (int) ((calculatedMobLevel + 15) * 5);
+
+			if (minRoll < (int)lootTable.minRoll){
+				minRoll = (int)lootTable.minRoll;
+			}
+
+			if (maxRoll < minRoll)
+				maxRoll = minRoll;
+
+			if (maxRoll > lootTable.maxRoll)
+				maxRoll = (int) lootTable.maxRoll;
+
+
+
+			if (maxRoll > 320)
+				maxRoll = 320;
+
+			roll = (int) ThreadLocalRandom.current().nextDouble(minRoll, maxRoll + 1); //Does not return Max, but does return min?
+
+
+			lootRow = lootTable.getLootRow(roll); //get the item row from the bell's curve of level +-15
+
+			if (lootRow == null)
+				continue; //no item found for roll
+
+			itemBaseUUID = lootRow.getValueOne();
+
+
+
+			if (lootRow.getValueOne() == 0)
+				continue;
+
+			//handle quantities > 1 for resource drops
+			minSpawn = lootRow.getValueTwo();
+			maxSpawn = lootRow.getValueThree();
+
+			// spawnQuanity between minspawn (inclusive) and maxspawn (inclusive)
+			if (maxSpawn > 1)
+				spawnQuanity = ThreadLocalRandom.current().nextInt((maxSpawn + 1 - minSpawn)) + minSpawn;
+
+
+
+			//get modifierPrefix
+
+			calculatedMobLevel = mobLevel;
+
+			if (calculatedMobLevel < 16)
+				calculatedMobLevel = 16;
+
+			if (calculatedMobLevel > 49)
+				calculatedMobLevel = 49;
+
+			int chanceMod = ThreadLocalRandom.current().nextInt(100) + 1;
+
+			if (chanceMod < 25){
+				modGroup = LootTable.modGroups.get(groupRow.getValueTwo());
+
+				if (modGroup != null) {
+
+
+					for (int a = 0;a<10;a++){
+						roll = ThreadLocalRandom.current().nextInt(100) + 1;
+						modRow = modGroup.getLootRow(roll);
+						if (modRow != null)
+							break;
+					}
+
+
+					if (modRow != null) {
+						subTableID = modRow.getValueOne();
+
+						if (LootTable.modTables.containsKey(subTableID)) {
+
+							modTable = LootTable.modTables.get(subTableID);
+
+							roll = gaussianLevel((int)calculatedMobLevel);
+
+							if (roll < modTable.minRoll)
+								roll = (int) modTable.minRoll;
+
+							if (roll > modTable.maxRoll)
+								roll = (int) modTable.maxRoll;
+
+
+
+							modRow = modTable.getLootRow(roll);
+
+							if (modRow != null) {
+								prefixValue = modRow.getValueOne();
+								modifierPrefix = modRow.getAction();
+							}
+						}
+					}
+				}
+			}else if(chanceMod < 50){
+				modGroup = LootTable.modGroups.get(groupRow.getValueThree());
+
+				if (modGroup != null) {
+
+					for (int a = 0;a<10;a++){
+						roll = ThreadLocalRandom.current().nextInt(100) + 1;
+						modRow = modGroup.getLootRow(roll);
+						if (modRow != null)
+							break;
+					}
+
+					if (modRow != null) {
+
+						subTableID = modRow.getValueOne();
+
+						if (LootTable.modTables.containsKey(subTableID)) {
+
+							modTable = LootTable.modTables.get(subTableID);
+							roll = gaussianLevel((int)calculatedMobLevel);
+
+							if (roll < modTable.minRoll)
+								roll = (int) modTable.minRoll;
+
+							if (roll > modTable.maxRoll)
+								roll = (int) modTable.maxRoll;
+
+							modRow = modTable.getLootRow(roll);
+
+							if (modRow == null){
+								modRow = modTable.getLootRow((int) ((modTable.minRoll + modTable.maxRoll) *.05f));
+							}
+
+							if (modRow != null) {
+								suffixValue = modRow.getValueOne();
+								modifierSuffix = modRow.getAction();
+							}
+						}
+					}
+				}
+			}else{
+				modGroup = LootTable.modGroups.get(groupRow.getValueTwo());
+
+				if (modGroup != null) {
+
+
+					for (int a = 0;a<10;a++){
+						roll = ThreadLocalRandom.current().nextInt(100) + 1;
+						modRow = modGroup.getLootRow(roll);
+						if (modRow != null)
+							break;
+					}
+
+
+					if (modRow != null) {
+						subTableID = modRow.getValueOne();
+
+						if (LootTable.modTables.containsKey(subTableID)) {
+
+							modTable = LootTable.modTables.get(subTableID);
+
+							roll = gaussianLevel((int)calculatedMobLevel);
+
+							if (roll < modTable.minRoll)
+								roll = (int) modTable.minRoll;
+
+							if (roll > modTable.maxRoll)
+								roll = (int) modTable.maxRoll;
+
+
+
+							modRow = modTable.getLootRow(roll);
+
+							if (modRow == null){
+								modRow = modTable.getLootRow((int) ((modTable.minRoll + modTable.maxRoll) *.05f));
+							}
+
+							if (modRow != null) {
+								prefixValue = modRow.getValueOne();
+								modifierPrefix = modRow.getAction();
+							}
+						}
+					}
+				}
+
+				//get modifierSuffix
+				modGroup = LootTable.modGroups.get(groupRow.getValueThree());
+
+				if (modGroup != null) {
+
+					for (int a = 0;a<10;a++){
+						roll = ThreadLocalRandom.current().nextInt(100) + 1;
+						modRow = modGroup.getLootRow(roll);
+						if (modRow != null)
+							break;
+					}
+
+					if (modRow != null) {
+
+						subTableID = modRow.getValueOne();
+
+						if (LootTable.modTables.containsKey(subTableID)) {
+
+							modTable = LootTable.modTables.get(subTableID);
+							roll = gaussianLevel((int)calculatedMobLevel);
+
+							if (roll < modTable.minRoll)
+								roll = (int) modTable.minRoll;
+
+							if (roll > modTable.maxRoll)
+								roll = (int) modTable.maxRoll;
+
+							modRow = modTable.getLootRow(roll);
+
+							if (modRow == null){
+								modRow = modTable.getLootRow((int) ((modTable.minRoll + modTable.maxRoll) *.05f));
+							}
+
+							if (modRow != null) {
+								suffixValue = modRow.getValueOne();
+								modifierSuffix = modRow.getAction();
+							}
+						}
+					}
+				}
+			}
+
+
+			itemBase = ItemBase.getItemBase(itemBaseUUID);
+
+			if (itemBase == null)
+				return;
+
+			//Handle logging of drops
+			LootTable.HandleDropLogs(itemBase);
+
+		
+
+
+			// Handle drop rates of resources/runes/contracts.
+			// We intentionally drop them in half
+			//			if ((itemBase.getMessageType() == ItemType.CONTRACT) ||
+			//					(itemBase.getMessageType() == ItemType.RUNE) ){
+			//				if (ThreadLocalRandom.current().nextBoolean() == false)
+			//					continue;
+			//			}
+
+			
+
+			if (itemBase.getType() == ItemType.OFFERING)
+				spawnQuanity = 1;
+
+			if (spawnQuanity > 0)
+				mobLoot = new MobLoot(mob, itemBase, spawnQuanity, false);
+			else
+				mobLoot = new MobLoot(mob, itemBase, false);
+
+			if (!modifierPrefix.isEmpty())
+				mobLoot.addPermanentEnchantment(modifierPrefix, 0, prefixValue, true);
+
+			if (!modifierSuffix.isEmpty())
+				mobLoot.addPermanentEnchantment(modifierSuffix, 0, suffixValue, false);
+			mobLoot.loadEnchantments();
+
+			loot.add(mobLoot);
+
+
+
+		}
+	}
+
+	private static void addMobLootResources(Mob mob, ArrayList<MobLoot> loot, int mobLevel, int lootTableID, int cnt, boolean hotzone) {
+
+		// Member variable declaration
+		float calculatedMobLevel;
+		int minSpawn;
+		int maxSpawn;
+		int spawnQuanity = 0;
+		int prefixValue = 0;
+		int suffixValue = 0;
+		int subTableID;
+		String modifierPrefix = "";
+		String modifierSuffix = "";
+
+		// Lookup Table Variables
+		LootTable lootTable;
+		LootRow lootRow;
+		LootTable lootGroup;
+		LootRow groupRow = null;
+		LootTable modTable;
+		LootTable modGroup;
+		LootRow modRow = null;
+
+		// Used for actual generation of items
+		int itemBaseUUID;
+		ItemBase itemBase;
+		MobLoot mobLoot;
+
+		Zone zone = mob.getParentZone();
+		// Member variable assignment
+		if (!LootTable.lootGroups.containsKey(lootTableID))
+			return;
+
+		lootGroup = LootTable.lootGroups.get(lootTableID);
+
+		calculatedMobLevel = mobLevel;
+
+		if (calculatedMobLevel > 49)
+			calculatedMobLevel = 49;
+
+		int roll = 0;
+		for (int i = 0; i < cnt; i++) {
+
+
+
+			if  (lootTableID == 1901)
+				groupRow = lootGroup.getLootRow(66);
+			else if (lootTableID == 1501)
+				groupRow = lootGroup.getLootRow(98);
+			else
+				groupRow = lootGroup.getLootRow(80);
+
+
+
+
+
+			if (groupRow == null)
+				return;
+
+			//get loot table for this group
+			if (!LootTable.lootTables.containsKey(groupRow.getValueOne()))
+				return;
+
+
+			lootTable = LootTable.lootTables.get(groupRow.getValueOne());
+
+			//get item ID //FUCK THIS RETARDED SHIT
+			//			roll = gaussianLevel(calculatedMobLevel);
+
+
+
+
+			int minRoll =  (int) ((calculatedMobLevel-5) * 5);
+			int maxRoll = (int) ((calculatedMobLevel + 15) *5);
+
+			if (minRoll < (int)lootTable.minRoll){
+				minRoll = (int)lootTable.minRoll;
+			}
+
+			if (maxRoll < minRoll)
+				maxRoll = minRoll;
+
+
+
+			if (maxRoll > 320)
+				maxRoll = 320;
+
+			roll = ThreadLocalRandom.current().nextInt(minRoll, maxRoll + 1); //Does not return Max, but does return min?
+			lootRow = lootTable.getLootRow(roll); //get the item row from the bell's curve of level +-15
+
+			if (lootRow == null)
+				continue; //no item found for roll
+
+			itemBaseUUID = lootRow.getValueOne();
+
+			if (lootRow.getValueOne() == 0)
+				continue;
+
+			//handle quantities > 1 for resource drops
+			minSpawn = lootRow.getValueTwo();
+			maxSpawn = lootRow.getValueThree();
+
+			// spawnQuanity between minspawn (inclusive) and maxspawn (inclusive)
+			if (maxSpawn > 1)
+				spawnQuanity = ThreadLocalRandom.current().nextInt((maxSpawn + 1 - minSpawn)) + minSpawn;
+
+
+			itemBase = ItemBase.getItemBase(itemBaseUUID);
+			if (itemBase == null)
+				return;
+			LootTable.HandleDropLogs(itemBase);
+
+
+			switch (itemBase.getUUID()){
+			case 19290:
+				continue;
+			case 19291:
+				continue;
+			case 19292:
+				continue;
+			case 27530:
+				continue;
+			case 973000:
+				continue;
+			case 973200:
+				continue;
+
+			case 26360:
+				continue;
+			}
+
+			// Handle drop rates of resources/runes/contracts.
+			// We intentionally drop them in half
+
+
+
+			if (itemBase.getType() == ItemType.OFFERING)
+				spawnQuanity = 1;
+
+			if (spawnQuanity > 0)
+				mobLoot = new MobLoot(mob, itemBase, spawnQuanity, false);
+			else
+				mobLoot = new MobLoot(mob, itemBase, false);
+
+			loot.add(mobLoot);
+
+		}
+	}
+
+	public static int gaussianLevel(int level) {
+		int ret = -76;
+
+		while (ret < -75 || ret > 75) {
+			ret = (int) (ThreadLocalRandom.current().nextGaussian() * 75);
+		}
+
+		return (level * 5) + ret;
+		//		float useLevel = (float)(level + (ThreadLocalRandom.current().nextGaussian() * 5));
+		//
+		//		if (useLevel < (level - 15))
+		//			useLevel = level - 15;
+		//		else if (useLevel > (level + 15))
+		//			useLevel = level + 15;
+		//		return (int)(useLevel * 5);
+	}
+
+
+
+	
+
+	//This set's the drop chances for stat runes.
+	public static void populateStatRuneChances() {
+		//+3, Increased
+		statRuneChances.put(250018, 60);
+		statRuneChances.put(250009, 60);
+		statRuneChances.put(250027, 60);
+		statRuneChances.put(250036, 60);
+		statRuneChances.put(250000, 60);
+
+		//+5, Enhanced
+		statRuneChances.put(250019, 60);
+		statRuneChances.put(250010, 60);
+		statRuneChances.put(250028, 60);
+		statRuneChances.put(250037, 60);
+		statRuneChances.put(250001, 60);
+
+		//+10 Exceptional
+		statRuneChances.put(250020, 60);
+		statRuneChances.put(250011, 60);
+		statRuneChances.put(250029, 60);
+		statRuneChances.put(250038, 60);
+		statRuneChances.put(250002, 60);
+
+		//+15, Amazing
+		statRuneChances.put(250021, 60);
+		statRuneChances.put(250012, 60);
+		statRuneChances.put(250030, 60);
+		statRuneChances.put(250039, 60);
+		statRuneChances.put(250003, 60);
+
+		//+20, Incredible
+		statRuneChances.put(250022, 60);
+		statRuneChances.put(250013, 60);
+		statRuneChances.put(250031, 60);
+		statRuneChances.put(250040, 60);
+		statRuneChances.put(250004, 60);
+
+		//+25, Great
+		statRuneChances.put(250023, 60);
+		statRuneChances.put(250014, 60);
+		statRuneChances.put(250032, 60);
+		statRuneChances.put(250041, 60);
+		statRuneChances.put(250005, 60);
+
+		//+30, Heroic
+		statRuneChances.put(250024, 60);
+		statRuneChances.put(250015, 60);
+		statRuneChances.put(250033, 60);
+		statRuneChances.put(250042, 60);
+		statRuneChances.put(250006, 60);
+
+		//+35, Legendary
+		statRuneChances.put(250025, 60);
+		statRuneChances.put(250016, 60);
+		statRuneChances.put(250034, 60);
+		statRuneChances.put(250043, 60);
+		statRuneChances.put(250007, 60);
+
+		//+40, of the Gods
+		statRuneChances.put(250026, 60);
+		statRuneChances.put(250017, 60);
+		statRuneChances.put(250035, 60);
+		statRuneChances.put(250044, 60);
+		statRuneChances.put(250008, 60);
+	}
+
+	public ConcurrentHashMap<Integer, LootRow> getLootTable() {
+		return lootTable;
+	}
+
+	private static void HandleDropLogs(ItemBase itemBase){
+
+		if (itemBase == null)
+			return;
+
+		LootTable.dropCount++; //item dropped, add to all item count.
+
+
+		if (LootTable.itemsDroppedMap.get(itemBase) == null){
+			LootTable.itemsDroppedMap.put(itemBase, 1); //First time dropping, make count 1.
+		}else{
+			int dropCount = LootTable.itemsDroppedMap.get(itemBase);
+			dropCount++;
+			LootTable.itemsDroppedMap.put(itemBase, dropCount);
+		}
+
+		switch (itemBase.getType()){
+		case RESOURCE:
+			LootTable.resourceCount++;
+
+			if (LootTable.resourceDroppedMap.get(itemBase) == null){
+				LootTable.resourceDroppedMap.put(itemBase, 1); //First time dropping, make count 1.
+			}else{
+				int dropCount = LootTable.resourceDroppedMap.get(itemBase);
+				dropCount++;
+				LootTable.resourceDroppedMap.put(itemBase, dropCount);
+			}
+			break;
+		case RUNE:
+			LootTable.runeCount++;
+			if (LootTable.runeDroppedMap.get(itemBase) == null){
+				LootTable.runeDroppedMap.put(itemBase, 1); //First time dropping, make count 1.
+			}else{
+				int dropCount = LootTable.runeDroppedMap.get(itemBase);
+				dropCount++;
+				LootTable.runeDroppedMap.put(itemBase, dropCount);
+			}
+			break;
+		case CONTRACT:
+			LootTable.contractCount++;
+
+			if (LootTable.contractDroppedMap.get(itemBase) == null){
+				LootTable.contractDroppedMap.put(itemBase, 1); //First time dropping, make count 1.
+			}else{
+				int dropCount = LootTable.contractDroppedMap.get(itemBase);
+				dropCount++;
+				LootTable.contractDroppedMap.put(itemBase, dropCount);
+			}
+
+			break;
+		case WEAPON: //Glass Drop
+
+			if (itemBase.isGlass()){
+				LootTable.glassCount++;
+				if (LootTable.glassDroppedMap.get(itemBase) == null){
+					LootTable.glassDroppedMap.put(itemBase, 1); //First time dropping, make count 1.
+				}else{
+					int dropCount = LootTable.glassDroppedMap.get(itemBase);
+					dropCount++;
+					LootTable.glassDroppedMap.put(itemBase, dropCount);
+				}
+			}
+
+			break;
+		}
+
+	}
+
+	public static Item CreateGamblerItem(Item item, PlayerCharacter gambler){
+
+		if (item == null)
+			return null;
+
+		int groupID = 0;
+
+		switch (item.getItemBase().getUUID()){
+		case 971050: //Wrapped Axe
+			groupID = 3000;
+			break;
+		case 971051://Wrapped Great Axe
+			groupID = 3005;
+			break;
+		case 971052://Wrapped Throwing Axe
+			groupID = 3010;
+			break;
+		case 971053://	Wrapped Bow
+			groupID = 3015;
+			break;
+		case 971054://Wrapped Crossbow
+			groupID = 3020;
+			break;
+		case 971055:	//Wrapped Dagger
+			groupID = 3025;
+			break;
+		case 971056:	//	Wrapped Throwing Dagger
+			groupID = 3030;
+			break;
+		case 971057:	//	Wrapped Hammer
+			groupID = 3035;
+			break;
+		case 971058://			Wrapped Great Hammer
+			groupID = 3040;
+			break;
+		case 971059://			Wrapped Throwing Hammer
+			groupID = 3045;
+			break;
+		case 971060://			Wrapped Polearm
+			groupID = 3050;
+			break;
+		case 971061://			Wrapped Spear
+			groupID = 3055;
+			break;
+		case 971062://			Wrapped Staff
+			groupID = 3060;
+			break;
+		case 971063://			Wrapped Sword
+			groupID = 3065;
+			break;
+		case 971064://			Wrapped Great Sword
+			groupID = 3070;
+			break;
+		case 971065://			Wrapped Unarmed Weapon
+			groupID = 3075;
+			break;
+		case 971066://			Wrapped Cloth Armor
+			groupID = 3100;
+			break;
+		case 971067://			Wrapped Light Armor
+			groupID = 3105;
+			break;
+		case 971068://			Wrapped Medium Armor
+			groupID = 3110;
+			break;
+		case 971069://			Wrapped Heavy Armor
+			groupID = 3115;
+			break;
+		case 971070://			Wrapped Rune
+			groupID = 3200;
+			break;
+		case 971071://			Wrapped City Improvement
+			groupID = 3210;
+			break;
+		}
+		//couldnt find group
+		if (groupID == 0)
+			return null;
+
+
+		LootTable lootGroup = LootTable.lootGroups.get(groupID);
+
+		if (lootGroup == null)
+			return null;
+		float calculatedMobLevel;
+		int minSpawn;
+		int maxSpawn;
+		int spawnQuanity = 0;
+		int prefixValue = 0;
+		int suffixValue = 0;
+		int subTableID;
+		String modifierPrefix = "";
+		String modifierSuffix = "";
+
+		// Lookup Table Variables
+		LootTable lootTable;
+		LootRow lootRow;
+		LootRow groupRow = null;
+		LootTable modTable;
+		LootTable modGroup;
+		LootRow modRow = null;
+
+		// Used for actual generation of items
+		int itemBaseUUID;
+		ItemBase itemBase = null;
+		MobLoot mobLoot;
+
+
+
+		int roll = ThreadLocalRandom.current().nextInt(100) + 1; //Does not return Max, but does return min?
+
+		groupRow = lootGroup.getLootRow(roll);
+
+		lootTable = LootTable.lootTables.get(groupRow.getValueOne());
+		roll = ThreadLocalRandom.current().nextInt(100) + 1;
+		lootRow = lootTable.getLootRow(roll + 220); //get the item row from the bell's curve of level +-15
+
+		if (lootRow == null)
+			return null; //no item found for roll
+
+		itemBaseUUID = lootRow.getValueOne();
+
+
+
+		if (lootRow.getValueOne() == 0)
+			return null;
+
+		//handle quantities > 1 for resource drops
+		minSpawn = lootRow.getValueTwo();
+		maxSpawn = lootRow.getValueThree();
+
+		// spawnQuanity between minspawn (inclusive) and maxspawn (inclusive)
+		if (maxSpawn > 1)
+			spawnQuanity = ThreadLocalRandom.current().nextInt((maxSpawn + 1 - minSpawn)) + minSpawn;
+
+
+
+		//get modifierPrefix
+
+		calculatedMobLevel = 49;
+
+
+
+		int chanceMod = ThreadLocalRandom.current().nextInt(100) + 1;
+
+		if (chanceMod < 25){
+			modGroup = LootTable.modGroups.get(groupRow.getValueTwo());
+
+			if (modGroup != null) {
+
+
+				for (int a = 0;a<10;a++){
+					roll = ThreadLocalRandom.current().nextInt(100) + 1;
+					modRow = modGroup.getLootRow(roll);
+					if (modRow != null)
+						break;
+				}
+
+
+				if (modRow != null) {
+					subTableID = modRow.getValueOne();
+
+					if (LootTable.modTables.containsKey(subTableID)) {
+
+						modTable = LootTable.modTables.get(subTableID);
+
+						roll = gaussianLevel((int)calculatedMobLevel);
+
+						if (roll < modTable.minRoll)
+							roll = (int) modTable.minRoll;
+
+						if (roll > modTable.maxRoll)
+							roll = (int) modTable.maxRoll;
+
+
+
+						modRow = modTable.getLootRow(roll);
+
+						if (modRow != null) {
+							prefixValue = modRow.getValueOne();
+							modifierPrefix = modRow.getAction();
+						}
+					}
+				}
+			}
+		}else if(chanceMod < 50){
+			modGroup = LootTable.modGroups.get(groupRow.getValueThree());
+
+			if (modGroup != null) {
+
+				for (int a = 0;a<10;a++){
+					roll = ThreadLocalRandom.current().nextInt(100) + 1;
+					modRow = modGroup.getLootRow(roll);
+					if (modRow != null)
+						break;
+				}
+
+				if (modRow != null) {
+
+					subTableID = modRow.getValueOne();
+
+					if (LootTable.modTables.containsKey(subTableID)) {
+
+						modTable = LootTable.modTables.get(subTableID);
+						roll = gaussianLevel((int)calculatedMobLevel);
+
+						if (roll < modTable.minRoll)
+							roll = (int) modTable.minRoll;
+
+						if (roll > modTable.maxRoll)
+							roll = (int) modTable.maxRoll;
+
+						modRow = modTable.getLootRow(roll);
+
+						if (modRow == null){
+							modRow = modTable.getLootRow((int) ((modTable.minRoll + modTable.maxRoll) *.05f));
+						}
+
+						if (modRow != null) {
+							suffixValue = modRow.getValueOne();
+							modifierSuffix = modRow.getAction();
+						}
+					}
+				}
+			}
+		}else{
+			modGroup = LootTable.modGroups.get(groupRow.getValueTwo());
+
+			if (modGroup != null) {
+
+
+				for (int a = 0;a<10;a++){
+					roll = ThreadLocalRandom.current().nextInt(100) + 1;
+					modRow = modGroup.getLootRow(roll);
+					if (modRow != null)
+						break;
+				}
+
+
+				if (modRow != null) {
+					subTableID = modRow.getValueOne();
+
+					if (LootTable.modTables.containsKey(subTableID)) {
+
+						modTable = LootTable.modTables.get(subTableID);
+
+						roll = gaussianLevel((int)calculatedMobLevel);
+
+						if (roll < modTable.minRoll)
+							roll = (int) modTable.minRoll;
+
+						if (roll > modTable.maxRoll)
+							roll = (int) modTable.maxRoll;
+
+
+
+						modRow = modTable.getLootRow(roll);
+
+						if (modRow == null){
+							modRow = modTable.getLootRow((int) ((modTable.minRoll + modTable.maxRoll) *.05f));
+						}
+
+						if (modRow != null) {
+							prefixValue = modRow.getValueOne();
+							modifierPrefix = modRow.getAction();
+						}
+					}
+				}
+			}
+
+			//get modifierSuffix
+			modGroup = LootTable.modGroups.get(groupRow.getValueThree());
+
+			if (modGroup != null) {
+
+				for (int a = 0;a<10;a++){
+					roll = ThreadLocalRandom.current().nextInt(100) + 1;
+					modRow = modGroup.getLootRow(roll);
+					if (modRow != null)
+						break;
+				}
+
+				if (modRow != null) {
+
+					subTableID = modRow.getValueOne();
+
+					if (LootTable.modTables.containsKey(subTableID)) {
+
+						modTable = LootTable.modTables.get(subTableID);
+						roll = gaussianLevel((int)calculatedMobLevel);
+
+						if (roll < modTable.minRoll)
+							roll = (int) modTable.minRoll;
+
+						if (roll > modTable.maxRoll)
+							roll = (int) modTable.maxRoll;
+
+						modRow = modTable.getLootRow(roll);
+
+						if (modRow == null){
+							modRow = modTable.getLootRow((int) ((modTable.minRoll + modTable.maxRoll) *.05f));
+						}
+
+						if (modRow != null) {
+							suffixValue = modRow.getValueOne();
+							modifierSuffix = modRow.getAction();
+						}
+					}
+				}
+			}
+		}
+
+
+		itemBase = ItemBase.getItemBase(itemBaseUUID);
+		byte charges = (byte) itemBase.getNumCharges();
+		short dur = (short) itemBase.getDurability();
+
+
+
+		short weight = itemBase.getWeight();
+		if (!gambler.getCharItemManager().hasRoomInventory(weight)) {
+			return null;
+		}
+
+
+		Item gambledItem = new Item(itemBase, gambler.getObjectUUID(),
+				OwnerType.PlayerCharacter, charges, charges, dur, dur,
+				true, false,ItemContainerType.INVENTORY,(byte) 0,
+                new ArrayList<>(),"");
+
+		if (spawnQuanity == 0 && itemBase.getType().equals(ItemType.RESOURCE))
+			spawnQuanity = 1;
+
+		if (spawnQuanity > 0)
+			item.setNumOfItems(spawnQuanity);
+
+		try {
+			gambledItem = DbManager.ItemQueries.ADD_ITEM(gambledItem);
+
+		} catch (Exception e) {
+			Logger.error(e);
+		}
+
+		if (gambledItem == null) {
+
+			return null;
+		}
+		if (!modifierPrefix.isEmpty())
+			gambledItem.addPermanentEnchantment(modifierPrefix, 0);
+
+		if (!modifierSuffix.isEmpty())
+			gambledItem.addPermanentEnchantment(modifierSuffix, 0);
+
+
+
+		//add item to inventory
+		gambler.getCharItemManager().addItemToInventory(gambledItem);
+
+		gambler.getCharItemManager().updateInventory();
+
+		return gambledItem;
+	}
+
+}
diff --git a/src/engine/objects/MaxSkills.java b/src/engine/objects/MaxSkills.java
new file mode 100644
index 00000000..00a12414
--- /dev/null
+++ b/src/engine/objects/MaxSkills.java
@@ -0,0 +1,79 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.objects;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.HashMap;
+
+public class MaxSkills  {
+
+	private int runeID;
+	private int skillToken;
+	private int skillLevel;
+	private int maxSkillPercent;
+
+
+
+	public static HashMap<Integer, ArrayList<MaxSkills>> MaxSkillsSet = new HashMap<>();
+
+
+	/**
+	 * ResultSet Constructor
+	 */
+
+	public MaxSkills(ResultSet rs) throws SQLException {
+		this.runeID = rs.getInt("runeID");
+		this.skillToken =rs.getInt("skillToken");
+		this.skillLevel = rs.getInt("skillLevel");
+        this.maxSkillPercent = rs.getInt("maxSkillPercent");
+    }
+
+	public MaxSkills(int runeID, int skillToken, int skillLevel, int maxSkillPercent) {
+		super();
+		this.runeID = runeID;
+		this.skillToken = skillToken;
+		this.skillLevel = skillLevel;
+        this.maxSkillPercent = maxSkillPercent;
+    }
+
+	public int getRuneID() {
+		return runeID;
+	}
+
+	public void setRuneID(int runeID) {
+		this.runeID = runeID;
+	}
+
+	public int getSkillLevel() {
+		return skillLevel;
+	}
+
+	public void setSkillLevel(int skillLevel) {
+		this.skillLevel = skillLevel;
+	}
+
+	public int getSkillToken() {
+		return skillToken;
+	}
+
+	public void setSkillToken(int skillToken) {
+		this.skillToken = skillToken;
+	}
+
+	public int getMaxSkillPercent() {
+		return maxSkillPercent;
+	}
+
+	public void setMaxSkillPercent(int maxSkillPercent) {
+		this.maxSkillPercent = maxSkillPercent;
+	}
+}
diff --git a/src/engine/objects/MenuOption.java b/src/engine/objects/MenuOption.java
new file mode 100644
index 00000000..8363cac4
--- /dev/null
+++ b/src/engine/objects/MenuOption.java
@@ -0,0 +1,58 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.objects;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+
+public class MenuOption extends AbstractGameObject {
+
+	private final int menuID;
+	private final String message;
+	private final int optionID;
+	private final int prereq;
+
+	/**
+	 * ResultSet Constructor
+	 */
+	public MenuOption(ResultSet rs) throws SQLException {
+		super(rs);
+		this.menuID = rs.getInt("menuID");
+		this.message = rs.getString("message");
+		this.optionID = rs.getInt("optionID");
+		this.prereq = rs.getInt("prereq");
+	}
+
+	/*
+	 * Getters
+	 */
+	public int getMenuID() {
+		return this.menuID;
+	}
+
+	public String getMessage() {
+		return this.message;
+	}
+
+	public int getOptionID() {
+		return this.optionID;
+	}
+
+	public int getPrereq() {
+		return this.prereq;
+	}
+
+	/*
+	 * Database
+	 */
+	@Override
+	public void updateDatabase() {}
+}
diff --git a/src/engine/objects/MeshBounds.java b/src/engine/objects/MeshBounds.java
new file mode 100644
index 00000000..5266746b
--- /dev/null
+++ b/src/engine/objects/MeshBounds.java
@@ -0,0 +1,49 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.objects;
+
+import engine.gameManager.DbManager;
+import engine.math.Bounds;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+public class MeshBounds {
+
+	public int meshID;
+	public final float minX;
+	public final float minY;
+	public final  float minZ;
+	public final float maxX;
+	public final float maxY;
+	public final float maxZ;
+	public final float radius;
+
+	public MeshBounds(ResultSet rs) throws SQLException {
+
+		meshID = rs.getInt("meshID");
+		minX = rs.getFloat("minX"); 
+		minY = rs.getFloat("minY");
+		minZ = rs.getFloat("minZ");
+		maxX = rs.getFloat("maxX");
+		maxY = rs.getFloat("maxY");
+		maxZ = rs.getFloat("maxZ");
+		float radiusX = (int) maxX;
+		float radiusZ = (int) maxZ;
+		
+		radius = Math.max(radiusX,radiusZ);
+	}
+
+	public static void InitializeBuildingBounds(){
+		Bounds.meshBoundsCache = DbManager.BuildingQueries.LOAD_MESH_BOUNDS();
+	}
+	
+
+}
diff --git a/src/engine/objects/Mine.java b/src/engine/objects/Mine.java
new file mode 100644
index 00000000..e53c6d86
--- /dev/null
+++ b/src/engine/objects/Mine.java
@@ -0,0 +1,791 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+
+
+
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.objects;
+
+import engine.Enum;
+import engine.InterestManagement.WorldGrid;
+import engine.db.archive.DataWarehouse;
+import engine.db.archive.MineRecord;
+import engine.gameManager.*;
+import engine.net.ByteBufferWriter;
+import engine.net.client.msg.ErrorPopupMsg;
+import engine.server.MBServerStatics;
+import engine.session.SessionID;
+import org.pmw.tinylog.Logger;
+
+import java.net.UnknownHostException;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.time.LocalDateTime;
+import java.util.ArrayList;
+import java.util.concurrent.ConcurrentHashMap;
+
+import static engine.gameManager.DbManager.MineQueries;
+import static engine.gameManager.DbManager.getObject;
+import static engine.math.FastMath.sqr;
+
+public class Mine extends AbstractGameObject {
+
+	private String zoneName;
+	private Resource production;
+	private boolean isActive = false;
+	private float latitude;
+	private float longitude;
+	private float altitude;
+	private Guild owningGuild;
+	private int lastClaimerID;
+	private SessionID lastClaimerSessionID;
+	private int flags;
+	private int buildingID;
+	private Zone parentZone;
+	private MineProduction mineType;
+	public LocalDateTime openDate;
+
+	public boolean dirtyMine = false;
+	//flags 1: never been claimed (make active).
+
+
+
+
+
+	// Not persisted to DB
+	private String guildName;
+	private GuildTag guildTag;
+	private String nationName;
+	private GuildTag nationTag;
+	public static ConcurrentHashMap<Mine, Integer> mineMap = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW);
+	public static ConcurrentHashMap<Integer, Mine> towerMap = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW);
+
+	private static long lastChange = System.currentTimeMillis();
+	public static LocalDateTime effectiveMineDate;
+
+	/**
+	 * ResultSet Constructor
+	 */
+	public Mine(ResultSet rs) throws SQLException, UnknownHostException {
+		super(rs);
+
+		this.mineType = MineProduction.getByName(rs.getString("mine_type"));
+
+		float offsetX = rs.getFloat("mine_offsetX");
+		float offsetZ = rs.getFloat("mine_offsetZ");
+		int ownerUID = rs.getInt("mine_ownerUID");
+		this.buildingID = rs.getInt("mine_buildingUID");
+		this.flags = rs.getInt("flags");
+		int parent = rs.getInt("parent");
+		this.parentZone = ZoneManager.getZoneByUUID(parent);
+		if (parentZone != null) {
+			this.latitude = parentZone.getLoc().x + offsetX;
+			this.longitude = parentZone.getLoc().z + offsetZ;
+			this.altitude = parentZone.getLoc().y;
+			if (this.parentZone.getParent() != null)
+				this.zoneName = this.parentZone.getParent().getName();
+			else
+				this.zoneName = this.parentZone.getName();
+		} else {
+			Logger.error( "Missing parentZone of ID " + parent);
+			this.latitude = -1000;
+			this.longitude = 1000;
+			this.altitude = 0;
+			this.zoneName = "Unknown Mine";
+		}
+		
+
+
+		this.owningGuild = Guild.getGuild(ownerUID);
+		Guild nation = null;
+		if (this.owningGuild != null && !this.owningGuild.isErrant()) {
+			this.guildName = this.owningGuild.getName();
+			this.guildTag = this.owningGuild.getGuildTag();
+			nation = this.owningGuild.getNation();
+		} else {
+			this.guildName = "";
+			this.guildTag = GuildTag.ERRANT;
+			nation = Guild.getErrantGuild();
+			this.owningGuild = Guild.getErrantGuild();
+		}
+		
+		int mineTime = this.owningGuild.getMineTime();
+
+		if(!nation.isErrant()) {
+			this.nationName = nation.getName();
+			this.nationTag = nation.getGuildTag();
+			mineTime = nation.getMineTime();
+		} else {
+			this.nationName = "";
+			this.nationTag = GuildTag.ERRANT;
+			
+		}
+		this.setActive(false);
+		this.production = Resource.valueOf(rs.getString("mine_resource"));
+
+		this.lastClaimerID = 0;
+		this.lastClaimerSessionID = null;
+
+	java.sql.Timestamp mineTimeStamp = rs.getTimestamp("mine_openDate");
+	
+	
+	Building building = BuildingManager.getBuildingFromCache(this.buildingID);
+	
+
+	if (mineTimeStamp == null && (this.owningGuild == null || this.owningGuild.isErrant() || building.getRank() < 1)){
+		if (building != null){
+			String zoneName = building.getParentZone().getName();
+			String parentZoneName = building.getParentZone().getParent().getName();
+			Logger.info(zoneName + " in " + parentZoneName + " has a dirty mine, setting active.");
+		}
+		this.dirtyMine = true;
+		openDate = LocalDateTime.now().withMinute(0).withSecond(0).withNano(0);
+		return;
+	}else if (this.owningGuild.isErrant() || nation.isErrant()){
+		openDate = LocalDateTime.now().withMinute(0).withSecond(0).withNano(0);
+		return;
+	}else if (mineTimeStamp == null){
+	
+		this.openDate = LocalDateTime.now().withHour(mineTime).withMinute(0).withSecond(0).withNano(0);
+		
+		if (LocalDateTime.now().isAfter(this.openDate.plusHours(1)))
+			this.openDate = this.openDate.plusDays(1);
+		return;
+	}else{
+		this.openDate =  mineTimeStamp.toLocalDateTime().withHour(mineTime);
+		
+		if (LocalDateTime.now().isAfter(this.openDate.plusHours(1))){
+			this.openDate = this.openDate.plusDays(1);
+			return;
+		}
+	}
+	
+	//after 1 day...
+	if(this.openDate.getDayOfYear() - LocalDateTime.now().getDayOfYear() > 1){
+		this.openDate = this.openDate.withDayOfYear(LocalDateTime.now().getDayOfYear());
+		if (LocalDateTime.now().isAfter(this.openDate.plusHours(1)))
+			this.openDate = this.openDate.plusDays(1);
+		return;
+	}
+	
+	}
+
+    public static void SendMineAttackMessage(Building mine){
+
+        if (mine.getBlueprint() == null)
+            return;
+
+        if (mine.getBlueprint().getBuildingGroup() != Enum.BuildingGroup.MINE)
+            return;
+
+        
+        if (mine.getGuild().isErrant())
+        	return;
+        
+        if (mine.getGuild().getNation().isErrant())
+            return;
+
+        if (mine.getTimeStamp("MineAttack") > System.currentTimeMillis())
+            return;
+
+        mine.getTimestamps().put("MineAttack", System.currentTimeMillis() + MBServerStatics.ONE_MINUTE);
+
+        ChatManager.chatNationInfo(mine.getGuild().getNation(), mine.getName() + " in " + mine.getParentZone().getParent().getName() + " is Under attack!");
+    }
+
+    private void setNextMineWindow() {
+    	
+    	//default time, 9 pm est
+    	int mineHour = 21;
+    	
+    	if (this.owningGuild != null || this.owningGuild.isErrant() == false)
+    		mineHour = this.owningGuild.getNation().getMineTime();
+    	int days = 1;
+    	
+    	//midnight hours
+    	if (this.openDate.getHour() != 0 && this.openDate.getHour() != 24 && (this.openDate.getDayOfMonth() == LocalDateTime.now().getDayOfMonth()))
+    		if (mineHour == 0 || mineHour == 24)
+    			days = 2;
+    	
+    	LocalDateTime newTime = this.openDate.plusDays(days).withHour(mineHour).withMinute(0).withSecond(0).withNano(0);
+    	
+    	DbManager.MineQueries.CHANGE_MINE_TIME(this, newTime);
+		this.openDate = newTime;
+	}
+
+	public static void loadAllMines() {
+
+		// Set current mine effective date
+try{
+	
+
+		effectiveMineDate = LocalDateTime.now().withHour(0).withMinute(0).withSecond(0);
+
+		//Load mine resources
+		MineProduction.addResources();
+
+		//pre-load all building sets
+		ArrayList<Mine> serverMines = MineQueries.GET_ALL_MINES_FOR_SERVER();
+
+		for (Mine mine : serverMines) {
+			Mine.mineMap.put(mine, mine.buildingID);
+			Mine.towerMap.put(mine.buildingID, mine);
+			mine.initializeMineTime();
+		}
+		
+}catch (Exception e){
+	e.printStackTrace();
+}
+	}
+
+	/*
+	 * Getters
+	 */
+	private void initializeMineTime(){
+
+		//Mine time has already been set at loading from the database. skip.
+
+		if (this.openDate != null)
+			return;
+
+		Guild nation = null;
+
+		if (this.owningGuild != null)
+			nation = this.owningGuild.getNation();
+
+		int mineTime = (nation != null && !nation.isErrant()) ? nation.getMineTime() : MBServerStatics.MINE_EARLY_WINDOW;
+
+		LocalDateTime openDate = LocalDateTime.now().withHour(mineTime).withMinute(0).withSecond(0).withNano(0);
+
+		//Failed to Update Database, default mine time.
+
+		if (!MineQueries.CHANGE_MINE_TIME(this, openDate)){
+			Logger.info("Mine with UUID " + this.getObjectUUID() + " failed to set Mine Window. Defaulting to Earliest.");
+			openDate = openDate.withHour(MBServerStatics.MINE_EARLY_WINDOW).withMinute(0).withSecond(0).withNano(0);
+			this.openDate = openDate;
+			return;
+		}
+
+		this.openDate = openDate;
+
+	}
+
+	public boolean changeProductionType(Resource resource){
+		if (!this.validForMine(resource))
+			return false;
+		//update resource in database;
+		if(!MineQueries.CHANGE_RESOURCE(this, resource))
+			return false;
+
+		this.production = resource;
+		return true;
+	}
+
+	public MineProduction getMineType() {
+		return this.mineType;
+	}
+
+	public String getZoneName() {
+		return this.zoneName;
+	}
+
+	public Resource getProduction() {
+		return this.production;
+	}
+
+	public boolean getIsActive() {
+		return this.isActive;
+	}
+
+	public float getAltitude() {
+		return this.altitude;
+	}
+
+	public Guild getOwningGuild() {
+		return this.owningGuild;
+	}
+
+	public int getFlags() {
+		return flags;
+	}
+
+	public void setFlags(int flags) {
+		this.flags = flags;
+	}
+
+	public Zone getParentZone() {
+		return parentZone;
+	}
+
+	public GuildTag getGuildTag() {
+		return guildTag;
+	}
+
+	public void setMineType(String type) {
+		this.mineType = MineProduction.getByName(type);
+	}
+
+	public void setActive(boolean isAc) {
+
+		this.isActive = isAc;
+		Building building = BuildingManager.getBuildingFromCache(this.buildingID);
+		if (building != null && !this.isActive)
+			building.isDeranking.compareAndSet(true, false);
+	}
+
+	public void setOwningGuild(Guild owningGuild) {
+		this.owningGuild = owningGuild;
+	}
+
+	public static Mine getMineFromTower(int towerID) {
+		return Mine.towerMap.get(towerID);
+	}
+
+	public boolean validForMine(Resource r) {
+		if (this.mineType == null)
+			return false;
+		return this.mineType.validForMine(r, this.isExpansion());
+	}
+
+	/*
+	 * Serialization
+	 */
+	
+	public static void serializeForClientMsg(Mine mine,ByteBufferWriter writer) {
+		writer.putInt(mine.getObjectType().ordinal());
+		writer.putInt(mine.getObjectUUID());
+		writer.putInt(mine.getObjectUUID()); //actually a hash of mine
+		//		writer.putInt(0x215C92BB); //this.unknown1);
+		writer.putString(mine.mineType.name);
+		writer.putString(mine.zoneName);
+		writer.putInt(mine.production.hash);
+		writer.putInt(mine.production.baseProduction);
+		writer.putInt(mine.getModifiedProductionAmount()); //TODO calculate range penalty here
+		writer.putInt(3600); //window in seconds
+
+		LocalDateTime mw = mine.openDate;
+
+		writer.putLocalDateTime(mw);
+		mw = mw.plusHours(1);
+		writer.putLocalDateTime(mw);
+		writer.put(mine.isActive ? (byte) 0x01 : (byte) 0x00);
+
+		writer.putFloat(mine.latitude);
+		writer.putFloat(mine.altitude);
+		writer.putFloat(mine.longitude);
+		writer.putInt(mine.isExpansion() ? mine.mineType.xpacHash : mine.mineType.hash);
+
+		writer.putString(mine.guildName);
+		GuildTag._serializeForDisplay(mine.guildTag,writer);
+		writer.putString(mine.nationName);
+		GuildTag._serializeForDisplay(mine.nationTag,writer);
+	}
+
+	public void serializeForMineProduction(ByteBufferWriter writer) {
+		writer.putInt(this.getObjectType().ordinal());
+		writer.putInt(this.getObjectUUID());
+		writer.putInt(this.getObjectUUID()); //actually a hash of mine
+		//		writer.putInt(0x215C92BB); //this.unknown1);
+		writer.putString(this.mineType.name);
+		writer.putString(this.zoneName);
+		writer.putInt(this.production.hash);
+		writer.putInt(this.production.baseProduction);
+		writer.putInt(this.getModifiedProductionAmount()); //TODO calculate range penalty here
+		writer.putInt(3600); //window in seconds
+		writer.putInt(this.isExpansion() ? this.mineType.xpacHash : this.mineType.hash);
+	}
+
+	public static ArrayList<Mine> getMinesForGuild(int guildID) {
+		ArrayList<Mine> mineList = new ArrayList<>();
+		for (Mine mine : Mine.mineMap.keySet()) {
+			if (mine.owningGuild != null && mine.owningGuild.getObjectUUID() == guildID)
+				mineList.add(mine);
+		}
+		return mineList;
+	}
+
+	public static long getLastChange() {
+		return lastChange;
+	}
+
+	public static void setLastChange(long lastChange) {
+		Mine.lastChange = lastChange;
+	}
+
+	/*
+	 * Database
+	 */
+	public static Mine getMine(int UID){
+        return MineQueries.GET_MINE(UID);
+
+	}
+
+	public static ArrayList<Mine> getMines() {
+		return new ArrayList<>(mineMap.keySet());
+	}
+
+	@Override
+	public void updateDatabase() {
+		// TODO Create update logic.
+	}
+
+	public int getBuildingID() {
+		return buildingID;
+	}
+
+	public void setBuildingID(int buildingID) {
+		this.buildingID = buildingID;
+	}
+
+	public void handleStartMineWindow() {
+
+		// Do not open errant mines until after woo
+
+		//		if  ((this.getOwningGuild() == null) &&
+		//				(this.getOpenDate().isAfter(DateTime.now())))
+		//			return;
+
+		this.lastClaimerID = 0;
+		this.setActive(true);
+		ChatManager.chatSystemChannel(this.zoneName + "'s Mine is now Active!");
+		Logger.info(this.zoneName + "'s Mine is now Active!");
+	}
+
+	public static boolean validClaimer(PlayerCharacter pc) {
+		//verify the player exists
+		if (pc == null)
+			return false;
+
+		//verify the player is in valid guild
+		Guild g = pc.getGuild();
+		if (g == null) {
+			ChatManager.chatSystemError(pc, "Mine can only be claimed by a guild.");
+			return false;
+		} else if (g.isErrant()) {
+			ChatManager.chatSystemError(pc, "Guild cannot be Errant to claim..");
+			return false;
+		}
+
+		//verify the player is in nation
+		Guild n = g.getNation();
+		if (n.isErrant()) {
+			ChatManager.chatSystemError(pc, "Must have a Nation");
+			return false;
+		}
+
+		
+		if (SessionManager.getPlayerCharacterByID(pc.getObjectUUID()) == null){
+			return false;
+		}
+		//Get a count of nation mines, can't go over capital tol rank.
+		City capital = n.getOwnedCity();
+		City guildCity = g.getOwnedCity();
+		if (guildCity == null){
+			ChatManager.chatSystemError(pc, "Guild must own city to claim.");
+			return false;
+		}
+		if (capital == null) {
+			ChatManager.chatSystemError(pc, "Guild must own city to claim.");
+			return false;
+		}
+
+		if (guildCity.getWarehouse() == null){
+			ChatManager.chatSystemError(pc, "City must own warehouse for to claim.");
+			return false;
+		}
+
+		Building tol = capital.getTOL();
+
+		if (tol == null) {
+			ChatManager.chatSystemError(pc, "Tree of life not found for city.");
+			return false;
+		}
+		
+		int rank = tol.getRank();
+
+		if (rank < 1) {
+			ChatManager.chatSystemError(pc, "Tree of life is not yet sprouted.");
+			return false;
+		}
+
+		int mineCnt = 0;
+
+		mineCnt += Mine.getMinesForGuild(n.getObjectUUID()).size();
+		for (Guild guild: n.getSubGuildList()){
+			mineCnt += Mine.getMinesForGuild(guild.getObjectUUID()).size();
+		}
+
+
+		if (mineCnt > rank) {
+			ChatManager.chatSystemError(pc, "Your Nation can only hold " + tol.getRank() + " mines. Your Nation already has" + mineCnt);
+			return false;
+		}
+
+		return true;
+	}
+
+
+	public void handleDestroyMine() {
+
+		if (!this.isActive)
+			return;
+
+		//remove tags from mine
+
+		this.guildName = "";
+		this.nationName = "";
+		this.owningGuild = null;
+		Mine.setLastChange(System.currentTimeMillis());
+
+		// remove hirelings
+
+		Building building = (Building) getObject(Enum.GameObjectType.Building, this.buildingID);
+		BuildingManager.cleanupHirelings(building);
+	}
+
+	public boolean handleEndMineWindow(){
+
+		Building mineBuilding = BuildingManager.getBuildingFromCache(this.buildingID);
+
+		if (mineBuilding == null){
+			Logger.debug( "Failed to Activate Mine with UID " + this.getObjectUUID() +". Unable to Load Building with UID " +this.buildingID);
+			return false;
+		}
+
+		if (mineBuilding.getRank() > 0) {
+			//never knocked down, let's just move on.
+			//hasn't been claimed since server start.
+			if (this.dirtyMine && this.lastClaimerID == 0 && (this.owningGuild == null || this.owningGuild.isErrant()))
+				return false;
+			this.setActive(false);
+			setNextMineWindow();
+			return true;
+		}
+
+		PlayerCharacter claimer = PlayerCharacter.getFromCache(this.lastClaimerID);
+
+		if (!validClaimer(claimer)){
+			LocalDateTime resetTime = LocalDateTime.now().withDayOfMonth(LocalDateTime.now().getDayOfMonth()).withHour(LocalDateTime.now().getHour()).withMinute(0).withSecond(0).withNano(0);
+			this.openDate = resetTime;
+			return false;
+		}
+			
+
+		//		//verify the player hasn't logged out since claim
+
+		//		if (SessionManager.getSession(claimer) == null)
+		//			return false;
+		//		if (!SessionManager.getSession(claimer).getSessionID().equals(this.lastClaimerSessionID))
+		//			return false;
+
+		if (this.owningGuild == null || this.owningGuild.isErrant() || this.owningGuild.getNation().isErrant()){
+			LocalDateTime resetTime = LocalDateTime.now().withDayOfMonth(LocalDateTime.now().getDayOfMonth()).withHour(LocalDateTime.now().getHour()).withMinute(0).withSecond(0).withNano(0);
+			this.openDate = resetTime;
+			return false;
+		}
+	
+
+		//Update ownership to map
+
+		this.guildName = this.owningGuild.getName();
+		this.guildTag = this.owningGuild.getGuildTag();
+		Guild nation = this.owningGuild.getNation();
+		this.nationName = nation.getName();
+		this.nationTag = nation.getGuildTag();
+		
+		LocalDateTime guildDate = LocalDateTime.now().withHour(this.owningGuild.getMineTime()).withMinute(0).withSecond(0).withNano(0);
+		
+		if (this.openDate.getDayOfMonth() == LocalDateTime.now().getDayOfMonth())
+		if (this.owningGuild.getMineTime() == 0 || this.owningGuild.getMineTime() == 24)
+			guildDate = guildDate.plusDays(1);
+		
+		guildDate = guildDate.withHour(this.owningGuild.getMineTime()).withMinute(0).withSecond(0).withNano(0);
+		this.openDate = guildDate;
+		Mine.setLastChange(System.currentTimeMillis());
+
+		if (mineBuilding.getRank() < 1){
+
+			if (claimer == null){
+				this.lastClaimerID = 0;
+				updateGuildOwner(null);
+				return false;
+			}
+			
+			this.dirtyMine = false;
+
+			mineBuilding.rebuildMine();
+			WorldGrid.updateObject(mineBuilding);
+			ChatManager.chatSystemChannel(claimer.getName() + " has claimed the mine in " + this.parentZone.getParent().getName() + " for " + this.owningGuild.getName() + ". The mine is no longer active.");
+
+			// Warehouse this claim event
+
+			MineRecord mineRecord = MineRecord.borrow(this, claimer, Enum.RecordEventType.CAPTURE);
+			DataWarehouse.pushToWarehouse(mineRecord);
+
+		}else{
+			mineBuilding.setRank(mineBuilding.getRank());
+		}
+
+		this.setActive(false);
+		setNextMineWindow();
+		return true;
+	}
+
+	public boolean claimMine(PlayerCharacter claimer){
+
+		if (claimer == null)
+			return false;
+
+		if (!validClaimer(claimer))
+			return false;
+
+		if (!this.isActive) {
+			ErrorPopupMsg.sendErrorMsg(claimer, "Can not for to claim inactive mine.");
+			return false;
+		}
+
+		if (!updateGuildOwner(claimer))
+			return false;
+
+		this.lastClaimerID = claimer.getObjectUUID();
+		Mine.setLastChange(System.currentTimeMillis());
+		return true;
+	}
+	public boolean depositMineResources(){
+
+		if (this.owningGuild == null)
+			return false;
+
+		if (this.owningGuild.getOwnedCity() == null)
+			return false;
+
+		if (this.owningGuild.getOwnedCity().getWarehouse() == null)
+			return false;
+
+		ItemBase resourceIB = ItemBase.getItemBase(this.production.UUID);
+		return this.owningGuild.getOwnedCity().getWarehouse().depositFromMine(this,resourceIB, this.getModifiedProductionAmount());
+	}
+
+	public boolean updateGuildOwner(PlayerCharacter pc){
+
+		Building mineBuilding = BuildingManager.getBuildingFromCache(this.buildingID);
+
+		//should never return null, but let's check just in case.
+
+		if (mineBuilding == null){
+			ChatManager.chatSystemError(pc, "Unable to find mine tower.");
+			Logger.debug("Failed to Update Mine with UID " + this.getObjectUUID() +". Unable to Load Building with UID " +this.buildingID );
+			return false;
+		}
+
+		if (pc == null) {
+			this.owningGuild = null;
+			this.guildName = "None";
+			this.guildTag = GuildTag.ERRANT;
+			this.nationName = "None";
+			this.nationTag = GuildTag.ERRANT;
+			//Update Building.
+			mineBuilding.setOwner(null);
+			WorldGrid.updateObject(mineBuilding);
+			return true;
+		}
+
+		if (SessionManager.getSession(pc) != null) {
+			this.lastClaimerSessionID = SessionManager.getSession(pc).getSessionID();
+		} else {
+			Logger.error("Failed to find session for player " + pc.getObjectUUID());
+
+			return false;
+		}
+		
+		Guild guild = pc.getGuild();
+
+		if (guild.getOwnedCity() == null)
+			return false;
+
+		if (!MineQueries.CHANGE_OWNER(this, guild.getObjectUUID())) {
+			Logger.debug("Database failed to Change Ownership of Mine with UID " + this.getObjectUUID());
+			ChatManager.chatSystemError(pc, "Failed to claim Mine.");
+			return false;
+		}
+
+
+		//All tests passed.
+
+		//update mine.
+		this.owningGuild = guild;
+		//		this.guildName = this.owningGuild.getName();
+		//		this.guildTag = this.owningGuild.getGuildTag();
+		//
+		//		//nation will never return null, read getNation()
+		//		Guild nation = this.owningGuild.getNation();
+		//		this.nationName = nation.getName();
+		//		this.nationTag = nation.getGuildTag();
+
+		//Update Building.
+		PlayerCharacter guildLeader = (PlayerCharacter) Guild.GetGL(this.owningGuild);
+		if (guildLeader != null)
+			mineBuilding.setOwner(guildLeader);
+		WorldGrid.updateObject(mineBuilding);
+		return true;
+	}
+
+	public boolean isExpansion(){
+        return (this.flags & 2) != 0;
+    }
+
+	public int getModifiedProductionAmount(){
+		//TODO Calculate Distance modifications.
+
+		//calculate base values.
+		int baseProduction = this.production.baseProduction;
+		float baseModValue = this.production.baseProduction * .1f;
+		float rankModValue = this.production.baseProduction * .0143f;
+		float totalModded = 0;
+
+		//get Mine Building.
+		Building mineBuilding = BuildingManager.getBuilding(this.buildingID);
+		if (mineBuilding == null)
+			return this.production.baseProduction;
+		for (AbstractCharacter harvester:mineBuilding.getHirelings().keySet()){
+			totalModded += baseModValue;
+			totalModded += rankModValue * harvester.getRank();
+		}
+		//add base production on top;
+		totalModded += baseProduction;
+		//skip distance check for expansion.
+		if (this.isExpansion())
+			return (int) totalModded;
+
+		if (this.owningGuild != null){
+			if(this.owningGuild.getOwnedCity() != null){
+				float distanceSquared = this.owningGuild.getOwnedCity().getLoc().distanceSquared2D(mineBuilding.getLoc());
+
+				if (distanceSquared > sqr(10000 * 3))
+					totalModded *=.25f;
+				else if (distanceSquared > sqr(10000 * 2))
+					totalModded *= .50f;
+				else if (distanceSquared > sqr(10000))
+					totalModded *= .75f;
+			}
+		}
+		return (int) totalModded;
+
+	}
+
+}
diff --git a/src/engine/objects/MineProduction.java b/src/engine/objects/MineProduction.java
new file mode 100644
index 00000000..76c15f0a
--- /dev/null
+++ b/src/engine/objects/MineProduction.java
@@ -0,0 +1,94 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.objects;
+
+import java.util.HashMap;
+
+public enum MineProduction {
+
+	LUMBER("Lumber Camp", new HashMap<>(), Resource.WORMWOOD, 1618637196, 1663491950),
+	ORE("Ore Mine", new HashMap<>(), Resource.OBSIDIAN, 518103023, -788976428),
+	GOLD("Gold Mine", new HashMap<>(), Resource.GALVOR, -662193002, -1227205358),
+	MAGIC("Magic Mine", new HashMap<>(), Resource.BLOODSTONE, 504746863, -1753567069);
+
+	public final String name;
+	public final HashMap<Integer, Resource> resources;
+	public final Resource xpac;
+	public final int hash;
+	public final int xpacHash;
+
+	MineProduction(String name, HashMap<Integer, Resource>resources, Resource xpac, int hash, int xpacHash) {
+		this.name = name;
+		this.resources = resources;
+		this.xpac = xpac;
+		this.hash = hash;
+		this.xpacHash = xpacHash;
+	}
+
+	public static void addResources() {
+		if (MineProduction.LUMBER.resources.size() == 0) {
+			MineProduction.LUMBER.resources.put(7, Resource.GOLD);
+			MineProduction.LUMBER.resources.put(1580004, Resource.LUMBER);
+			MineProduction.LUMBER.resources.put(1580005, Resource.OAK);
+			MineProduction.LUMBER.resources.put(1580006, Resource.BRONZEWOOD);
+			MineProduction.LUMBER.resources.put(1580007, Resource.MANDRAKE);
+		}
+		if (MineProduction.ORE.resources.size() == 0) {
+			MineProduction.ORE.resources.put(7, Resource.GOLD);
+			MineProduction.ORE.resources.put(1580000, Resource.STONE);
+			MineProduction.ORE.resources.put(1580001, Resource.TRUESTEEL);
+			MineProduction.ORE.resources.put(1580002, Resource.IRON);
+			MineProduction.ORE.resources.put(1580003, Resource.ADAMANT);
+		}
+		if (MineProduction.GOLD.resources.size() == 0) {
+			MineProduction.GOLD.resources.put(7, Resource.GOLD);
+			MineProduction.GOLD.resources.put(1580000, Resource.STONE);
+			MineProduction.GOLD.resources.put(1580008, Resource.COAL);
+			MineProduction.GOLD.resources.put(1580009, Resource.AGATE);
+			MineProduction.GOLD.resources.put(1580010, Resource.DIAMOND);
+			MineProduction.GOLD.resources.put(1580011, Resource.ONYX);
+		}
+		if (MineProduction.MAGIC.resources.size() == 0) {
+			MineProduction.MAGIC.resources.put(7, Resource.GOLD);
+			MineProduction.MAGIC.resources.put(1580012, Resource.AZOTH);
+			MineProduction.MAGIC.resources.put(1580013, Resource.ORICHALK);
+			MineProduction.MAGIC.resources.put(1580014, Resource.ANTIMONY);
+			MineProduction.MAGIC.resources.put(1580015, Resource.SULFUR);
+			MineProduction.MAGIC.resources.put(1580016, Resource.QUICKSILVER);
+		}
+	}
+
+	public static MineProduction getByName(String name) {
+		if (name.toLowerCase().equals("lumber"))
+			return MineProduction.LUMBER;
+		else if (name.toLowerCase().equals("ore"))
+			return MineProduction.ORE;
+		else if (name.toLowerCase().equals("gold"))
+			return MineProduction.GOLD;
+		else
+			return MineProduction.MAGIC;
+	}
+
+	public boolean validForMine(Resource r, boolean isXpac) {
+		if (r == null)
+			return false;
+		if (this.resources.containsKey(r.UUID))
+			return true;
+		else return isXpac && r.UUID == this.xpac.UUID;
+    }
+	
+	
+
+//Name			Xpac		Resources
+//Lumber Camp	Wormwood	Gold, Lumber, Oak, Bronzewood, Mandrake
+//Ore Mine		Obsidian	Gold, Stone, Truesteal, Iron, Adamant
+//Gold Mine		Galvor		Gold, Coal, Agate, Diamond, Onyx
+//Magic Mine	Bloodstone	Gold, Orichalk, Azoth, Antimony, Quicksilver, Sulfer
+}
diff --git a/src/engine/objects/Mob.java b/src/engine/objects/Mob.java
new file mode 100644
index 00000000..dfd30590
--- /dev/null
+++ b/src/engine/objects/Mob.java
@@ -0,0 +1,3011 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.objects;
+
+import engine.Enum;
+import engine.Enum.*;
+import engine.InterestManagement.HeightMap;
+import engine.InterestManagement.WorldGrid;
+import engine.ai.MobileFSM;
+import engine.ai.MobileFSM.STATE;
+import engine.exception.SerializationException;
+import engine.gameManager.*;
+import engine.job.JobContainer;
+import engine.job.JobScheduler;
+import engine.jobs.DeferredPowerJob;
+import engine.jobs.UpgradeNPCJob;
+import engine.math.Bounds;
+import engine.math.Vector3fImmutable;
+import engine.net.ByteBufferWriter;
+import engine.net.Dispatch;
+import engine.net.DispatchMessage;
+import engine.net.client.ClientConnection;
+import engine.net.client.msg.ErrorPopupMsg;
+import engine.net.client.msg.ManageCityAssetsMsg;
+import engine.net.client.msg.PetMsg;
+import engine.net.client.msg.PlaceAssetMsg;
+import engine.powers.EffectsBase;
+import engine.server.MBServerStatics;
+import engine.server.world.WorldServer;
+import org.joda.time.DateTime;
+import org.pmw.tinylog.Logger;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ThreadLocalRandom;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+import static engine.net.client.msg.ErrorPopupMsg.sendErrorPopup;
+
+public class Mob extends AbstractIntelligenceAgent {
+
+	protected int dbID; //the database ID
+	protected int loadID;
+	protected boolean isMob;
+	protected MobBase mobBase;
+
+	//mob specific
+
+	protected float spawnRadius;
+	protected int spawnTime;
+
+	//used by static mobs
+	protected int parentZoneID;
+	protected Zone parentZone;
+	protected float statLat;
+	protected float statLon;
+	protected float statAlt;
+	protected Building building;
+	protected Contract contract;
+	private static ReentrantReadWriteLock createLock = new ReentrantReadWriteLock();
+	private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
+
+	// Variables NOT to be stored in db
+	private static int staticID = 0;
+	private int currentID;
+	private int ownerUID = 0; //only used by pets
+	private boolean hasLoot = false;
+	private static ConcurrentHashMap<Integer, Mob> mobMapByDBID = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW);
+	private AbstractWorldObject fearedObject = null;
+	private int buildingID;
+	private boolean isSiege = false;
+	private boolean isPlayerGuard = false;
+	private long timeToSpawnSiege;
+	private AbstractCharacter npcOwner;
+	private Vector3fImmutable inBuildingLoc = null;
+	private final ConcurrentHashMap<Integer, Boolean> playerAgroMap = new ConcurrentHashMap<>();
+	private boolean noAggro = false;
+	private STATE state = STATE.Disabled;
+	private int aggroTargetID = 0;
+	private boolean walkingHome = true;
+	private long lastAttackTime = 0;
+	private long deathTime = 0;
+	private ConcurrentHashMap<Mob, Integer> siegeMinionMap = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW);
+	public ReentrantReadWriteLock minionLock = new ReentrantReadWriteLock();
+	private int patrolPointIndex = 0;
+	private int lastMobPowerToken = 0;
+	private  HashMap<Integer, MobEquipment> equip = null;
+	private String nameOverride = "";
+	private Regions lastRegion = null;
+	private long despawnTime = 0;
+	private DeferredPowerJob weaponPower;
+	private DateTime upgradeDateTime = null;
+	private boolean lootSync = false;
+	private int fidalityID = 0;
+	private int equipmentSetID = 0;
+	private int lootSet = 0;
+	private boolean isGuard;
+	private ArrayList<Integer> fidelityRunes = null;
+	
+	public boolean despawned = false;
+	public Vector3fImmutable destination = Vector3fImmutable.ZERO;
+	public Vector3fImmutable localLoc = Vector3fImmutable.ZERO;
+
+	/**
+	 * No Id Constructor
+	 */
+	public Mob(String firstName, String lastName, short statStrCurrent, short statDexCurrent, short statConCurrent,
+			short statIntCurrent, short statSpiCurrent, short level, int exp, boolean sit, boolean walk, boolean combat, Vector3fImmutable bindLoc,
+			Vector3fImmutable currentLoc, Vector3fImmutable faceDir, short healthCurrent, short manaCurrent, short stamCurrent, Guild guild,
+			byte runningTrains, int npcType, boolean isMob, Zone parent,Building building, int contractID) {
+		super( firstName, lastName, statStrCurrent, statDexCurrent, statConCurrent, statIntCurrent, statSpiCurrent, level, exp, sit,
+				walk, combat, bindLoc, currentLoc, faceDir, healthCurrent, manaCurrent, stamCurrent, guild, runningTrains);
+		
+		this.dbID = MBServerStatics.NO_DB_ROW_ASSIGNED_YET;
+		this.state = STATE.Idle;
+		this.loadID = npcType;
+		this.isMob = isMob;
+		this.mobBase = MobBase.getMobBase(loadID);
+		this.currentID = MBServerStatics.NO_DB_ROW_ASSIGNED_YET;
+		this.parentZone = parent;
+		this.parentZoneID = (parent != null) ? parent.getObjectUUID() : 0;
+		this.building = building;
+
+		if (building != null)
+			this.buildingID = building.getObjectUUID();
+		else this.buildingID = 0;
+
+		if (contractID == 0)
+			this.contract = null;
+		else
+			this.contract = DbManager.ContractQueries.GET_CONTRACT(contractID);
+
+		if (this.contract != null)
+			this.level = 10;
+
+		//initializeMob(false, false);
+		clearStatic();
+	}
+
+	/**
+	 * Normal Constructor
+	 */
+	public Mob(String firstName, String lastName, short statStrCurrent, short statDexCurrent, short statConCurrent,
+			short statIntCurrent, short statSpiCurrent, short level, int exp, boolean sit, boolean walk, boolean combat, Vector3fImmutable bindLoc,
+			Vector3fImmutable currentLoc, Vector3fImmutable faceDir, short healthCurrent, short manaCurrent, short stamCurrent, Guild guild,
+			byte runningTrains, int npcType, boolean isMob, Zone parent, int newUUID, Building building, int contractID) {
+		super( firstName, lastName, statStrCurrent, statDexCurrent, statConCurrent, statIntCurrent, statSpiCurrent, level, exp, sit,
+				walk, combat, bindLoc, currentLoc, faceDir, healthCurrent, manaCurrent, stamCurrent, guild, runningTrains, newUUID);
+		this.state = STATE.Idle;
+		this.dbID = newUUID;
+		this.loadID = npcType;
+		this.isMob = isMob;
+
+		if (contractID == 0)
+			this.contract = null;
+		else
+			this.contract = DbManager.ContractQueries.GET_CONTRACT(contractID);
+
+		this.mobBase = MobBase.getMobBase(loadID);
+		this.parentZone = parent;
+		this.parentZoneID = (parent != null) ? parent.getObjectUUID() : 0;
+		this.building = building;
+		initializeMob(false,false,false);
+		clearStatic();
+	}
+
+	/**
+	 * Pet Constructor
+	 */
+	public Mob( MobBase mobBase, Guild guild, Zone parent, short level, PlayerCharacter owner, int tableID) {
+		super(mobBase.getFirstName(), "", (short) 0, (short) 0, (short) 0, (short) 0, (short) 0, level, 0, false, true, false, owner.getLoc(), owner.getLoc(), owner.getFaceDir(), (short) mobBase.getHealthMax(), (short) 0, (short) 0, guild, (byte) 0, tableID);
+		this.state = STATE.Idle;
+		this.dbID = tableID;
+		this.loadID = mobBase.getObjectUUID();
+		this.isMob = true;
+		this.mobBase = mobBase;
+		this.parentZone = parent;
+		this.parentZoneID = (parent != null) ? parent.getObjectUUID() : 0;
+		this.ownerUID = owner.getObjectUUID();
+		initializeMob(true,false,false);
+		clearStatic();
+	}
+	//SIEGE CONSTRUCTOR
+	public Mob( MobBase mobBase, Guild guild, Zone parent, short level, Vector3fImmutable loc, int tableID,boolean isPlayerGuard) {
+		super( mobBase.getFirstName(), "", (short) 0, (short) 0, (short) 0, (short) 0, (short) 0, level, 0, false, true, false, loc, loc, Vector3fImmutable.ZERO, (short) mobBase.getHealthMax(), (short) 0, (short) 0, guild, (byte) 0, tableID);
+		this.dbID = tableID;
+		this.loadID = mobBase.getObjectUUID();
+		this.isMob = true;
+		this.mobBase = mobBase;
+		this.parentZone = parent;
+		this.parentZoneID = (parent != null) ? parent.getObjectUUID() : 0;
+		this.ownerUID = 0;
+		this.equip = new HashMap<>();
+		initializeMob(false,true, isPlayerGuard);
+		clearStatic();
+	}
+
+	/**
+	 * ResultSet Constructor
+	 */
+	public Mob(ResultSet rs) throws SQLException {
+
+		super(rs);
+
+		try{
+			this.dbID = rs.getInt(1);
+			this.state = STATE.Idle;
+			this.loadID = rs.getInt("mob_mobbaseID");
+			this.gridObjectType = GridObjectType.DYNAMIC;
+			this.spawnRadius = rs.getFloat("mob_spawnRadius");
+			this.spawnTime = rs.getInt("mob_spawnTime");
+			this.isMob = true;
+			this.parentZone = null;
+			this.statLat = rs.getFloat("mob_spawnX");
+			this.statAlt = rs.getFloat("mob_spawnY");
+			this.statLon = rs.getFloat("mob_spawnZ");
+			
+			this.localLoc = new Vector3fImmutable(this.statLat,this.statAlt,this.statLon);
+			
+			this.parentZoneID = rs.getInt("parent");
+			this.level = (short) rs.getInt("mob_level");
+			int buildingID = rs.getInt("mob_buildingID");
+
+			try {
+				this.building = BuildingManager.getBuilding(buildingID);
+			}catch(Exception e){
+				this.building = null;
+				Logger.error(e.getMessage());
+			}
+
+			int contractID = rs.getInt("mob_contractID");
+
+			if (contractID == 0)
+				this.contract = null;
+			else
+				this.contract = DbManager.ContractQueries.GET_CONTRACT(contractID);
+
+			if (this.contract != null)
+				if (NPC.ISGuardCaptain(contract.getContractID())){
+					this.spawnTime = 60*15;
+					this.isPlayerGuard = true;
+					this.nameOverride = contract.getName();
+				}
+
+			int guildID = rs.getInt("mob_guildUID");
+		
+
+			if (this.fidalityID != 0){
+				if (this.building != null)
+					this.guild = this.building.getGuild();
+				else
+					this.guild = Guild.getGuild(guildID);
+			}else
+				if (this.building != null)
+					this.guild = this.building.getGuild();
+				else
+					this.guild = Guild.getGuild(guildID);
+			
+			if (this.guild == null)
+				this.guild = Guild.getErrantGuild();
+
+			java.util.Date sqlDateTime;
+			sqlDateTime = rs.getTimestamp("upgradeDate");
+
+			if (sqlDateTime != null)
+				upgradeDateTime = new DateTime(sqlDateTime);
+			else
+				upgradeDateTime = null;
+
+			// Submit upgrade job if NPC is currently set to rank.
+
+			if (this.upgradeDateTime != null)
+				Mob.submitUpgradeJob(this);
+
+			this.mobBase = MobBase.getMobBase(loadID);
+			
+			this.setObjectTypeMask(MBServerStatics.MASK_MOB | this.getTypeMasks());
+
+			if (this.mobBase != null && this.spawnTime == 0)
+				this.spawnTime = this.mobBase.getSpawnTime();
+			
+			this.bindLoc = new Vector3fImmutable(this.statLat, this.statAlt,this.statLon);
+
+			this.setParentZone(ZoneManager.getZoneByUUID(this.parentZoneID));
+			
+
+			this.fidalityID = rs.getInt("fidalityID");
+
+			this.equipmentSetID = rs.getInt("equipmentSet");
+			
+			if (this.contract != null)
+				this.equipmentSetID = this.contract.getEquipmentSet();
+
+			this.lootSet = (rs.getInt("lootSet"));
+
+			if (this.fidalityID != 0)
+				this.nameOverride = rs.getString("mob_name");
+
+			if (this.fidalityID != 0){
+
+				Zone parentZone = ZoneManager.getZoneByUUID(this.parentZoneID);
+				if (parentZone != null){
+					this.fidelityRunes = WorldServer.ZoneFidelityMobRunes.get(parentZone.getLoadNum()).get(this.fidalityID);
+
+					if (this.fidelityRunes != null)
+						for (Integer runeID : this.fidelityRunes){
+							if (runeID == 252623 ){
+								this.isGuard = true;
+								this.noAggro = true;
+							}
+						}
+				}
+			}
+		} catch(Exception e){
+			Logger.error( currentID + "");
+		}
+
+		try{
+			initializeMob(false,false,this.isPlayerGuard);
+		} catch(Exception e){
+			Logger.error(e);
+		}
+
+	}
+
+	private void clearStatic() {
+
+		if (this.parentZone != null)
+			this.parentZone.zoneMobSet.remove(this);
+
+		this.parentZone = null;
+		this.statLat = 0f;
+		this.statLon = 0f;
+		this.statAlt = 0f;
+	}
+
+	private void initializeMob(boolean isPet, boolean isSiege, boolean isGuard) {
+
+		if (this.mobBase != null) {
+			
+			this.gridObjectType = GridObjectType.DYNAMIC;
+			this.healthMax = this.mobBase.getHealthMax();
+			this.manaMax = 0;
+			this.staminaMax = 0;
+			this.setHealth(this.healthMax);
+			this.mana.set(this.manaMax);
+			this.stamina.set(this.staminaMax);
+
+			if(!this.nameOverride.isEmpty())
+				this.firstName = this.nameOverride;
+			else
+				this.firstName = this.mobBase.getFirstName();
+			if (isPet) {
+				this.setObjectTypeMask(MBServerStatics.MASK_PET | this.getTypeMasks());
+				if (ConfigManager.serverType.equals(ServerType.LOGINSERVER))
+				this.setLoc(this.getLoc());
+			}
+			if (!isPet && this.contract == null) {
+				this.level = (short) this.mobBase.getLevel();
+			}
+
+		} else
+			this.level = 1;
+
+		//add this npc to building
+		if (this.building != null && this.loadID != 0 && this.fidalityID == 0) {
+
+			int maxSlots;
+			maxSlots = building.getBlueprint().getSlotsForRank(this.building.getRank());
+
+			for (int slot = 1; slot < maxSlots + 1; slot++) {
+				if (!this.building.getHirelings().containsValue(slot)) {
+					this.building.getHirelings().put(this, slot);
+					break;
+				}
+			}
+		}
+
+		//set bonuses
+		this.bonuses = new PlayerBonuses(this);
+
+		//TODO set these correctly later
+		this.rangeHandOne = 8;
+		this.rangeHandTwo = -1;
+		this.minDamageHandOne = 0;
+		this.maxDamageHandOne = 0;
+		this.minDamageHandTwo = 1;
+		this.maxDamageHandTwo = 4;
+		this.atrHandOne = 300;
+		this.atrHandOne = 300;
+		this.defenseRating = (short) this.mobBase.getDefenseRating();
+		this.isActive = true;
+
+		this.charItemManager.load();
+
+		//load AI for general mobs.
+
+		if (isPet || isSiege || (isGuard && this.contract == null))
+			this.currentID =  (--Mob.staticID);
+		 else
+			this.currentID = this.dbID;
+
+		if (!isPet && !isSiege && !this.isPlayerGuard)
+			loadInventory();
+
+		//store mobs by Database ID
+
+		if (!isPet && !isSiege)
+			Mob.mobMapByDBID.put(this.dbID, this);
+	}
+
+	private void initializeSkills() {
+
+		if (this.mobBase.getMobBaseStats() == null)
+			return;
+
+		long skillVector = this.mobBase.getMobBaseStats().getSkillSet();
+		int skillValue = this.mobBase.getMobBaseStats().getSkillValue();
+
+		if (this.mobBase.getObjectUUID() >= 17233) {
+			for (CharacterSkills cs : CharacterSkills.values()) {
+				SkillsBase sb = DbManager.SkillsBaseQueries.GET_BASE_BY_TOKEN(cs.getToken());
+				CharacterSkill css = new CharacterSkill(sb, this, 50);
+				this.skills.put(sb.getName(), css);
+			}
+		} else {
+			for (CharacterSkills cs : CharacterSkills.values()) {
+				if ((skillVector & cs.getFlag()) != 0) {
+					SkillsBase sb = DbManager.SkillsBaseQueries.GET_BASE_BY_TOKEN(cs.getToken());
+					CharacterSkill css = new CharacterSkill(sb, this, skillValue);
+					this.skills.put(sb.getName(), css);
+				}
+			}
+		}
+	}
+
+	private void initializeStaticEffects() {
+
+		EffectsBase eb = null;
+		for (MobBaseEffects mbe : this.mobBase.getRaceEffectsList()) {
+
+			eb = PowersManager.getEffectByToken(mbe.getToken());
+
+			if (eb == null) {
+				Logger.info( "EffectsBase Null for Token " + mbe.getToken());
+				continue;
+			}
+
+			//check to upgrade effects if needed.
+			if (this.effects.containsKey(Integer.toString(eb.getUUID()))) {
+				if (mbe.getReqLvl() > (int) this.level)
+					continue;
+
+				Effect eff = this.effects.get(Integer.toString(eb.getUUID()));
+
+				if (eff == null)
+					continue;
+
+				if (eff.getTrains() > mbe.getRank())
+					continue;
+
+				//new effect is of a higher rank. remove old effect and apply new one.
+				eff.cancelJob();
+				this.addEffectNoTimer(Integer.toString(eb.getUUID()), eb, mbe.getRank(), true);
+			} else {
+				if (mbe.getReqLvl() > (int) this.level)
+					continue;
+
+				this.addEffectNoTimer(Integer.toString(eb.getUUID()), eb, mbe.getRank(), true);
+			}
+		}
+
+		//Apply all rune effects.
+		// Only Captains have contracts
+		if (contract != null || this.isPlayerGuard){
+			RuneBase guardRune = RuneBase.getRuneBase(252621);
+			for (MobBaseEffects mbe : guardRune.getEffectsList()) {
+
+				eb = PowersManager.getEffectByToken(mbe.getToken());
+
+				if (eb == null) {
+					Logger.info( "EffectsBase Null for Token " + mbe.getToken());
+					continue;
+				}
+
+				//check to upgrade effects if needed.
+				if (this.effects.containsKey(Integer.toString(eb.getUUID()))) {
+
+					if (mbe.getReqLvl() > (int) this.level)
+						continue;
+
+					Effect eff = this.effects.get(Integer.toString(eb.getUUID()));
+
+					if (eff == null)
+						continue;
+
+					//Current effect is a higher rank, dont apply.
+					if (eff.getTrains() > mbe.getRank())
+						continue;
+
+					//new effect is of a higher rank. remove old effect and apply new one.
+					eff.cancelJob();
+					this.addEffectNoTimer(Integer.toString(eb.getUUID()), eb, mbe.getRank(), true);
+				} else {
+
+					if (mbe.getReqLvl() > (int) this.level)
+						continue;
+
+					this.addEffectNoTimer(Integer.toString(eb.getUUID()), eb, mbe.getRank(), true);
+				}
+			}
+
+			RuneBase WarriorRune = RuneBase.getRuneBase(2518);
+			for (MobBaseEffects mbe : WarriorRune.getEffectsList()) {
+
+				eb = PowersManager.getEffectByToken(mbe.getToken());
+
+				if (eb == null) {
+					Logger.info( "EffectsBase Null for Token " + mbe.getToken());
+					continue;
+				}
+
+				//check to upgrade effects if needed.
+				if (this.effects.containsKey(Integer.toString(eb.getUUID()))) {
+
+					if (mbe.getReqLvl() > (int) this.level)
+						continue;
+
+					Effect eff = this.effects.get(Integer.toString(eb.getUUID()));
+
+					if (eff == null)
+						continue;
+
+					//Current effect is a higher rank, dont apply.
+					if (eff.getTrains() > mbe.getRank())
+						continue;
+
+					//new effect is of a higher rank. remove old effect and apply new one.
+					eff.cancelJob();
+					this.addEffectNoTimer(Integer.toString(eb.getUUID()), eb, mbe.getRank(), true);
+				} else {
+
+					if (mbe.getReqLvl() > (int) this.level)
+						continue;
+
+					this.addEffectNoTimer(Integer.toString(eb.getUUID()), eb, mbe.getRank(), true);
+				}
+			}
+		}
+
+		if (this.fidelityRunes != null){
+
+			for (int fidelityRune : this.fidelityRunes) {
+
+				RuneBase rune = RuneBase.getRuneBase(fidelityRune);
+
+				if (rune != null)
+					for (MobBaseEffects mbe : rune.getEffectsList()) {
+
+						eb = PowersManager.getEffectByToken(mbe.getToken());
+						if (eb == null) {
+							Logger.info("EffectsBase Null for Token " + mbe.getToken());
+							continue;
+						}
+
+						//check to upgrade effects if needed.
+						if (this.effects.containsKey(Integer.toString(eb.getUUID()))) {
+							if (mbe.getReqLvl() > (int) this.level)
+								continue;
+
+							Effect eff = this.effects.get(Integer.toString(eb.getUUID()));
+
+							if (eff == null)
+								continue;
+
+							//Current effect is a higher rank, dont apply.
+							if (eff.getTrains() > mbe.getRank())
+								continue;
+
+							//new effect is of a higher rank. remove old effect and apply new one.
+							eff.cancelJob();
+							this.addEffectNoTimer(Integer.toString(eb.getUUID()), eb, mbe.getRank(), true);
+
+						} else {
+
+							if (mbe.getReqLvl() > (int) this.level)
+								continue;
+
+							this.addEffectNoTimer(Integer.toString(eb.getUUID()), eb, mbe.getRank(), true);
+						}
+					}
+			}
+		}else
+			for (RuneBase rune : this.mobBase.getRunes()) {
+				for (MobBaseEffects mbe : rune.getEffectsList()) {
+
+					eb = PowersManager.getEffectByToken(mbe.getToken());
+					if (eb == null) {
+						Logger.info( "EffectsBase Null for Token " + mbe.getToken());
+						continue;
+					}
+
+					//check to upgrade effects if needed.
+					if (this.effects.containsKey(Integer.toString(eb.getUUID()))) {
+						if (mbe.getReqLvl() > (int) this.level)
+							continue;
+
+						Effect eff = this.effects.get(Integer.toString(eb.getUUID()));
+
+						if (eff == null)
+							continue;
+
+						//Current effect is a higher rank, dont apply.
+						if (eff.getTrains() > mbe.getRank())
+							continue;
+
+						//new effect is of a higher rank. remove old effect and apply new one.
+						eff.cancelJob();
+						this.addEffectNoTimer(Integer.toString(eb.getUUID()), eb, mbe.getRank(), true);
+					} else {
+
+						if (mbe.getReqLvl() > (int) this.level)
+							continue;
+
+						this.addEffectNoTimer(Integer.toString(eb.getUUID()), eb, mbe.getRank(), true);
+					}
+				}
+			}
+	}
+
+	/*
+	 * Getters
+	 */
+	@Override
+	public int getDBID() {
+		return this.dbID;
+	}
+
+	public int getLoadID() {
+		return loadID;
+	}
+
+	@Override
+	public int getObjectUUID() {
+		return currentID;
+	}
+
+	public float getSpawnX() {
+		return this.statLat;
+	}
+
+	public float getSpawnY() {
+		return this.statAlt;
+	}
+
+	public float getSpawnZ() {
+		return this.statLon;
+	}
+
+	public float getSpawnRadius() {
+		return this.spawnRadius;
+	}
+
+	public int getSpawnTime() {
+
+		if (this.spawnTime == 0)
+			return MBServerStatics.RESPAWN_TIMER;
+		 else
+			return this.spawnTime * 1000;
+	}
+
+	//use getSpawnTime instead. This is just for init tables
+	public int getTrueSpawnTime() {
+		return this.spawnTime;
+	}
+
+	public String getSpawnTimeAsString() {
+		if (this.spawnTime == 0)
+			return MBServerStatics.DEFAULT_SPAWN_TIME_MS / 1000 + " seconds (Default)";
+		 else
+			return this.spawnTime + " seconds";
+
+	}
+
+
+	public void setSpawnTime(int value) {
+		this.spawnTime = value;
+	}
+
+	@Override
+	public MobBase getMobBase() {
+		return this.mobBase;
+	}
+
+	public int getMobBaseID() {
+
+		if (this.mobBase != null)
+			return this.mobBase.getObjectUUID();
+
+		return 0;
+	}
+
+	public Vector3fImmutable getTrueBindLoc() {
+		return this.bindLoc;
+	}
+
+	public Zone getParentZone() {
+		return this.parentZone;
+	}
+
+	public int getParentZoneID() {
+
+		if (this.parentZone != null)
+			return this.parentZone.getObjectUUID();
+
+		return 0;
+	}
+
+	@Override
+	public int getGuildUUID() {
+
+		if (this.guild == null)
+			return 0;
+
+		return this.guild.getObjectUUID();
+	}
+
+	@Override
+	public PlayerCharacter getOwner() {
+
+		if (!this.isPet())
+			return null;
+
+		if (this.ownerUID == 0)
+			return null;
+
+		return PlayerCharacter.getFromCache(this.ownerUID);
+	}
+
+	public void setOwner(PlayerCharacter value) {
+
+		if (value == null)
+			this.ownerUID = 0;
+		else
+			this.ownerUID = value.getObjectUUID();
+	}
+
+	@Override
+	public AbstractWorldObject getFearedObject() {
+		return this.fearedObject;
+	}
+
+	public void setFearedObject(AbstractWorldObject awo) {
+		this.fearedObject = awo;
+	}
+
+	public void setParentZone(Zone zone) {
+
+		if (this.parentZone == null){
+			zone.zoneMobSet.add(this);
+			this.parentZone = zone;
+		}
+		
+		this.bindLoc = Mob.GetSpawnRadiusLocation(this);
+		this.lastBindLoc = bindLoc;
+		this.setLoc(bindLoc);
+		this.stopMovement(bindLoc);
+	}
+
+	@Override
+	public Vector3fImmutable getBindLoc() {
+
+		if(this.isPet() && !this.isSiege)
+			return this.getOwner() != null? this.getOwner().getLoc() : this.getLoc();
+			return this.bindLoc;
+	}
+
+	/*
+	 * Serialization
+	 */
+	
+	public static void __serializeForClientMsg(Mob mob,ByteBufferWriter writer) throws SerializationException {
+	}
+
+
+	
+	public static void serializeMobForClientMsgOtherPlayer(Mob mob,ByteBufferWriter writer, boolean hideAsciiLastName)
+			throws SerializationException {
+		Mob.serializeForClientMsgOtherPlayer(mob,writer);
+	}
+
+	
+	public static void serializeForClientMsgOtherPlayer(Mob mob, ByteBufferWriter writer)
+			throws SerializationException {
+		writer.putInt(0);
+		writer.putInt(0);
+
+		int tid = (mob.mobBase != null) ? mob.mobBase.getLoadID() : 0;
+		int classID = MobBase.GetClassType(mob.mobBase.getObjectUUID());
+		if (mob.isPet()) {
+			writer.putInt(2);
+			writer.putInt(3);
+			writer.putInt(0);
+			writer.putInt(2522);
+			writer.putInt(GameObjectType.NPCClassRune.ordinal());
+			writer.putInt(mob.currentID);
+		} else if (tid == 100570) { //kur'adar
+			writer.putInt(3);
+			Mob.serializeRune(mob,writer, 3, GameObjectType.NPCClassRuneTwo.ordinal(), 2518); //warrior class
+			serializeRune(mob,writer, 5, GameObjectType.NPCClassRuneThree.ordinal(), 252621); //guard rune
+		} else if (tid == 100962 || tid == 100965) { //Spydraxxx the Mighty, Denigo Tantric
+			writer.putInt(2);
+			serializeRune(mob,writer, 5, GameObjectType.NPCClassRuneTwo.ordinal(), 252621); //guard rune
+		}else if (mob.contract != null || mob.isPlayerGuard){
+			writer.putInt(3);
+			serializeRune(mob,writer, 3, GameObjectType.NPCClassRuneTwo.ordinal(),MobBase.GetClassType(mob.getMobBaseID())); //warrior class
+			serializeRune(mob,writer, 5, GameObjectType.NPCClassRuneThree.ordinal(), 252621); //guard rune
+		}else {
+
+			writer.putInt(1);
+		}
+
+		//Generate Race Rune
+		writer.putInt(1);
+		writer.putInt(0);
+
+		if (mob.mobBase != null)
+			writer.putInt(mob.mobBase.getLoadID());
+		 else
+			writer.putInt(mob.loadID);
+
+		writer.putInt(mob.getObjectType().ordinal());
+		writer.putInt(mob.currentID);
+
+		//Send Stats
+		writer.putInt(5);
+		writer.putInt(0x8AC3C0E6); //Str
+		writer.putInt(0);
+		writer.putInt(0xACB82E33); //Dex
+		writer.putInt(0);
+		writer.putInt(0xB15DC77E); //Con
+		writer.putInt(0);
+		writer.putInt(0xE07B3336); //Int
+		writer.putInt(0);
+		writer.putInt(0xFF665EC3); //Spi
+		writer.putInt(0);
+
+		if (!mob.nameOverride.isEmpty()){
+			writer.putString(mob.nameOverride);
+			writer.putInt(0);
+		} else {
+			writer.putString(mob.firstName);
+			writer.putString(mob.lastName);
+
+		}
+
+
+		writer.putInt(0);
+		writer.putInt(0);
+		writer.putInt(0);
+		writer.putInt(0);
+
+		writer.put((byte) 0);
+		writer.putInt(mob.getObjectType().ordinal());
+		writer.putInt(mob.currentID);
+
+		if (mob.mobBase != null) {
+			writer.putFloat(mob.mobBase.getScale());
+			writer.putFloat(mob.mobBase.getScale());
+			writer.putFloat(mob.mobBase.getScale());
+		} else {
+			writer.putFloat(1.0f);
+			writer.putFloat(1.0f);
+			writer.putFloat(1.0f);
+		}
+
+		//Believe this is spawn loc, ignore for now
+		writer.putVector3f(mob.getLoc());
+
+		//Rotation
+		writer.putFloat(mob.getRot().y);
+
+		//Inventory Stuff
+		writer.putInt(0);
+
+		// get a copy of the equipped items.
+
+
+		if (mob.equip != null){
+
+			writer.putInt(mob.equip.size());
+			
+			for (MobEquipment me:mob.equip.values()){
+				MobEquipment.serializeForClientMsg(me,writer);
+			}
+		 }else{
+			writer.putInt(0);
+		}
+
+		writer.putInt(mob.getRank());
+		writer.putInt(mob.getLevel());
+		writer.putInt(mob.getIsSittingAsInt()); //Standing
+		writer.putInt(mob.getIsWalkingAsInt()); //Walking
+		writer.putInt(mob.getIsCombatAsInt()); //Combat
+		writer.putInt(2); //Unknown
+		writer.putInt(1); //Unknown - Headlights?
+		writer.putInt(0);
+		writer.putInt(0);
+		writer.putInt(0);
+		writer.put((byte) 0);
+		writer.put((byte) 0);
+		writer.put((byte) 0);
+		writer.putInt(0);
+
+		if (mob.contract != null && mob.npcOwner == null){
+			writer.put((byte) 1);
+			writer.putLong(0);
+			writer.putLong(0);
+
+			if (mob.contract != null)
+				writer.putInt(mob.contract.getIconID());
+			 else
+				writer.putInt(0); //npc icon ID
+
+		} else
+			writer.put((byte)0);
+
+
+		if (mob.npcOwner != null){
+			writer.put((byte) 1);
+			writer.putInt(GameObjectType.PlayerCharacter.ordinal());
+			writer.putInt(131117009);
+			writer.putInt(mob.npcOwner.getObjectType().ordinal());
+			writer.putInt(mob.npcOwner.getObjectUUID());
+			writer.putInt(8);
+		}else
+			writer.put((byte)0);
+
+		if (mob.isPet()) {
+
+			writer.put((byte) 1);
+
+			if (mob.getOwner() != null) {
+				writer.putInt(mob.getOwner().getObjectType().ordinal());
+				writer.putInt(mob.getOwner().getObjectUUID());
+			} else {
+				writer.putInt(0); //ownerType
+				writer.putInt(0); //ownerID
+			}
+		} else {
+			writer.put((byte) 0);
+		}
+		writer.putInt(0);
+		writer.putInt(0);
+		writer.putInt(0);
+		writer.putInt(0);
+		writer.putInt(0);
+
+		writer.putInt(0);
+		writer.putInt(0);
+		writer.putInt(0);
+		writer.putInt(0);
+
+		if (!mob.isAlive() && !mob.isPet() && !mob.isNecroPet() && !mob.isSiege && !mob.isPlayerGuard) {
+			writer.putInt(0);
+			writer.putInt(0);
+		}
+
+		writer.put((byte) 0);
+		Guild._serializeForClientMsg(mob.getGuild(),writer);
+		//		writer.putInt(0);
+		//		writer.putInt(0);
+		if (mob.mobBase != null && mob.mobBase.getObjectUUID() == 100570) {
+			writer.putInt(2);
+			writer.putInt(0x00008A2E);
+			writer.putInt(0x1AB84003);
+		} else if (mob.isSiege) {
+			writer.putInt(1);
+			writer.putInt(74620179);
+		} else
+			writer.putInt(0);
+
+		//		writer.putInt(1);
+		//		writer.putInt(0); //0xAC13C5E9 - alternate textures
+		writer.putInt(0); //0xB8400300
+		writer.putInt(0);
+
+		//TODO Guard
+		writer.put((byte) 0);
+		//		writer.put((byte)0); //Is guard..
+
+		writer.putFloat(mob.healthMax);
+		writer.putFloat(mob.health.get());
+
+		//TODO Peace Zone
+		writer.put((byte) 1); //0=show tags, 1=don't
+
+		//DON't LOAD EFFECTS FOR DEAD MOBS.
+
+		if (!mob.isAlive())
+			writer.putInt(0);
+		else{
+			int	indexPosition = writer.position();
+			writer.putInt(0); //placeholder for item cnt
+			int total = 0;
+
+			//	Logger.info("",""+ mob.getEffects().size());
+			for (Effect eff : mob.getEffects().values()) {
+				if (eff.isStatic())
+					continue;
+				if ( !eff.serializeForLoad(writer))
+					continue;
+				++total;
+			}
+
+			writer.putIntAt(total, indexPosition);
+		}
+
+		//        // Effects
+		writer.put((byte) 0);
+	}
+
+	private static void serializeRune(Mob mob,ByteBufferWriter writer, int type, int objectType, int runeID) {
+		writer.putInt(type);
+		writer.putInt(0);
+		writer.putInt(runeID);
+		writer.putInt(objectType);
+		writer.putInt(mob.currentID);
+	}
+
+
+	public void calculateModifiedStats() {
+
+		float strVal = this.mobBase.getMobBaseStats().getBaseStr();
+		float dexVal = this.mobBase.getMobBaseStats().getBaseDex();
+		float conVal = 0; // I believe this will desync the Mobs Health if we call it.
+		float intVal = this.mobBase.getMobBaseStats().getBaseInt();
+		float spiVal = this.mobBase.getMobBaseStats().getBaseSpi();
+
+		// TODO modify for equipment
+		if (this.bonuses != null) {
+			// modify for effects
+			strVal += this.bonuses.getFloat(ModType.Attr, SourceType.Strength);
+			dexVal += this.bonuses.getFloat(ModType.Attr, SourceType.Dexterity);
+			conVal += this.bonuses.getFloat(ModType.Attr, SourceType.Constitution);
+			intVal += this.bonuses.getFloat(ModType.Attr, SourceType.Intelligence);
+			spiVal += this.bonuses.getFloat(ModType.Attr, SourceType.Spirit);
+
+			// apply dex penalty for armor
+			// modify percent amounts. DO THIS LAST!
+			strVal *= (1+this.bonuses.getFloatPercentAll(ModType.Attr, SourceType.Strength)); 
+			dexVal *= (1+this.bonuses.getFloatPercentAll(ModType.Attr, SourceType.Dexterity)); 
+			conVal *= (1+this.bonuses.getFloatPercentAll(ModType.Attr, SourceType.Constitution)); 
+			intVal *= (1+this.bonuses.getFloatPercentAll(ModType.Attr, SourceType.Intelligence)); 
+			spiVal *= (1+this.bonuses.getFloatPercentAll(ModType.Attr, SourceType.Spirit)); 
+		} else {
+			// apply dex penalty for armor
+		}
+
+		// Set current stats
+		this.statStrCurrent = (strVal < 1) ? (short) 1 : (short) strVal;
+		this.statDexCurrent = (dexVal < 1) ? (short) 1 : (short) dexVal;
+		this.statConCurrent = (conVal < 1) ? (short) 1 : (short) conVal;
+		this.statIntCurrent = (intVal < 1) ? (short) 1 : (short) intVal;
+		this.statSpiCurrent = (spiVal < 1) ? (short) 1 : (short) spiVal;
+
+	}
+
+	@Override
+	public float getSpeed() {
+		float bonus = 1;
+		if (this.bonuses != null)
+			// get rune and effect bonuses
+			bonus *= (1 + this.bonuses.getFloatPercentAll(ModType.Speed, SourceType.None));
+
+		if (this.isPlayerGuard){
+			switch (this.mobBase.getLoadID()){
+			case 2111:
+				if (this.isWalk())
+					if (this.isCombat())
+						return Enum.Guards.HumanArcher.getWalkCombatSpeed() * bonus;
+					else return Enum.Guards.HumanArcher.getWalkSpeed() * bonus;
+				else
+					return Enum.Guards.HumanArcher.getRunSpeed() * bonus;
+
+			case 14103:
+				if (this.isWalk())
+					if (this.isCombat())
+						return Enum.Guards.UndeadArcher.getWalkCombatSpeed() * bonus;
+					else return Enum.Guards.UndeadArcher.getWalkSpeed() * bonus;
+				else
+					return Enum.Guards.UndeadArcher.getRunSpeed() * bonus;
+			}
+		}
+		//return combat speeds
+		if (this.isCombat()){
+			if (this.isWalk()){
+				if (this.mobBase.getWalkCombat() <= 0)
+					return MBServerStatics.MOB_SPEED_WALKCOMBAT * bonus;
+				return this.mobBase.getWalkCombat() * bonus;
+			}else{
+				if (this.mobBase.getRunCombat() <= 0)
+					return MBServerStatics.MOB_SPEED_RUNCOMBAT * bonus;
+				return this.mobBase.getRunCombat() * bonus;
+			}
+			//not combat return normal speeds
+		}else{
+			if (this.isWalk()){
+				if (this.mobBase.getWalk() <= 0)
+					return MBServerStatics.MOB_SPEED_WALK * bonus;
+				return this.mobBase.getWalk() * bonus;
+			}else{
+				if (this.mobBase.getRun() <= 0)
+					return MBServerStatics.MOB_SPEED_RUN * bonus;
+				return this.mobBase.getRun() * bonus;
+			}
+		}
+
+	}
+
+	@Override
+	public float getPassiveChance(String type, int AttackerLevel, boolean fromCombat) {
+		//TODO add this later for dodge
+		return 0f;
+	}
+
+	/**
+	 * @ Kill this Character
+	 */
+	@Override
+	public void killCharacter(AbstractCharacter attacker) {
+
+		
+		this.stopMovement(this.getMovementLoc());
+
+		if (attacker != null){
+
+			if (attacker.getObjectType() == GameObjectType.PlayerCharacter) {
+				Group g = GroupManager.getGroup((PlayerCharacter) attacker);
+
+				// Give XP, now handled inside the Experience Object
+				if (!this.isPet() && !this.isNecroPet() && !this.isSummonedPet() && !this.isPlayerGuard)
+					Experience.doExperience((PlayerCharacter) attacker, this, g);
+			} else if (attacker.getObjectType().equals(GameObjectType.Mob)){
+				Mob mobAttacker = (Mob)attacker;
+
+				if (mobAttacker.isPet()){
+
+					PlayerCharacter owner = mobAttacker.getOwner();
+
+					if (owner != null){
+
+						if (!this.isPet() && !this.isNecroPet() && !this.isSummonedPet() && !this.isPlayerGuard){
+							Group g = GroupManager.getGroup(owner);
+
+							// Give XP, now handled inside the Experience Object
+							Experience.doExperience(owner, this, g);
+						}
+
+					}
+				}
+			}
+		}
+		killCleanup();
+	}
+
+	public void updateLocation(){
+
+		if (!this.isMoving())
+			return;
+
+		if (state == STATE.Disabled)
+			return;
+
+		if ( this.isAlive() == false || this.getBonuses().getBool(ModType.Stunned, SourceType.None) || this.getBonuses().getBool(ModType.CannotMove, SourceType.None)) {
+			//Target is stunned or rooted. Don't move
+
+			this.stopMovement(this.getMovementLoc());
+			
+			return;
+		}
+		
+		Vector3fImmutable newLoc = this.getMovementLoc();
+
+		if (newLoc.equals(this.getEndLoc())){
+			this.stopMovement(newLoc);
+			return;
+			//Next upda
+		}
+
+		setLoc(newLoc);
+		//Next update will be end Loc, lets stop him here.
+
+	}
+
+	@Override
+	public void killCharacter(String reason) {
+		killCleanup();
+	}
+
+	private void killCleanup() {
+		Dispatch dispatch;
+
+		try {
+			if (this.isSiege) {
+				this.deathTime = System.currentTimeMillis();
+				this.state = STATE.Dead;
+				try {
+					this.clearEffects();
+				}catch(Exception e){
+					Logger.error( e.getMessage());
+				}
+				this.combatTarget = null;
+				this.hasLoot = false;
+				this.playerAgroMap.clear();
+
+				this.timeToSpawnSiege = System.currentTimeMillis() + 60 * 15 * 1000;
+
+				if (this.isPet()) {
+
+					PlayerCharacter petOwner = this.getOwner();
+
+					if (petOwner != null){
+						this.setOwner(null);
+						petOwner.setPet(null);
+						PetMsg petMsg = new PetMsg(5, null);
+						dispatch = Dispatch.borrow(this.getOwner(), petMsg);
+						DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.PRIMARY);
+					}
+				}
+
+			} else if (this.isPet() || this.isNecroPet()) {
+				this.state = STATE.Disabled;
+
+				this.combatTarget = null;
+				this.hasLoot = false;
+
+				if (this.parentZone != null)
+					this.parentZone.zoneMobSet.remove(this);
+
+				try {
+					this.clearEffects();
+				}catch(Exception e){
+					Logger.error( e.getMessage());
+				}
+				this.playerAgroMap.clear();
+				WorldGrid.RemoveWorldObject(this);
+
+				DbManager.removeFromCache(this);
+
+				// YEAH BONUS CODE!  THANKS UNNAMED ASSHOLE!
+				//WorldServer.removeObject(this);
+				//WorldGrid.INSTANCE.removeWorldObject(this);
+				//owner.getPet().disableIntelligence();
+
+				PlayerCharacter petOwner = this.getOwner();
+
+				if (petOwner != null){
+					this.setOwner(null);
+					petOwner.setPet(null);
+					PetMsg petMsg = new PetMsg(5, null);
+					dispatch = Dispatch.borrow(petOwner, petMsg);
+					DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.PRIMARY);
+				}
+			}  else {
+
+				//cleanup effects
+
+				this.deathTime = System.currentTimeMillis();
+				this.state = STATE.Dead;
+
+				playerAgroMap.clear();
+
+				if (!this.isPlayerGuard){
+
+					ArrayList<MobLoot> alml = LootTable.getMobLootDeath(this, this.getLevel(), this.getLootTable());
+
+					for (MobLoot ml : alml) {
+						this.charItemManager.addItemToInventory(ml);
+					}
+
+					if (this.equip != null){
+
+						for (MobEquipment me: equip.values()){
+							if (me.getDropChance() == 0)
+								continue;
+
+							float chance = ThreadLocalRandom.current().nextFloat();
+
+							if (chance <= me.getDropChance()){
+								MobLoot ml = new MobLoot(this, me.getItemBase(), false);
+								ml.setFidelityEquipID(me.getObjectUUID());
+								this.charItemManager.addItemToInventory(ml);
+							}
+						}
+					}
+				}
+
+			}
+			try {
+				this.clearEffects();
+			}catch(Exception e){
+				Logger.error( e.getMessage());
+			}
+
+			this.combat = false;
+			this.walkMode = true;
+			this.combatTarget = null;
+
+			this.hasLoot = (this.charItemManager.getInventoryCount() > 0) ? true : false;
+
+		} catch (Exception e) {
+			Logger.error(e);
+		}
+	}
+
+	public void respawn() {
+		//Commenting out Mob ID rotation.
+
+		this.despawned = false;
+		this.playerAgroMap.clear();
+		this.setCombatTarget(null);
+		this.setHealth(this.healthMax);
+		this.stamina.set(this.staminaMax);
+		this.mana.set(this.manaMax);
+		this.combat = false;
+		this.walkMode = true;
+		this.combatTarget = null;
+		this.isAlive.set(true);
+		
+		if (!this.isSiege)
+		this.lastBindLoc = Mob.GetSpawnRadiusLocation(this);
+		else
+			this.lastBindLoc = this.bindLoc;
+		this.bindLoc = this.lastBindLoc;
+		this.setLoc(this.lastBindLoc);
+		this.stopMovement(this.lastBindLoc);
+		this.initializeStaticEffects();
+		this.recalculateStats();
+
+		this.setHealth(this.healthMax);
+
+		if (!this.isSiege && !this.isPlayerGuard && contract == null)
+			loadInventory();
+
+		//		LoadJob.getInstance();
+		//		LoadJob.forceLoad(this);
+	}
+
+	public void despawn() {
+		
+		this.despawned = true;
+
+		//WorldServer.removeObject(this);
+		WorldGrid.RemoveWorldObject(this);
+		this.charItemManager.clearInventory();
+		this.despawnTime = System.currentTimeMillis();
+		//		this.setLoc(Vector3fImmutable.ZERO);
+	}
+
+	//Sets the relative position to a parent zone
+	public void setRelPos(Zone zone, float locX, float locY, float locZ) {
+
+		//update mob zone map
+
+		if (this.parentZone != null)
+			this.parentZone.zoneMobSet.remove(this);
+
+		zone.zoneMobSet.add(this);
+
+		this.statLat = locX;
+		this.statAlt = locY;
+		this.statLon = locZ;
+		this.parentZone = zone;
+		this.setBindLoc(new Vector3fImmutable(this.statLat + zone.absX, this.statAlt + zone.absY, this.statLon + zone.absZ));
+	}
+
+	public boolean canRespawn(){
+		return System.currentTimeMillis() > this.despawnTime + 4000;
+	}
+
+	@Override
+	public boolean canBeLooted() {
+		return !this.isAlive();
+	}
+
+	public int getTypeMasks() {
+
+		if (this.mobBase == null)
+			return 0;
+
+		return this.mobBase.getTypeMasks();
+	}
+
+	/**
+	 * Clears and sets the inventory of the Mob. Must be called every time the
+	 * mob is spawned or respawned.
+	 */
+	private void loadInventory() {
+
+		if (!MBServerStatics.ENABLE_MOB_LOOT)
+			return;
+
+		this.charItemManager.clearInventory();
+		this.charItemManager.clearEquip();
+
+		if (isPlayerGuard)
+			return;
+
+		int gold = Mob.randomGoldAmount(this);
+
+		if (gold > 0 && this.getLootTable() != 0) {
+			addGoldToInventory(gold);
+		}
+
+		//add random loot to mob
+		ArrayList<MobLoot> alml = LootTable.getMobLoot(this, this.getLevel(), this.getLootTable(), false); //add hotzone check in later
+
+		for (MobLoot ml : alml) {
+			this.charItemManager.addItemToInventory(ml);
+		}
+
+
+
+		//add special loot to mob
+	}
+
+	private int getLootTable() {
+
+		if (this.mobBase == null)
+			return 0;
+
+		return this.mobBase.getLootTable();
+	}
+
+	/**
+	 * Sets the quantity of gold in the inventory. Calling this multiple times
+	 * will overwrite the gold amount.
+	 *
+	 * @param quantity Quantity of gold.
+	 */
+	private void addGoldToInventory(int quantity) {
+		MobLoot gold = new MobLoot(this, quantity);
+		this.charItemManager.addItemToInventory(gold);
+	}
+
+	/**
+	 * Generate a random quantity of gold for this mob.
+	 *
+	 * @return Quantity of gold
+	 */
+	public static int randomGoldAmount( Mob mob) {
+
+		// percentage chance to drop gold
+
+		//R8 mobs have 100% gold drop.
+		if (mob.getLevel() < 80)
+			if ((ThreadLocalRandom.current().nextDouble() * 100d) > MBServerStatics.GOLD_DROP_PERCENTAGE_CHANCE)
+				return 0;
+
+
+		int level = (int) mob.getLevel();
+		level = (level < 0) ? 0 : level;
+		level = (level > 50) ? 50 : level;
+
+		double minGold;
+		double maxGold;
+
+		if (mob.mobBase != null) {
+			minGold = mob.mobBase.getMinGold();
+			maxGold = mob.mobBase.getMaxGold();
+		} else {
+			minGold = MBServerStatics.GOLD_DROP_MINIMUM_PER_MOB_LEVEL[level];
+			maxGold = MBServerStatics.GOLD_DROP_MAXIMUM_PER_MOB_LEVEL[level];
+		}
+
+		double gold = (ThreadLocalRandom.current().nextDouble() * (maxGold - minGold) + minGold);
+
+
+		//server specific gold multiplier
+		double goldMod = MBServerStatics.GOLD_RATE_MOD;
+		gold *= goldMod;
+
+		//modify for hotzone
+
+		if (ZoneManager.inHotZone(mob.getLoc()))
+			gold *= MBServerStatics.HOT_GOLD_RATE_MOD;
+
+		gold *= MBServerStatics.GOLD_RATE_MOD;
+
+		return (int) gold;
+	}
+
+	public static Mob createMob(int loadID, Vector3fImmutable spawn, Guild guild, boolean isMob, Zone parent,Building building, int contractID) {
+
+		Mob mobWithoutID = new Mob("", "", (short) 0, (short) 0, (short) 0, (short) 0,
+				(short) 0, (short) 1, 0, false, false, false, spawn, spawn, Vector3fImmutable.ZERO,
+				(short) 1, (short) 1, (short) 1, guild, (byte) 0, loadID, isMob, parent,building,contractID);
+
+		if (parent != null) {
+			mobWithoutID.setRelPos(parent, spawn.x - parent.absX, spawn.y - parent.absY, spawn.z - parent.absZ);
+		}
+
+
+		if (mobWithoutID.mobBase == null) {
+			return null;
+		}
+		Mob mob;
+		try {
+			mob = DbManager.MobQueries.ADD_MOB(mobWithoutID, isMob);
+			mob.setObjectTypeMask(MBServerStatics.MASK_MOB | mob.getTypeMasks());
+			mob.setMob();
+			mob.setParentZone(parent);
+		} catch (Exception e) {
+			Logger.error("SQLException:" + e.getMessage());
+			mob = null;
+		}
+		return mob;
+	}
+
+	public static Mob createPet( int loadID, Guild guild, Zone parent, PlayerCharacter owner, short level) {
+		MobBase mobBase = MobBase.getMobBase(loadID);
+		Mob mob = null;
+		if (mobBase == null || owner == null) {
+			return null;
+		}
+		createLock.writeLock().lock();
+		level += 20;
+		try {
+			mob = new Mob( mobBase, guild, parent, level, owner, 0);
+			if (mob.mobBase == null) {
+				return null;
+			}
+			mob.runAfterLoad();
+
+			Vector3fImmutable loc = owner.getLoc();
+			if (parent != null) {
+				mob.setRelPos(parent, loc.x - parent.absX, loc.y - parent.absY, loc.z - parent.absZ);
+			}
+			DbManager.addToCache(mob);
+			mob.setPet(owner, true);
+			mob.setWalkMode(false);
+			mob.state = STATE.Awake;
+
+		} catch (Exception e) {
+			Logger.error(e);
+		} finally {
+			createLock.writeLock().unlock();
+		}
+
+		return mob;
+	}
+
+
+
+	public static int nextStaticID() {
+		int id = Mob.staticID;
+		Mob.staticID++;
+		return id;
+	}
+
+	/*
+	 * Database
+	 */
+
+
+	public static Mob getMob(int id) {
+
+		if (id == 0)
+			return null;
+
+		Mob mob  = (Mob) DbManager.getFromCache(GameObjectType.Mob, id);
+		if (mob != null)
+			return mob;
+		return DbManager.MobQueries.GET_MOB(id);
+	}
+
+	public static Mob getFromCache(int id) {
+
+
+		return (Mob) DbManager.getFromCache(GameObjectType.Mob, id);
+	}
+
+	public static Mob getFromCacheDBID(int id) {
+		if (Mob.mobMapByDBID.containsKey(id)) {
+			return Mob.mobMapByDBID.get(id);
+		}
+		return null;
+	}
+
+	@Override
+	public void updateDatabase() {
+		//		DbManager.MobQueries.updateDatabase(this);
+	}
+
+	public int removeFromDatabase() {
+		return DbManager.MobQueries.DELETE_MOB(this);
+	}
+
+	public void refresh() {
+		if (this.isAlive())
+			WorldGrid.updateObject(this);
+	}
+
+	public void recalculateStats() {
+
+		try {
+			calculateModifiedStats();
+		} catch (Exception e) {
+			Logger.error(e.getMessage());
+		}
+
+		try {
+			calculateAtrDefenseDamage();
+		} catch (Exception e) {
+			Logger.error( this.getMobBaseID() + " /" + e.getMessage());
+		}
+		try {
+			calculateMaxHealthManaStamina();
+		} catch (Exception e) {
+			Logger.error( e.getMessage());
+		}
+
+		Resists.calculateResists(this);
+	}
+
+	public void calculateMaxHealthManaStamina() {
+		float h = 1f;
+		float m = 0f;
+		float s = 0f;
+
+		h = this.mobBase.getHealthMax();
+		m = this.statSpiCurrent;
+		s = this.statConCurrent;
+
+		// Apply any bonuses from runes and effects
+		if (this.bonuses != null) {
+			h += this.bonuses.getFloat(ModType.HealthFull, SourceType.None);
+			m += this.bonuses.getFloat(ModType.ManaFull,SourceType.None);
+			s += this.bonuses.getFloat(ModType.StaminaFull, SourceType.None);
+
+			//apply effects percent modifiers. DO THIS LAST!
+			h *= (1 + this.bonuses.getFloatPercentAll(ModType.HealthFull,SourceType.None));
+			m *= (1 + this.bonuses.getFloatPercentAll(ModType.ManaFull,SourceType.None));
+			s *= (1 + this.bonuses.getFloatPercentAll(ModType.StaminaFull,SourceType.None));
+		}
+
+		// Set max health, mana and stamina
+		if (h > 0)
+			this.healthMax = h;
+		 else
+			this.healthMax = 1;
+
+		if (m > -1)
+			this.manaMax = m;
+		 else
+			this.manaMax = 0;
+
+		if (s > -1)
+			this.staminaMax = s;
+		 else
+			this.staminaMax = 0;
+
+		// Update health, mana and stamina if needed
+		if (this.getHealth() > this.healthMax)
+			this.setHealth(this.healthMax);
+
+		if (this.mana.get() > this.manaMax)
+			this.mana.set(this.manaMax);
+
+		if (this.stamina.get() > this.staminaMax)
+			this.stamina.set(staminaMax);
+
+	}
+
+	public void calculateAtrDefenseDamage() {
+
+		if (this.charItemManager == null || this.equip == null) {
+			Logger.error("Player " + currentID + " missing skills or equipment");
+			defaultAtrAndDamage(true);
+			defaultAtrAndDamage(false);
+			this.defenseRating = 0;
+			return;
+		}
+
+		try {
+			calculateAtrDamageForWeapon(
+					this.equip.get(MBServerStatics.SLOT_MAINHAND), true, this.equip.get(MBServerStatics.SLOT_OFFHAND));
+		} catch (Exception e) {
+
+			this.atrHandOne = (short) this.mobBase.getAttackRating();
+			this.minDamageHandOne = (short) this.mobBase.getMinDmg();
+			this.maxDamageHandOne = (short) this.mobBase.getMaxDmg();
+			this.rangeHandOne = 6.5f;
+			this.speedHandOne = 20;
+			Logger.info("Mobbase ID " + this.getMobBaseID() + " returned an error. setting to default ATR and Damage." + e.getMessage());
+		}
+
+		try {
+			calculateAtrDamageForWeapon(this.equip.get(MBServerStatics.SLOT_OFFHAND), false, this.equip.get(MBServerStatics.SLOT_MAINHAND));
+
+		} catch (Exception e) {
+
+			this.atrHandTwo = (short) this.mobBase.getAttackRating();
+			this.minDamageHandTwo = (short) this.mobBase.getMinDmg();
+			this.maxDamageHandTwo = (short) this.mobBase.getMaxDmg();
+			this.rangeHandTwo = 6.5f;
+			this.speedHandTwo = 20;
+			Logger.info( "Mobbase ID " + this.getMobBaseID() + " returned an error. setting to default ATR and Damage." + e.getMessage());
+		}
+
+		try {
+			float defense = this.mobBase.getDefenseRating();
+			defense += getShieldDefense(equip.get(MBServerStatics.SLOT_OFFHAND));
+			defense += getArmorDefense(equip.get(MBServerStatics.SLOT_HELMET));
+			defense += getArmorDefense(equip.get(MBServerStatics.SLOT_CHEST));
+			defense += getArmorDefense(equip.get(MBServerStatics.SLOT_ARMS));
+			defense += getArmorDefense(equip.get(MBServerStatics.SLOT_GLOVES));
+			defense += getArmorDefense(equip.get(MBServerStatics.SLOT_LEGGINGS));
+			defense += getArmorDefense(equip.get(MBServerStatics.SLOT_FEET));
+			defense += getWeaponDefense(equip);
+
+			if (this.bonuses != null) {
+				// add any bonuses
+				defense += (short) this.bonuses.getFloat(ModType.DCV, SourceType.None);
+
+				// Finally multiply any percent modifiers. DO THIS LAST!
+				float pos_Bonus = 1 + this.bonuses.getFloatPercentPositive(ModType.DCV, SourceType.None);
+
+			
+
+				defense = (short) (defense * pos_Bonus);
+
+				//Lucky rune applies next
+				
+				float neg_Bonus = this.bonuses.getFloatPercentNegative(ModType.DCV, SourceType.None);
+				defense = (short) (defense *(1 + neg_Bonus));
+
+			
+
+			} else {
+				// TODO add error log here
+				Logger.error( "Error: missing bonuses");
+			}
+
+			defense = (defense < 1) ? 1 : defense;
+			this.defenseRating = (short) (defense + 0.5f);
+		} catch (Exception e) {
+			Logger.info("Mobbase ID " + this.getMobBaseID() + " returned an error. Setting to Default Defense." + e.getMessage());
+			this.defenseRating = (short) this.mobBase.getDefense();
+		}
+		// calculate defense for equipment
+	}
+
+	private float getWeaponDefense(HashMap<Integer, MobEquipment> equipped) {
+		MobEquipment weapon = equipped.get(MBServerStatics.SLOT_MAINHAND);
+		ItemBase wb = null;
+		CharacterSkill skill, mastery;
+		float val = 0;
+		boolean unarmed = false;
+		if (weapon == null) {
+			weapon = equipped.get(MBServerStatics.SLOT_OFFHAND);
+
+			if (weapon == null)
+				unarmed = true;
+			 else
+				wb = weapon.getItemBase();
+
+		} else
+			wb = weapon.getItemBase();
+
+		if (wb == null)
+			unarmed = true;
+
+		if (unarmed) {
+			skill = null;
+			mastery = null;
+		} else {
+			skill = this.skills.get(wb.getSkillRequired());
+			mastery = this.skills.get(wb.getMastery());
+		}
+
+		if (skill != null)
+			val += (int) skill.getModifiedAmount() / 2f;
+
+		if (mastery != null)
+			val += (int) mastery.getModifiedAmount() / 2f;
+
+		return val;
+	}
+
+	private float getShieldDefense(MobEquipment shield) {
+
+		if (shield == null)
+			return 0;
+
+		ItemBase ab = shield.getItemBase();
+
+		if (ab == null || !ab.isShield())
+			return 0;
+
+		CharacterSkill blockSkill = this.skills.get("Block");
+		float skillMod;
+
+		if (blockSkill == null) {
+			skillMod = CharacterSkill.getQuickMastery(this, "Block");
+
+			if (skillMod == 0f)
+				return 0;
+
+		} else
+			skillMod = blockSkill.getModifiedAmount();
+
+			//			// Only fighters and healers can block
+			//			if (this.baseClass != null && (this.baseClass.getUUID() == 2500 || this.baseClass.getUUID() == 2501))
+			//				this.bonuses.setBool("Block", true);
+
+		float def = ab.getDefense();
+		//apply item defense bonuses
+		// float val = ((float)ab.getDefense()) * (1 + (skillMod / 100));
+		return (def * (1 + ((int) skillMod / 100f)));
+	}
+
+	private float getArmorDefense(MobEquipment armor) {
+
+		if (armor == null)
+			return 0;
+
+		ItemBase ib = armor.getItemBase();
+
+		if (ib == null)
+			return 0;
+
+		if (!ib.getType().equals(ItemType.ARMOR))
+			return 0;
+
+		if (ib.getSkillRequired().isEmpty())
+			return ib.getDefense();
+
+		CharacterSkill armorSkill = this.skills.get(ib.getSkillRequired());
+
+		if (armorSkill == null)
+			return ib.getDefense();
+
+		float def = ib.getDefense();
+
+		//apply item defense bonuses
+
+		return (def * (1 + ((int) armorSkill.getModifiedAmount() / 50f)));
+	}
+
+	private void calculateAtrDamageForWeapon(MobEquipment weapon, boolean mainHand, MobEquipment otherHand) {
+
+		int baseStrength = 0;
+
+		float skillPercentage, masteryPercentage;
+		float mastDam;
+
+		// make sure weapon exists
+		boolean noWeapon = false;
+		ItemBase wb = null;
+
+		if (weapon == null)
+			noWeapon = true;
+		 else {
+
+			ItemBase ib = weapon.getItemBase();
+
+			if (ib == null)
+				noWeapon = true;
+			 else {
+
+				if (ib.getType().equals(ItemType.WEAPON) == false) {
+					defaultAtrAndDamage(mainHand);
+					return;
+				} else
+					wb = ib;
+			}
+		}
+		float min, max;
+		float speed = 20f;
+		boolean strBased = false;
+
+		// get skill percentages and min and max damage for weapons
+
+		if (noWeapon) {
+
+			if (mainHand)
+				this.rangeHandOne = this.mobBase.getAttackRange();
+			 else
+				this.rangeHandTwo = -1; // set to do not attack
+
+			skillPercentage = getModifiedAmount(this.skills.get("Unarmed Combat"));
+			masteryPercentage = getModifiedAmount(this.skills.get("Unarmed Combat Mastery"));
+
+			if (masteryPercentage == 0f)
+				mastDam = CharacterSkill.getQuickMastery(this, "Unarmed Combat Mastery");
+			 else
+				mastDam = masteryPercentage;
+
+			// TODO Correct these
+			min = this.mobBase.getMinDmg();
+			max = this.mobBase.getMaxDmg();
+		} else {
+
+			if (mainHand)
+				this.rangeHandOne = weapon.getItemBase().getRange() * (1 + (baseStrength / 600));
+			 else
+				this.rangeHandTwo = weapon.getItemBase().getRange() * (1 + (baseStrength / 600));
+
+			skillPercentage = getModifiedAmount(this.skills.get(wb.getSkillRequired()));
+			masteryPercentage = getModifiedAmount(this.skills.get(wb.getMastery()));
+
+			if (masteryPercentage == 0f)
+				mastDam = 0f;
+			else
+				mastDam = masteryPercentage;
+
+			min = (float) wb.getMinDamage();
+			max = (float) wb.getMaxDamage();
+			strBased = wb.isStrBased();
+		}
+
+		// calculate atr
+		float atr = this.mobBase.getAttackRating();
+
+		atr += ((int) skillPercentage * 4f); //<-round down skill% -
+		atr += ((int) masteryPercentage * 3f);
+
+		if (this.statStrCurrent > this.statDexCurrent)
+			atr += statStrCurrent / 2;
+		 else
+			atr += statDexCurrent / 2;
+
+		// add in any bonuses to atr
+		if (this.bonuses != null) {
+			// Add any base bonuses
+			atr += this.bonuses.getFloat(ModType.OCV, SourceType.None);
+
+			// Finally use any multipliers. DO THIS LAST!
+			float pos_Bonus = 1 + this.bonuses.getFloatPercentPositive(ModType.OCV, SourceType.None);
+
+		
+			atr *= pos_Bonus;
+
+			// next precise
+			
+//			atr *= (1 + ((float) this.bonuses.getShort("rune.Attack") / 100));
+
+			//and negative percent modifiers
+			//TODO DO DEBUFFS AFTER?? wILL TEst when finished
+			float neg_Bonus = this.bonuses.getFloatPercentNegative(ModType.OCV, SourceType.None);
+
+			
+
+			atr *= (1 + neg_Bonus);
+		}
+
+		atr = (atr < 1) ? 1 : atr;
+
+		// set atr
+		if (mainHand)
+			this.atrHandOne = (short) (atr + 0.5f);
+		 else
+			this.atrHandTwo = (short) (atr + 0.5f);
+
+		//calculate speed
+
+		if (wb != null)
+			speed = wb.getSpeed();
+		 else
+			speed = 20f; //unarmed attack speed
+
+		if (this.bonuses != null && this.bonuses.getFloat(ModType.AttackDelay, SourceType.None) != 0f) //add effects speed bonus
+			speed *= (1 + this.bonuses.getFloatPercentAll(ModType.AttackDelay, SourceType.None));
+
+		if (speed < 10)
+			speed = 10;
+
+		//add min/max damage bonuses for weapon  **REMOVED
+
+		//if duel wielding, cut damage by 30%
+		// calculate damage
+		float minDamage;
+		float maxDamage;
+		float pri = (strBased) ? (float) this.statStrCurrent : (float) this.statDexCurrent;
+		float sec = (strBased) ? (float) this.statDexCurrent : (float) this.statStrCurrent;
+
+		minDamage = (float) (min * ((0.0315f * Math.pow(pri, 0.75f)) + (0.042f * Math.pow(sec, 0.75f)) + (0.01f * ((int) skillPercentage + (int) mastDam))));
+		maxDamage = (float) (max * ((0.0785f * Math.pow(pri, 0.75f)) + (0.016f * Math.pow(sec, 0.75f)) + (0.0075f * ((int) skillPercentage + (int) mastDam))));
+
+		minDamage = (float) ((int) (minDamage + 0.5f)); //round to nearest decimal
+		maxDamage = (float) ((int) (maxDamage + 0.5f)); //round to nearest decimal
+		//	Logger.info("MobCalculateDamage", "Mob with ID "+ this.getObjectUUID() +   " and MOBBASE with ID " + this.getMobBaseID() + " returned " + minDamage + "/" + maxDamage + " modified Damage.");
+
+		//add Base damage last.
+		float minDamageMod = this.mobBase.getDamageMin();
+		float maxDamageMod = this.mobBase.getDamageMax();
+
+		minDamage += minDamageMod;
+		maxDamage += maxDamageMod;
+
+		// add in any bonuses to damage
+		if (this.bonuses != null) {
+			// Add any base bonuses
+			minDamage += this.bonuses.getFloat(ModType.MinDamage, SourceType.None);
+			maxDamage += this.bonuses.getFloat(ModType.MaxDamage, SourceType.None);
+
+			// Finally use any multipliers. DO THIS LAST!
+			minDamage *= (1 + this.bonuses.getFloatPercentAll(ModType.MinDamage, SourceType.None));
+			maxDamage *= (1 + this.bonuses.getFloatPercentAll(ModType.MaxDamage, SourceType.None));
+		}
+
+		// set damages
+		if (mainHand) {
+			this.minDamageHandOne = (short) minDamage;
+			this.maxDamageHandOne = (short) maxDamage;
+			this.speedHandOne = 30;
+		} else {
+			this.minDamageHandTwo = (short) minDamage;
+			this.maxDamageHandTwo = (short) maxDamage;
+			this.speedHandTwo = 30;
+		}
+	}
+
+	private static float getModifiedAmount(CharacterSkill skill) {
+
+		if (skill == null)
+			return 0f;
+
+		return skill.getModifiedAmount();
+	}
+
+	private void defaultAtrAndDamage(boolean mainHand) {
+
+		if (mainHand) {
+			this.atrHandOne = 0;
+			this.minDamageHandOne = 0;
+			this.maxDamageHandOne = 0;
+			this.rangeHandOne = -1;
+			this.speedHandOne = 20;
+		} else {
+			this.atrHandTwo = 0;
+			this.minDamageHandTwo = 0;
+			this.maxDamageHandTwo = 0;
+			this.rangeHandTwo = -1;
+			this.speedHandTwo = 20;
+		}
+	}
+	
+	public static int getBuildingSlot(Mob mob){
+		int slot = -1;
+
+		if (mob.building == null)
+			return -1;
+
+
+
+		BuildingModelBase buildingModel = BuildingModelBase.getModelBase(mob.building.getMeshUUID());
+
+		if (buildingModel == null)
+			return -1;
+
+		
+			if (mob.building.getHirelings().containsKey(mob))
+				slot =  (mob.building.getHirelings().get(mob));
+		
+
+		if (buildingModel.getNPCLocation(slot) == null)
+			return -1;
+
+
+		return slot;
+	}
+
+	public void setInBuildingLoc(Building inBuilding, AbstractCharacter ac) {
+		
+		Mob mob = null;
+		
+		NPC npc = null;
+		
+		
+		if (ac.getObjectType().equals(GameObjectType.Mob))
+			mob = (Mob)ac;
+		
+		else if (ac.getObjectType().equals(GameObjectType.NPC))
+				npc = (NPC)ac;
+
+		// *** Refactor : Need to take a look at this, make sure
+		// npc's are loaded in correct spots.
+
+		BuildingModelBase buildingModel = BuildingModelBase.getModelBase(inBuilding.getMeshUUID());
+
+		Vector3fImmutable slotLocation = Vector3fImmutable.ZERO;
+
+		if (buildingModel != null){
+
+
+			int putSlot = -1;
+			BuildingLocation buildingLocation = null;
+
+			//-1 slot means no slot available in building.
+			
+			if (npc != null){
+				if (npc.getSiegeMinionMap().containsKey(this))
+					putSlot = npc.getSiegeMinionMap().get(this);
+			}else if (mob != null)
+				if (mob.getSiegeMinionMap().containsKey(this))
+					putSlot = mob.getSiegeMinionMap().get(this);
+			
+			int count = 0;
+			
+			for (BuildingLocation slotLoc: buildingModel.getLocations())
+				if (slotLoc.getType() == 6)
+					count++;
+			
+		
+			buildingLocation = buildingModel.getSlotLocation((count) - putSlot);
+
+			if (buildingLocation != null){
+				slotLocation = buildingLocation.getLoc();
+			}
+
+		}
+		
+		this.inBuildingLoc = slotLocation;
+
+	}
+
+	public Vector3fImmutable getInBuildingLoc() {
+		return inBuildingLoc;
+	}
+
+	public ItemBase getWeaponItemBase(boolean mainHand) {
+
+		if (this.equipmentSetID != 0){
+
+			if (equip != null) {
+				MobEquipment me = null;
+
+				if (mainHand)
+					me = equip.get(1); //mainHand
+				 else
+					me = equip.get(2); //offHand
+
+				if (me != null) {
+
+					ItemBase ib = me.getItemBase();
+
+					if (ib != null)
+						return ib;
+
+				}
+			}
+		}
+		MobBase mb = this.mobBase;
+
+		if (mb != null) {
+
+			if (equip != null) {
+
+				MobEquipment me = null;
+
+				if (mainHand)
+					me = equip.get(1); //mainHand
+				 else
+					me = equip.get(2); //offHand
+
+				if (me != null) {
+
+					ItemBase ib = me.getItemBase();
+
+					if (ib != null)
+						return ib;
+				}
+			}
+		}
+		return null;
+	}
+
+	@Override
+	public void runAfterLoad() {
+
+		try{
+			if (this.equipmentSetID != 0)
+				this.equip = MobBase.loadEquipmentSet(this.equipmentSetID);
+			else
+				this.equip = new HashMap<>();
+
+		} catch(Exception e){
+			Logger.error( e.getMessage());
+		}
+
+		if (this.equip == null) {
+			Logger.error("Null equipset returned for uuid " + currentID);
+			this.equip = new HashMap<>(0);
+		}
+
+		try{
+			this.initializeStaticEffects();
+
+			try {
+				this.initializeSkills();
+			} catch (Exception e) {
+				Logger.error( e.getMessage());
+			}
+
+			recalculateStats();
+			this.setHealth(this.healthMax);
+
+			// Set bounds for this mobile
+			Bounds mobBounds = Bounds.borrow();
+			mobBounds.setBounds(this.getLoc());
+			this.setBounds(mobBounds);
+
+		} catch (Exception e){
+			Logger.error(e.getMessage());
+		}
+	}
+
+	@Override
+	protected ConcurrentHashMap<Integer, CharacterPower> initializePowers() {
+		return new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW);
+	}
+
+	public boolean canSee(PlayerCharacter target) {
+		return this.mobBase.getSeeInvis() >= target.getHidden();
+	}
+
+	public int getBuildingID() {
+		return buildingID;
+	}
+
+	public void setBuildingID(int buildingID) {
+		this.buildingID = buildingID;
+	}
+
+	public boolean isSiege() {
+		return isSiege;
+	}
+
+	public void setSiege(boolean isSiege) {
+		this.isSiege = isSiege;
+	}
+
+	public long getTimeToSpawnSiege() {
+		return timeToSpawnSiege;
+	}
+
+	public void setTimeToSpawnSiege(long timeToSpawnSiege) {
+		this.timeToSpawnSiege = timeToSpawnSiege;
+	}
+
+	public AbstractCharacter getNpcOwner() {
+		return npcOwner;
+	}
+
+	public void setNpcOwner(AbstractCharacter npcOwner) {
+		this.npcOwner = npcOwner;
+	}
+
+	public boolean isNecroPet() {
+		return this.mobBase.isNecroPet();
+	}
+
+	public static void HandleAssistedAggro(PlayerCharacter source, PlayerCharacter target) {
+
+		HashSet<AbstractWorldObject> mobsInRange = WorldGrid.getObjectsInRangePartial(source, MBServerStatics.AI_DROP_AGGRO_RANGE, MBServerStatics.MASK_MOB);
+
+		for (AbstractWorldObject awo : mobsInRange) {
+			Mob mob = (Mob) awo;
+
+			//Mob is not attacking anyone, skip.
+			if (mob.getCombatTarget() == null)
+				continue;
+
+			//Mob not attacking target's target, let's not be failmu and skip this target.
+			if (mob.getCombatTarget() != target)
+				continue;
+
+			//target is mob's combat target, LETS GO.
+			if (source.getHateValue() > target.getHateValue()) {
+				mob.setCombatTarget(source);
+				MobileFSM.setAggro(mob, source.getObjectUUID());
+			}
+		}
+	}
+
+	public void handleDirectAggro(AbstractCharacter ac) {
+
+		if (ac.getObjectType().equals(GameObjectType.PlayerCharacter) == false)
+			return;
+
+		PlayerCharacter player = (PlayerCharacter)ac;
+
+		if (this.getCombatTarget() == null) {
+			MobileFSM.setAggro(this, player.getObjectUUID());
+			return;
+		}
+
+		if (player.getObjectUUID() == this.getCombatTarget().getObjectUUID())
+			return;
+
+		if (this.getCombatTarget().getObjectType() == GameObjectType.PlayerCharacter) {
+
+			if (ac.getHateValue() > ((PlayerCharacter) this.getCombatTarget()).getHateValue()) {
+				this.setCombatTarget(player);
+				MobileFSM.setAggro(this, player.getObjectUUID());
+			}
+		}
+	}
+
+	public boolean remove(Building building) {
+
+		// Remove npc from it's building
+		this.state = STATE.Disabled;
+
+		try {
+			this.clearEffects();
+		}catch(Exception e){
+			Logger.error(e.getMessage());
+		}
+
+		if (this.parentZone != null)
+			this.parentZone.zoneMobSet.remove(this);
+
+		if (building != null) {
+			building.getHirelings().remove(this);
+			this.removeMinions();
+		}
+
+		// Delete npc from database
+
+		if (DbManager.MobQueries.DELETE_MOB(this) == 0)
+			return false;
+
+		// Remove npc from the simulation
+
+		this.removeFromCache();
+		DbManager.removeFromCache(this);
+		WorldGrid.RemoveWorldObject(this);
+		WorldGrid.removeObject(this);
+		return true;
+	}
+
+	public void removeMinions() {
+
+		for (Mob toRemove : this.siegeMinionMap.keySet()) {
+
+			toRemove.state = STATE.Disabled;
+
+			if (this.isMoving()){
+			
+				this.stopMovement(this.getLoc());
+				this.state = STATE.Disabled;
+
+				if (toRemove.parentZone != null)
+					toRemove.parentZone.zoneMobSet.remove(toRemove);
+			}
+
+			try {
+				toRemove.clearEffects();
+			} catch(Exception e){
+				Logger.error(e.getMessage());
+			}
+
+			if (toRemove.parentZone != null)
+				toRemove.parentZone.zoneMobSet.remove(toRemove);
+
+			WorldGrid.RemoveWorldObject(toRemove);
+			WorldGrid.removeObject(toRemove);
+			DbManager.removeFromCache(toRemove);
+
+			PlayerCharacter petOwner = toRemove.getOwner();
+
+			if (petOwner != null) {
+
+				petOwner.setPet(null);
+				toRemove.setOwner(null);
+
+				PetMsg petMsg = new PetMsg(5, null);
+				Dispatch dispatch = Dispatch.borrow(petOwner, petMsg);
+				DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.PRIMARY);
+			}
+		}
+	}
+
+	public static void submitUpgradeJob(Mob mob) {
+
+		JobContainer jc;
+
+		if (mob.getUpgradeDateTime() == null) {
+			Logger.error("Failed to get Upgrade Date");
+			return;
+		}
+
+		// Submit upgrade job for future date or current instant
+
+		if (mob.getUpgradeDateTime().isAfter(DateTime.now()))
+			jc = JobScheduler.getInstance().scheduleJob(new UpgradeNPCJob(mob),
+					mob.getUpgradeDateTime().getMillis());
+		else
+			JobScheduler.getInstance().scheduleJob(new UpgradeNPCJob(mob), 0);
+
+	}
+
+	public void setRank(int newRank) {
+
+		DbManager.MobQueries.SET_PROPERTY(this, "mob_level", newRank);
+		this.level = (short) newRank;
+
+	}
+
+	public static int getUpgradeTime(Mob mob) {
+
+		if (mob.getRank() < 7)
+			return (mob.getRank() * 8);
+
+		return 0;
+	}
+
+	public static int getUpgradeCost(Mob mob) {
+
+		int upgradeCost;
+
+		upgradeCost = Integer.MAX_VALUE;
+
+		if (mob.getRank() < 7)
+			return (mob.getRank() * 100650) + 21450;
+
+		return upgradeCost;
+	}
+
+	public boolean isRanking() {
+
+		return this.upgradeDateTime != null;
+	}
+
+	public boolean isNoAggro() {
+		return noAggro;
+	}
+
+	public void setNoAggro(boolean noAggro) {
+		this.noAggro = noAggro;
+	}
+
+	public STATE getState() {
+		return state;
+	}
+
+	public void setState(STATE state) {
+		this.state = state;
+	}
+
+	public int getAggroTargetID() {
+		return aggroTargetID;
+	}
+
+	public void setAggroTargetID(int aggroTargetID) {
+		this.aggroTargetID = aggroTargetID;
+	}
+
+	public boolean isWalkingHome() {
+		return walkingHome;
+	}
+
+	public void setWalkingHome(boolean walkingHome) {
+		this.walkingHome = walkingHome;
+	}
+
+	public long getLastAttackTime() {
+		return lastAttackTime;
+	}
+
+	public void setLastAttackTime(long lastAttackTime) {
+		this.lastAttackTime = lastAttackTime;
+	}
+
+	public ConcurrentHashMap<Integer, Boolean> getPlayerAgroMap() {
+		return playerAgroMap;
+	}
+
+	public long getDeathTime() {
+		return deathTime;
+	}
+
+	public boolean isHasLoot() {
+		return hasLoot;
+	}
+	public void setDeathTime(long deathTime) {
+		this.deathTime = deathTime;
+	}
+
+	public DeferredPowerJob getWeaponPower() {
+		return weaponPower;
+	}
+
+	public void setWeaponPower(DeferredPowerJob weaponPower) {
+		this.weaponPower = weaponPower;
+	}
+	public ConcurrentHashMap<Mob, Integer> getSiegeMinionMap() {
+		return siegeMinionMap;
+	}
+
+	public Building getBuilding() {
+		return this.building;
+	}
+
+	public DateTime getUpgradeDateTime() {
+
+		lock.readLock().lock();
+
+		try {
+			return upgradeDateTime;
+		} finally {
+			lock.readLock().unlock();
+		}
+	}
+
+	public synchronized Mob createGuardMob(int loadID, Guild guild, Zone parent, Vector3fImmutable loc, short level, String pirateName) {
+
+		MobBase minionMobBase;
+		Mob mob;
+		int maxSlots = 1;
+
+		switch (this.getRank()){
+		case 1:
+		case 2:
+			maxSlots = 1;
+			break;
+		case 3:
+			maxSlots = 2;
+			break;
+		case 4:
+		case 5:
+			maxSlots = 3;
+			break;
+		case 6:
+			maxSlots = 4;
+			break;
+		case 7:
+			maxSlots = 5;
+			break;
+		default:
+			maxSlots = 1;
+
+		}
+
+		if (siegeMinionMap.size() == maxSlots)
+			return null;
+
+		minionMobBase = this.mobBase;
+
+		if (minionMobBase == null)
+			return null;
+
+		mob = new Mob(minionMobBase, guild, parent, level,new Vector3fImmutable(1,1,1), 0,true);
+		
+		mob.despawned = true;
+
+		mob.setLevel(level);
+		//grab equipment and name from minionbase.
+		if (this.contract != null){
+			MinionType minionType = MinionType.ContractToMinionMap.get(this.contract.getContractID());
+			if (minionType != null){
+				mob.equipmentSetID = minionType.getEquipSetID();
+				String rank = "";
+				
+				if (this.getRank() < 3)
+					rank = MBServerStatics.JUNIOR;
+				else if (this.getRank() < 6)
+					rank = "";
+				else if (this.getRank() == 6)
+					rank = MBServerStatics.VETERAN;
+				else
+					rank = MBServerStatics.ELITE;
+				
+				if (rank.isEmpty())
+					mob.nameOverride = pirateName + " " + minionType.getRace() + " " + minionType.getName();
+				else
+					mob.nameOverride = pirateName + " " + minionType.getRace() + " " + rank + " " + minionType.getName();
+			}
+		}
+	
+		
+
+		if (parent != null)
+			mob.setRelPos(parent, loc.x - parent.absX, loc.y - parent.absY, loc.z - parent.absZ);
+
+		mob.setObjectTypeMask(MBServerStatics.MASK_MOB | mob.getTypeMasks());
+
+		// mob.setMob();
+		mob.isPlayerGuard = true;
+		mob.setParentZone(parent);
+		DbManager.addToCache(mob);
+		mob.runAfterLoad();
+		
+		
+
+		RuneBase guardRune = RuneBase.getRuneBase(252621);
+
+		for (MobBaseEffects mbe : guardRune.getEffectsList()) {
+
+			EffectsBase eb = PowersManager.getEffectByToken(mbe.getToken());
+
+			if (eb == null) {
+				Logger.info( "EffectsBase Null for Token " + mbe.getToken());
+				continue;
+			}
+
+			//check to upgrade effects if needed.
+			if (mob.effects.containsKey(Integer.toString(eb.getUUID()))) {
+				if (mbe.getReqLvl() > (int) mob.level) {
+					continue;
+				}
+
+				Effect eff = mob.effects.get(Integer.toString(eb.getUUID()));
+
+				if (eff == null)
+					continue;
+
+				//Current effect is a higher rank, dont apply.
+				if (eff.getTrains() > mbe.getRank())
+					continue;
+
+				//new effect is of a higher rank. remove old effect and apply new one.
+				eff.cancelJob();
+				mob.addEffectNoTimer(Integer.toString(eb.getUUID()), eb, mbe.getRank(), true);
+			} else {
+
+				if (mbe.getReqLvl() > (int) mob.level)
+					continue;
+
+				mob.addEffectNoTimer(Integer.toString(eb.getUUID()), eb, mbe.getRank(), true);
+			}
+		}
+
+		int slot = 0;
+		slot += siegeMinionMap.size() + 1;
+
+		siegeMinionMap.put(mob, slot);
+		mob.setInBuildingLoc(this.building, this);
+		mob.setBindLoc(loc.add(mob.inBuildingLoc));
+		mob.deathTime = System.currentTimeMillis();
+		mob.spawnTime = 900;
+		mob.npcOwner = this;
+		mob.state = STATE.Respawn;
+
+		return mob;
+	}
+
+	public static void setUpgradeDateTime(Mob mob,DateTime upgradeDateTime) {
+
+		if (!DbManager.MobQueries.updateUpgradeTime(mob, upgradeDateTime)){
+			Logger.error("Failed to set upgradeTime for building " + mob.currentID);
+			return;
+		}
+		mob.upgradeDateTime = upgradeDateTime;
+	}
+
+	public Contract getContract() {
+		return contract;
+	}
+
+	public void setContract(Contract contract) {
+		this.contract = contract;
+	}
+
+	public boolean isPlayerGuard() {
+		return isPlayerGuard;
+	}
+
+	public void setPlayerGuard(boolean isPlayerGuard) {
+		this.isPlayerGuard = isPlayerGuard;
+	}
+
+	public int getPatrolPointIndex() {
+		return patrolPointIndex;
+	}
+
+	public void setPatrolPointIndex(int patrolPointIndex) {
+		this.patrolPointIndex = patrolPointIndex;
+	}
+
+	public int getLastMobPowerToken() {
+		return lastMobPowerToken;
+	}
+
+	public void setLastMobPowerToken(int lastMobPowerToken) {
+		this.lastMobPowerToken = lastMobPowerToken;
+	}
+
+	public Regions getLastRegion() {
+		return lastRegion;
+	}
+
+	public void setLastRegion(Regions lastRegion) {
+		this.lastRegion = lastRegion;
+	}
+
+	public boolean isLootSync() {
+		return lootSync;
+	}
+
+	public void setLootSync(boolean lootSync) {
+		this.lootSync = lootSync;
+	}
+
+	public int getFidalityID() {
+		return fidalityID;
+	}
+
+	public HashMap<Integer, MobEquipment> getEquip() {
+		return equip;
+	}
+
+	public int getEquipmentSetID() {
+		return equipmentSetID;
+	}
+
+	public int getLootSet() {
+		return lootSet;
+	}
+
+	public boolean isGuard(){
+		return this.isGuard;
+	}
+
+	public String getNameOverride() {
+		return nameOverride;
+	}
+	
+	public static Vector3fImmutable GetSpawnRadiusLocation(Mob mob){
+		
+		Vector3fImmutable returnLoc = Vector3fImmutable.ZERO;
+		
+		if (mob.fidalityID != 0 && mob.building != null){
+
+			
+			Vector3fImmutable spawnRadiusLoc = Vector3fImmutable.getRandomPointInCircle(mob.localLoc, mob.spawnRadius);
+
+			Vector3fImmutable buildingWorldLoc = ZoneManager.convertLocalToWorld(mob.building, spawnRadiusLoc);
+			
+			return buildingWorldLoc;
+			
+		
+			
+		}else{
+			
+			boolean run = true;
+			
+			while(run){
+				Vector3fImmutable localLoc = new Vector3fImmutable(mob.statLat + mob.parentZone.absX, mob.statAlt + mob.parentZone.absY, mob.statLon + mob.parentZone.absZ);
+				Vector3fImmutable spawnRadiusLoc = Vector3fImmutable.getRandomPointInCircle(localLoc, mob.spawnRadius);
+				
+				//not a roaming mob, just return the random loc.
+				if (mob.spawnRadius < 12000)
+					return spawnRadiusLoc;
+				
+				Zone spawnZone = ZoneManager.findSmallestZone(spawnRadiusLoc);
+				//dont spawn roaming mobs in npc cities
+				if (spawnZone.isNPCCity())
+					continue;
+				
+				//dont spawn roaming mobs in player cities.
+				if (spawnZone.isPlayerCity())
+					continue;
+				
+				//don't spawn mobs in water.
+				if (HeightMap.isLocUnderwater(spawnRadiusLoc))
+					continue;
+				
+				run = false;
+				
+				return spawnRadiusLoc;
+				
+			}
+
+		}
+		
+		//shouldn't ever get here.
+		
+		return returnLoc;
+	}
+	
+	public void processUpgradeMob(PlayerCharacter player){
+		
+		lock.writeLock().lock();
+		
+		try{
+			
+		building = this.getBuilding();
+
+		// Cannot upgrade an npc not within a building
+
+		if (building == null)
+			return;
+
+		// Cannot upgrade an npc at max rank
+
+		if (this.getRank() == 7)
+			return;
+
+		// Cannot upgrade an npc who is currently ranking
+
+		if (this.isRanking())
+			return;
+
+		int rankCost = Mob.getUpgradeCost(this);
+
+		// SEND NOT ENOUGH GOLD ERROR
+
+		if (rankCost > building.getStrongboxValue()) {
+			sendErrorPopup(player, 127);
+			return;
+		}
+
+		try {
+
+			if (!building.transferGold(-rankCost,false)){
+				return;
+			}
+
+			DateTime dateToUpgrade = DateTime.now().plusHours(Mob.getUpgradeTime(this));
+			Mob.setUpgradeDateTime(this,dateToUpgrade);
+
+			// Schedule upgrade job
+
+			Mob.submitUpgradeJob(this);
+
+		} catch (Exception e) {
+			PlaceAssetMsg.sendPlaceAssetError(player.getClientConnection(), 1, "A Serious error has occurred. Please post details for to ensure transaction integrity");
+		}
+		
+		}catch(Exception e){
+			Logger.error(e);
+		}finally{
+			lock.writeLock().unlock();
+		}
+	}
+	
+	public void processRedeedMob(ClientConnection origin) {
+
+		// Member variable declaration
+		PlayerCharacter player;
+		Contract contract;
+		CharacterItemManager itemMan;
+		ItemBase itemBase;
+		Item item;
+
+		this.lock.writeLock().lock();
+		
+		try{
+			
+			player = SessionManager.getPlayerCharacter(origin);
+			itemMan = player.getCharItemManager();
+			
+
+			contract = this.getContract();
+
+			if (!player.getCharItemManager().hasRoomInventory((short)1)){
+				ErrorPopupMsg.sendErrorPopup(player, 21);
+				return;
+			}
+
+
+			if (!building.getHirelings().containsKey(this))
+				return;
+
+			if (!this.remove(building)) {
+				PlaceAssetMsg.sendPlaceAssetError(player.getClientConnection(), 1, "A Serious error has occurred. Please post details for to ensure transaction integrity");
+				return;
+			}
+
+			building.getHirelings().remove(this);
+
+			itemBase = ItemBase.getItemBase(contract.getContractID());
+
+			if (itemBase == null) {
+				Logger.error( "Could not find Contract for npc: " + this.getObjectUUID());
+				return;
+			}
+
+			boolean itemWorked = false;
+
+			item = new Item( itemBase, player.getObjectUUID(), OwnerType.PlayerCharacter, (byte) ((byte) this.getRank() - 1), (byte) ((byte) this.getRank() - 1),
+					(short) 1, (short) 1, true, false, Enum.ItemContainerType.INVENTORY, (byte) 0,
+                    new ArrayList<>(),"");
+			item.setNumOfItems(1);
+			item.containerType = Enum.ItemContainerType.INVENTORY;
+
+			try {
+				item = DbManager.ItemQueries.ADD_ITEM(item);
+				itemWorked = true;
+			} catch (Exception e) {
+				Logger.error(e);
+			}
+			if (itemWorked) {
+				itemMan.addItemToInventory(item);
+				itemMan.updateInventory();
+			}
+
+			ManageCityAssetsMsg mca = new ManageCityAssetsMsg();
+			mca.actionType = NPC.SVR_CLOSE_WINDOW;
+			mca.setTargetType(building.getObjectType().ordinal());
+			mca.setTargetID(building.getObjectUUID());
+			origin.sendMsg(mca);
+		
+
+		}catch(Exception e){
+			Logger.error(e);
+		}finally{
+			this.lock.writeLock().unlock();
+		}
+
+	}
+	public void dismiss() {
+
+		if (this.isPet()) {
+
+			if (this.isSummonedPet()) { //delete summoned pet
+
+				WorldGrid.RemoveWorldObject(this);
+				DbManager.removeFromCache(this);
+				if (this.getObjectType() == GameObjectType.Mob){
+					((Mob)this).setState(STATE.Disabled);
+					if (((Mob)this).getParentZone() != null)
+						((Mob)this).getParentZone().zoneMobSet.remove(this);
+				}
+
+			} else { //revert charmed pet
+				this.setMob();
+				this.setCombatTarget(null);
+				//				if (this.isAlive())
+				//					WorldServer.updateObject(this);
+			}
+			//clear owner
+			PlayerCharacter owner = this.getOwner();
+
+			//close pet window
+			if (owner != null) {
+				Mob pet = owner.getPet();
+				PetMsg pm = new PetMsg(5, null);
+				Dispatch dispatch = Dispatch.borrow(owner, pm);
+				DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+
+				if (pet != null && pet.getObjectUUID() == this.getObjectUUID())
+					owner.setPet(null);
+
+				if (this.getObjectType().equals(GameObjectType.Mob))
+					((Mob)this).setOwner(null);
+			}
+
+
+		}
+	}
+	
+	public void dismissNecroPet(boolean updateOwner) {
+
+		this.state = STATE.Disabled;
+
+		this.combatTarget = null;
+		this.hasLoot = false;
+
+		if (this.parentZone != null)
+			this.parentZone.zoneMobSet.remove(this);
+
+		try {
+			this.clearEffects();
+		}catch(Exception e){
+			Logger.error( e.getMessage());
+		}
+		this.playerAgroMap.clear();
+		WorldGrid.RemoveWorldObject(this);
+
+		DbManager.removeFromCache(this);
+
+		// YEAH BONUS CODE!  THANKS UNNAMED ASSHOLE!
+		//WorldServer.removeObject(this);
+		//WorldGrid.INSTANCE.removeWorldObject(this);
+		//owner.getPet().disableIntelligence();
+
+		PlayerCharacter petOwner = this.getOwner();
+
+		if (petOwner != null){
+			((Mob)this).setOwner(null);
+			petOwner.setPet(null);
+			
+			if (updateOwner == false)
+				return;
+			PetMsg petMsg = new PetMsg(5, null);
+			Dispatch dispatch = Dispatch.borrow(petOwner, petMsg);
+			DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.PRIMARY);
+		}
+	}
+	
+	
+
+	
+}
diff --git a/src/engine/objects/MobBase.java b/src/engine/objects/MobBase.java
new file mode 100644
index 00000000..2c4bf960
--- /dev/null
+++ b/src/engine/objects/MobBase.java
@@ -0,0 +1,396 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.objects;
+
+import ch.claude_martin.enumbitset.EnumBitSet;
+import engine.Enum;
+import engine.gameManager.DbManager;
+import engine.server.MBServerStatics;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.HashMap;
+
+public class MobBase extends AbstractGameObject {
+
+	private final int loadID;
+	private final String firstName;
+	private final byte level;
+	private float healthMax;
+	private int attackRating;
+	private int defenseRating;
+	private float damageMin;
+	private float damageMax;
+	private float hitBoxRadius;
+	private final int lootTable;
+	private final float scale;
+
+	private int minGold;
+	private int maxGold;
+
+	private EnumBitSet<Enum.MobFlagType> flags;
+	private EnumBitSet<Enum.AggroType> noAggro;
+	private int mask;
+
+	private int goldMod;
+	private int seeInvis;
+	private int spawnTime = 0;
+	private int defense = 0;
+	private int atr = 0;
+	private float minDmg = 0;
+	private float maxDmg = 0;
+	private ArrayList<MobBaseEffects> raceEffectsList;
+	private float attackRange;
+	private boolean isNecroPet = false;
+
+	private MobBaseStats mobBaseStats;
+	private ArrayList<RuneBase> runes;
+	private HashMap<Integer, Integer> staticPowers;
+
+	private float walk = 0;
+	private float run = 0;
+	private float walkCombat = 0;
+	private float runCombat = 0;
+
+	/**
+	 * ResultSet Constructor
+	 */
+	public MobBase(ResultSet rs) throws SQLException {
+		super(rs, rs.getInt("ID"));
+
+		this.loadID = rs.getInt("loadID");
+
+		this.firstName = rs.getString("name");
+		this.level = rs.getByte("level");
+		this.lootTable = rs.getInt("lootTableID");
+
+		this.goldMod = rs.getInt("goldMod");
+		this.spawnTime = rs.getInt("spawnTime");
+
+		LevelDefault levelDefault = LevelDefault.getLevelDefault(this.level);
+		this.healthMax = rs.getInt("health");
+		this.damageMin = rs.getFloat("minDmg");
+		this.damageMax = rs.getFloat("maxDmg");
+
+		this.attackRating = rs.getInt("atr");
+		this.defenseRating = rs.getInt("defense");
+		this.attackRange = rs.getFloat("attackRange");
+
+		if (MobbaseGoldEntry.MobbaseGoldMap.containsKey(this.loadID)){
+			MobbaseGoldEntry goldEntry = MobbaseGoldEntry.MobbaseGoldMap.get(this.loadID);
+
+			if (goldEntry != null){
+				this.minGold = goldEntry.getMin();
+				this.maxGold = goldEntry.getMax();
+			}
+		}
+		else
+			if (levelDefault != null) {
+				this.minGold = (levelDefault.goldMin * this.goldMod / 100);
+				this.maxGold = (levelDefault.goldMax * this.goldMod / 100);
+			} else {
+				this.minGold = 10;
+				this.maxGold = 30;
+			}
+
+		this.flags = EnumBitSet.asEnumBitSet(rs.getLong("flags"), Enum.MobFlagType.class);
+		this.noAggro = EnumBitSet.asEnumBitSet(rs.getLong("noaggro"), Enum.AggroType.class);
+
+		this.seeInvis = rs.getInt("seeInvis");
+		this.scale = rs.getFloat("scale");
+		this.hitBoxRadius = 5f;
+		this.mask = 0;
+
+		if (this.getObjectUUID() == 12021 || this.getObjectUUID() == 12022) {
+			this.isNecroPet = true;
+		}
+
+		if (Enum.MobFlagType.HUMANOID.elementOf(this.flags))
+			this.mask += MBServerStatics.MASK_HUMANOID;
+
+		if (Enum.MobFlagType.UNDEAD.elementOf(this.flags))
+			this.mask += MBServerStatics.MASK_UNDEAD;
+
+		if (Enum.MobFlagType.BEAST.elementOf(this.flags))
+			this.mask += MBServerStatics.MASK_BEAST;
+
+		if (Enum.MobFlagType.DRAGON.elementOf(this.flags))
+			this.mask += MBServerStatics.MASK_DRAGON;
+
+		if (Enum.MobFlagType.RAT.elementOf(this.flags))
+			this.mask += MBServerStatics.MASK_RAT;
+
+		this.runes = DbManager.MobBaseQueries.LOAD_RUNES_FOR_MOBBASE(this.loadID);
+		this.raceEffectsList = DbManager.MobBaseQueries.LOAD_STATIC_EFFECTS(this.loadID);
+		this.mobBaseStats = DbManager.MobBaseQueries.LOAD_STATS(this.loadID);
+		DbManager.MobBaseQueries.LOAD_ALL_MOBBASE_LOOT(this.loadID);
+		DbManager.MobBaseQueries.LOAD_ALL_MOBBASE_SPEEDS(this);
+
+	}
+
+	public static HashMap<Integer, MobEquipment> loadEquipmentSet(int equipmentSetID){
+
+		ArrayList<EquipmentSetEntry> equipList;
+		HashMap<Integer, MobEquipment> equip = new HashMap<>();
+
+		if (equipmentSetID == 0)
+			return equip;
+
+		equipList = EquipmentSetEntry.EquipmentSetMap.get(equipmentSetID);
+
+		if (equipList == null)
+			return equip;
+
+		for (EquipmentSetEntry equipmentSetEntry : equipList) {
+
+			MobEquipment mobEquipment = new MobEquipment(equipmentSetEntry.getItemID(), equipmentSetEntry.getDropChance());
+			ItemBase itemBase = mobEquipment.getItemBase();
+
+			if (itemBase != null) {
+				if (itemBase.getType().equals(Enum.ItemType.WEAPON))
+					if (mobEquipment.getSlot() == 1 && itemBase.getEquipFlag() == 2)
+						mobEquipment.setSlot(2);
+
+				equip.put(mobEquipment.getSlot(), mobEquipment);
+			}
+		}
+
+		return equip;
+	}
+
+	public HashMap<Integer, Integer> getStaticPowers() {
+		return staticPowers;
+	}
+
+	public void updateStaticEffects() {
+		this.raceEffectsList = DbManager.MobBaseQueries.LOAD_STATIC_EFFECTS(this.getObjectUUID());
+	}
+
+	public void updateRunes() {
+		this.runes = DbManager.MobBaseQueries.LOAD_RUNES_FOR_MOBBASE(this.getObjectUUID());
+	}
+
+	public void updatePowers() {
+		this.staticPowers = DbManager.MobBaseQueries.LOAD_STATIC_POWERS(this.getObjectUUID());
+	}
+
+	public void updateSpeeds(float walk, float walkCombat,float run, float runCombat){
+		this.walk = walk;
+		this.walkCombat = walkCombat;
+		this.run = run;
+		this.runCombat = runCombat;
+
+	}
+
+	/*
+	 * Getters
+	 */
+	public String getFirstName() {
+		return this.firstName;
+	}
+
+	public int getLoadID() {
+		return this.loadID;
+	}
+
+	public int getLevel() {
+		return this.level;
+	}
+
+	public int getLootTable() {
+		return this.lootTable;
+	}
+
+	public float getHealthMax() {
+		return this.healthMax;
+	}
+
+	public float getDamageMin() {
+		return this.damageMin;
+	}
+
+	public float getDamageMax() {
+		return this.damageMax;
+	}
+
+	public int getAttackRating() {
+		return this.attackRating;
+	}
+
+	public int getDefenseRating() {
+		return this.defenseRating;
+	}
+
+	public int getMinGold() {
+		return this.minGold;
+	}
+
+	public int getMaxGold() {
+		return this.maxGold;
+	}
+
+	public EnumBitSet<Enum.MobFlagType> getFlags() {
+		return this.flags;
+	}
+
+	public EnumBitSet getNoAggro() {
+		return this.noAggro;
+	}
+
+	public int getGoldMod() {
+		return this.goldMod;
+	}
+
+	public float getScale() {
+		return this.scale;
+	}
+
+	public int getTypeMasks() {
+		return this.mask;
+	}
+
+	public int getSeeInvis() {
+		return this.seeInvis;
+	}
+
+	public int getSpawnTime() {
+		return this.spawnTime;
+	}
+
+
+
+	/*
+	 * Database
+	 */
+	public static MobBase getMobBase(int id) {
+		return MobBase.getMobBase(id, false);
+	}
+
+	public static MobBase getMobBase(int id, boolean forceDB) {
+		return DbManager.MobBaseQueries.GET_MOBBASE(id, forceDB);
+	}
+
+	public static MobBase copyMobBase(MobBase mobbase, String name) {
+		return DbManager.MobBaseQueries.COPY_MOBBASE(mobbase, name);
+	}
+
+	public static boolean renameMobBase(int ID, String newName) {
+		return DbManager.MobBaseQueries.RENAME_MOBBASE(ID, newName);
+	}
+
+	@Override
+	public void updateDatabase() {
+		// TODO Create update logic.
+	}
+
+	public float getHitBoxRadius() {
+		if (this.hitBoxRadius < 0f) {
+			return 0f;
+		} else {
+			return this.hitBoxRadius;
+		}
+	}
+
+	public MobBaseStats getMobBaseStats() {
+		return mobBaseStats;
+	}
+
+	public float getMaxDmg() {
+		return maxDmg;
+	}
+
+	public float getMinDmg() {
+		return minDmg;
+	}
+
+	public int getAtr() {
+		return atr;
+	}
+
+	public void setAtr(int atr) {
+		this.atr = atr;
+	}
+
+	public int getDefense() {
+		return defense;
+	}
+
+	public void setDefense(int defense) {
+		this.defense = defense;
+	}
+
+	/**
+	 * @return the raceEffectsList
+	 */
+	public ArrayList<MobBaseEffects> getRaceEffectsList() {
+		return raceEffectsList;
+	}
+
+	/**
+	 * @return the runes
+	 */
+	public ArrayList<RuneBase> getRunes() {
+		return runes;
+	}
+
+	public float getAttackRange() {
+		return attackRange;
+	}
+
+	public boolean isNecroPet() {
+		return isNecroPet;
+	}
+
+	public static int GetClassType(int mobbaseID){
+
+		switch (mobbaseID){
+		case 17235:
+		case 17233:
+		case 17256:
+		case 17259:
+		case 17260:
+		case 17261:
+			return 2518;
+		case 17258:
+		case 17257:
+		case 17237:
+		case 17234:
+			return 2521;
+		default:
+			return 2518;
+		}
+	}
+
+	public float getWalk() {
+		return walk;
+	}
+
+	public void setWalk(float walk) {
+		this.walk = walk;
+	}
+
+	public float getRun() {
+		return run;
+	}
+
+	public void setRun(float run) {
+		this.run = run;
+	}
+
+	public float getWalkCombat() {
+		return walkCombat;
+	}
+
+	public float getRunCombat() {
+		return runCombat;
+	}
+
+}
diff --git a/src/engine/objects/MobBaseEffects.java b/src/engine/objects/MobBaseEffects.java
new file mode 100644
index 00000000..d8c08172
--- /dev/null
+++ b/src/engine/objects/MobBaseEffects.java
@@ -0,0 +1,66 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.objects;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+
+public class MobBaseEffects  {
+
+	private int mobBaseID;
+	private int token;
+	private int rank;
+	private int reqLvl;
+	
+
+
+	/**
+	 * ResultSet Constructor
+	 */
+	public MobBaseEffects(ResultSet rs) throws SQLException {
+		this.token = rs.getInt("token");
+		this.rank = rs.getInt("rank");
+		this.reqLvl = rs.getInt("reqLvl");
+	}
+
+
+	/**
+	 * @return the mobBaseID
+	 */
+	public int getMobBaseID() {
+		return mobBaseID;
+	}
+
+
+
+	public void setMobBaseID(int mobBaseID) {
+		this.mobBaseID = mobBaseID;
+	}
+
+
+	public int getToken() {
+		return token;
+	}
+
+
+	public int getRank() {
+		return rank;
+	}
+
+
+
+	public int getReqLvl() {
+		return reqLvl;
+	}
+
+
+
+}
diff --git a/src/engine/objects/MobBaseStats.java b/src/engine/objects/MobBaseStats.java
new file mode 100644
index 00000000..60a4681a
--- /dev/null
+++ b/src/engine/objects/MobBaseStats.java
@@ -0,0 +1,95 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.objects;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+
+
+public class MobBaseStats  {
+
+	private final int baseStr;
+	private final int baseInt;
+	private final int baseCon;
+	private final int baseSpi;
+	private final int baseDex;
+	private final long skillSet;
+	private final int skillValue;
+	public static MobBaseStats mbs = null;
+
+
+	/**
+	 * ResultSet Constructor
+	 */
+	public MobBaseStats(ResultSet rs) throws SQLException {
+		this.baseStr = rs.getInt("Strength");
+		this.baseInt = rs.getInt("Intelligence");
+		this.baseCon = rs.getInt("Constitution");
+		this.baseSpi = rs.getInt("Spirit");
+		this.baseDex = rs.getInt("Dexterity");
+		this.skillSet = rs.getLong("baseSkills");
+		this.skillValue = rs.getInt("skillAmount");
+	}
+
+	/**
+	 * Generic Constructor
+	 */
+
+	public MobBaseStats()  {
+		this.baseStr = 0;
+		this.baseInt = 0;
+		this.baseCon = 0;
+		this.baseSpi = 0;
+		this.baseDex = 0;
+		this.skillSet = 0;
+		this.skillValue = 0;
+	}
+	public int getBaseStr() {
+		return baseStr;
+	}
+
+
+	public int getBaseInt() {
+		return baseInt;
+	}
+
+
+	public int getBaseCon() {
+		return baseCon;
+	}
+
+
+	public int getBaseSpi() {
+		return baseSpi;
+	}
+
+
+	public int getBaseDex() {
+		return baseDex;
+	}
+
+	public long getSkillSet() {
+		return skillSet;
+	}
+
+	public int getSkillValue() {
+		return skillValue;
+	}
+	
+	public static MobBaseStats GetGenericStats(){
+		if (mbs != null)
+			return mbs;
+        mbs = new MobBaseStats();
+		return mbs;
+	}
+
+
+}
diff --git a/src/engine/objects/MobEquipment.java b/src/engine/objects/MobEquipment.java
new file mode 100644
index 00000000..deb9d405
--- /dev/null
+++ b/src/engine/objects/MobEquipment.java
@@ -0,0 +1,396 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.objects;
+
+import engine.exception.SerializationException;
+import engine.gameManager.PowersManager;
+import engine.net.ByteBufferWriter;
+import engine.powers.EffectsBase;
+import engine.powers.poweractions.AbstractPowerAction;
+import org.pmw.tinylog.Logger;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.concurrent.atomic.AtomicInteger;
+
+public class MobEquipment extends AbstractGameObject {
+
+	private static AtomicInteger equipCounter = new AtomicInteger(0);
+	private final ItemBase itemBase;
+	private int slot;
+	private int parentID;
+
+	//effects
+	private boolean enchanted;
+	private boolean isID = false;
+	private AbstractPowerAction prefix;
+	private AbstractPowerAction suffix;
+	private int pValue;
+	private int sValue;
+	private int magicValue;
+
+	private float dropChance = 0;
+
+	/**
+	 * No Id Constructor
+	 */
+	public MobEquipment(ItemBase itemBase, int slot, int parentID) {
+		super(MobEquipment.getNewID());
+		this.itemBase = itemBase;
+		this.slot = slot;
+		this.parentID = parentID;
+		this.enchanted = false;
+		this.prefix = null;
+		this.suffix = null;
+		this.pValue = 0;
+		this.sValue = 0;
+		setMagicValue();
+	}
+
+	public MobEquipment(ItemBase itemBase, int slot, int parentID, String pIDString, String sIDString, int pValue, int sValue) {
+		super(MobEquipment.getNewID());
+		this.itemBase = itemBase;
+		this.slot = slot;
+		this.parentID = parentID;
+
+		//add effects
+		this.prefix = PowersManager.getPowerActionByIDString(pIDString);
+		this.suffix = PowersManager.getPowerActionByIDString(sIDString);
+
+		this.pValue = pValue;
+		this.sValue = sValue;
+		this.enchanted = this.prefix == null || this.suffix == null;
+		setMagicValue();
+	}
+
+	/**
+	 * ResultSet Constructor
+	 */
+	public MobEquipment(ResultSet rs) throws SQLException {
+		super(MobEquipment.getNewID());
+		int itemBaseID = rs.getInt("ItemID");
+		this.itemBase = ItemBase.getItemBase(itemBaseID);
+		this.slot = rs.getInt("slot");
+		this.parentID = rs.getInt("mobID");
+		setMagicValue();
+	}
+
+
+	public MobEquipment(int itemBaseID,float dropChance)  {
+		super(MobEquipment.getNewID());
+		this.itemBase = ItemBase.getItemBase(itemBaseID);
+
+		if (this.itemBase != null)
+			this.slot = this.itemBase.getValidSlot();
+		else{
+			Logger.error("Failed to find Itembase for ID : "  + itemBaseID);
+			this.slot = 0;
+		}
+
+		this.dropChance = dropChance;
+
+		this.parentID = 0;
+		setMagicValue();
+	}
+
+	public ItemBase getItemBase() {
+		return itemBase;
+	}
+
+	public int getSlot() {
+		return this.slot;
+	}
+
+	public void setSlot(int value) {
+		this.slot = value;
+	}
+
+	public static int getNewID() {
+		return MobEquipment.equipCounter.incrementAndGet();
+	}
+
+	public static void serializeForVendor(MobEquipment mobEquipment,ByteBufferWriter writer, float percent) throws SerializationException {
+		_serializeForClientMsg(mobEquipment,writer, false);
+		int baseValue = mobEquipment.itemBase.getBaseValue() + mobEquipment.itemBase.getMagicValue();
+		writer.putInt(mobEquipment.magicValue);
+		writer.putInt(mobEquipment.magicValue);
+	}
+
+	
+	public static void serializeForClientMsg(MobEquipment mobEquipment,ByteBufferWriter writer) throws SerializationException {
+		_serializeForClientMsg(mobEquipment,writer, true);
+	}
+
+	public static void _serializeForClientMsg(MobEquipment mobEquipment,ByteBufferWriter writer, boolean useSlot) throws SerializationException {
+
+		if (useSlot)
+			writer.putInt(mobEquipment.slot);
+		writer.putInt(0); // Pad
+		writer.putInt(mobEquipment.itemBase.getUUID());
+		writer.putInt(mobEquipment.getObjectType().ordinal());
+		writer.putInt(mobEquipment.getObjectUUID());
+
+		// Unknown statics
+		for (int i = 0; i < 3; i++) {
+			writer.putInt(0); // Pad
+		}
+		for (int i = 0; i < 4; i++) {
+			writer.putInt(0x3F800000); // Static
+		}
+		for (int i = 0; i < 5; i++) {
+			writer.putInt(0); // Pad
+		}
+		for (int i = 0; i < 2; i++) {
+			writer.putInt(0xFFFFFFFF); // Static
+		}
+
+		writer.putInt(0);
+
+		writer.put((byte) 1); // End Datablock byte
+		writer.putInt(0); // Unknown. pad?
+		writer.put((byte) 1); // End Datablock byte
+
+		writer.putFloat(mobEquipment.itemBase.getDurability());
+		writer.putFloat(mobEquipment.itemBase.getDurability());
+
+		writer.put((byte) 1); // End Datablock byte
+
+		writer.putInt(0); // Pad
+		writer.putInt(0); // Pad
+
+		writer.putInt(mobEquipment.itemBase.getBaseValue());
+		writer.putInt(mobEquipment.magicValue);
+
+		serializeEffects(mobEquipment,writer);
+
+		writer.putInt(0x00000000);
+
+		//name color, think mobEquipment is where mobEquipment goes
+		if (mobEquipment.enchanted)
+			if (mobEquipment.isID)
+				writer.putInt(36);
+			else
+				writer.putInt(40);
+		else
+			writer.putInt(4);
+
+		writer.putInt(0);
+		writer.putInt(0); // Pad
+		writer.putInt(1);
+		writer.putShort((short) 0);
+		writer.put((byte) 0);
+	}
+
+	public final void setMagicValue() {
+		float value = 1;
+		if (itemBase != null)
+			value = itemBase.getBaseValue();
+		if (this.prefix != null) {
+			if (this.prefix.getEffectsBase() != null)
+				value += this.prefix.getEffectsBase().getValue();
+			if (this.prefix.getEffectsBase2() != null)
+				value += this.prefix.getEffectsBase2().getValue();
+		}
+		if (this.suffix != null) {
+			if (this.suffix.getEffectsBase() != null)
+				value += this.suffix.getEffectsBase().getValue();
+			if (this.suffix.getEffectsBase2() != null)
+				value += this.suffix.getEffectsBase2().getValue();
+		}
+
+		if (itemBase != null)
+
+			for (Integer token : itemBase.getBakedInStats().keySet()) {
+
+				EffectsBase effect = PowersManager.getEffectByToken(token);
+
+				AbstractPowerAction apa = PowersManager.getPowerActionByIDString(effect.getIDString());
+				if (apa.getEffectsBase() != null)
+					if (apa.getEffectsBase().getValue() > 0){
+						//System.out.println(apa.getEffectsBase().getValue());
+						value += apa.getEffectsBase().getValue();
+					}
+
+
+				if (apa.getEffectsBase2() != null)
+					value += apa.getEffectsBase2().getValue();
+			}
+
+		this.magicValue = (int) value;
+	}
+
+	public int getMagicValue() {
+		
+		if (!this.isID) {
+            return itemBase.getBaseValue();
+        }
+		return this.magicValue;
+	}
+
+
+	public static void serializeEffects(MobEquipment mobEquipment,ByteBufferWriter writer) {
+
+		//skip sending effects if not IDed
+		if (!mobEquipment.isID) {
+			writer.putInt(0);
+			return;
+		}
+
+		//handle effect count
+		int cnt = 0;
+		EffectsBase pre = null;
+		EffectsBase suf = null;
+		if (mobEquipment.prefix != null) {
+			pre = PowersManager.getEffectByIDString(mobEquipment.prefix.getIDString());
+			if (pre != null)
+				cnt++;
+		}
+		if (mobEquipment.suffix != null) {
+			suf = PowersManager.getEffectByIDString(mobEquipment.suffix.getIDString());
+			if (suf != null)
+				cnt++;
+		}
+
+		writer.putInt(cnt);
+
+		//serialize prefix
+		if (pre != null)
+			serializeEffect(mobEquipment,writer, pre, mobEquipment.pValue);
+
+		//serialize suffix
+		if (suf != null)
+			serializeEffect(mobEquipment,writer, suf, mobEquipment.sValue);
+	}
+
+	public static void serializeEffect(MobEquipment mobEquipment,ByteBufferWriter writer, EffectsBase eb, int rank) {
+		String name;
+		if (eb.isPrefix()) {
+			if (mobEquipment.itemBase == null)
+				name = eb.getName();
+			else
+				name = eb.getName() + ' ' + mobEquipment.itemBase.getName();
+		}
+		else if (eb.isSuffix()) {
+			if (mobEquipment.itemBase == null)
+				name = eb.getName();
+			else
+				name = mobEquipment.itemBase.getName() + ' ' + eb.getName();
+		}
+		else {
+			if (mobEquipment.itemBase == null)
+				name = "";
+			else
+				name = mobEquipment.itemBase.getName();
+		}
+
+		writer.putInt(eb.getToken());
+		writer.putInt(rank);
+		writer.putInt(1);
+		writer.put((byte) 1);
+		writer.putInt(mobEquipment.getObjectType().ordinal());
+		writer.putInt(mobEquipment.getObjectUUID());
+		writer.putString(name);
+		writer.putFloat(-1000f);
+	}
+
+	public void setPrefix(String pIDString, int pValue) {
+		AbstractPowerAction apa = PowersManager.getPowerActionByIDString(pIDString);
+		if (apa != null) {
+			this.prefix = apa;
+			this.pValue = pValue;
+		} else
+			this.prefix = null;
+
+		this.enchanted = this.prefix != null || this.suffix != null;
+
+		setMagicValue();
+	}
+
+	public void setSuffix(String sIDString, int sValue) {
+		AbstractPowerAction apa = PowersManager.getPowerActionByIDString(sIDString);
+		if (apa != null) {
+			this.suffix = apa;
+			this.sValue = sValue;
+		} else
+			this.suffix = null;
+
+		this.enchanted = this.prefix != null || this.suffix != null;
+
+		setMagicValue();
+	}
+
+	public void setIsID(boolean value) {
+		this.isID = value;
+	}
+
+	public boolean isID() {
+		return this.isID;
+	}
+
+	public void transferEnchants(Item item) {
+		if (this.prefix != null) {
+			String IDString = this.prefix.getIDString();
+			item.addPermanentEnchantment(IDString, this.pValue);
+		}
+		if (this.suffix != null) {
+			String IDString = this.suffix.getIDString();
+			item.addPermanentEnchantment(IDString, this.sValue);
+		}
+		if (this.isID)
+			item.setIsID(true);
+	}
+
+
+
+	/*
+	 * Database
+	 */
+	@Override
+	public void updateDatabase() {
+	}
+
+
+	public void persistObject() {
+		PreparedStatementShared ps = null;
+		try {
+			ps = prepareStatement("INSERT INTO static_npc_mobequipment (`mobID`, `slot`, `itemID`) VALUES (?, ?, ?)");
+			ps.setInt(1, this.parentID, true);
+			ps.setInt(2, this.slot);
+			ps.setInt(3, this.itemBase.getUUID(), true);
+			ps.executeUpdate();
+		} catch (SQLException e) {
+			Logger.error( e.toString());
+		} finally {
+			ps.release();
+		}
+	}
+
+	public static void removeObject(int UUID, int slot) {
+		PreparedStatementShared ps = null;
+		try {
+			ps = prepareStatement("DELETE FROM `static_npc_mobequipment` WHERE `mobID`=? AND slot=?");
+			ps.setInt(1, UUID);
+			ps.setInt(2, slot);
+			ps.executeUpdate();
+		} catch (SQLException e) {
+			Logger.error( e.toString());
+		} finally {
+			ps.release();
+		}
+	}
+
+	public float getDropChance() {
+		return dropChance;
+	}
+
+	public void setDropChance(float dropChance) {
+		this.dropChance = dropChance;
+	}
+}
diff --git a/src/engine/objects/MobLoot.java b/src/engine/objects/MobLoot.java
new file mode 100644
index 00000000..2a9c18ba
--- /dev/null
+++ b/src/engine/objects/MobLoot.java
@@ -0,0 +1,408 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.objects;
+
+import engine.Enum;
+import engine.Enum.ItemType;
+import engine.Enum.OwnerType;
+import engine.gameManager.DbManager;
+import engine.gameManager.PowersManager;
+import engine.powers.poweractions.AbstractPowerAction;
+import org.pmw.tinylog.Logger;
+
+import java.util.ArrayList;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * An immutable, non-persistant implementation of Item
+ *
+ * @author Burfo
+ */
+public final class MobLoot extends Item {
+
+	private static final AtomicInteger LastUsedId = new AtomicInteger(0);
+
+	private boolean isDeleted = false;
+	private boolean noSteal;
+	private String prefix = "";
+	private String suffix = "";
+
+	private int fidelityEquipID = 0;
+
+
+	/**
+	 * Create a new MobLoot.
+	 * Do not use this to create Gold.
+	 *
+	 * @param mob Mob that owns this item
+	 * @param ib ItemBase
+	 */
+	public MobLoot(AbstractCharacter mob, ItemBase ib, boolean noSteal) {
+		this(mob, ib, 0, noSteal);
+	}
+
+	/**
+	 * Create a new MobLoot item to hold Gold for the Mob.
+	 *
+	 * @param mob Mob that owns this item
+	 * @param qtyOfGold Quantity of gold
+	 */
+	public MobLoot(AbstractCharacter mob, int qtyOfGold) {
+		this(mob, ItemBase.getGoldItemBase(), qtyOfGold, false);
+	}
+
+	/**
+	 * Create a new MobLoot.
+	 * Primarily used for stackable items that have a quantity.
+	 *
+	 * @param mob Mob that owns this item
+	 * @param ib ItemBase
+	 * @param quantity Quantity of the item
+	 */
+	public MobLoot(AbstractCharacter mob, ItemBase ib, int quantity, boolean noSteal) {
+		super( ib, mob.getObjectUUID(),
+				OwnerType.Mob, (byte) 0, (byte) 0, (short) 0,
+				(short) 0, true, false, false, false, true,
+				false, (byte) 0, new ArrayList<>(), generateId());
+
+		if (quantity == 0 && ib.getType() == ItemType.RESOURCE)
+			quantity = 1;
+
+
+		if (quantity > 0)
+			this.setNumOfItems(quantity);
+
+		this.noSteal = noSteal;
+		this.setIsID(this.getItemBase().isAutoID());
+
+		// Class is 'final'; passing 'this' should be okay at the end of the constructor
+
+		DbManager.addToCache(this);
+	}
+
+	/**
+	 * Converts this MotLoot to a persistable Item. Used when a MotLoot is
+	 * looted
+	 * from a Mob to a Player. Do not call for a Gold item.
+	 *
+	 * @return An orphaned Item, ready to be moved to the Player's inventory.
+	 */
+	public synchronized Item promoteToItem(PlayerCharacter looter) {
+
+		if (looter == null)
+			return null;
+
+		if (isDeleted)
+			return null;
+
+		if (this.getItemBase().getType().equals(ItemType.GOLD))
+			return null;
+
+		
+		Item item = this;
+		
+		item.setOwner(looter);
+		//item.setIsID(false);
+
+		item.containerType = Enum.ItemContainerType.INVENTORY;
+		item.setValue(0);
+		item.setName(this.getCustomName());
+		item.setIsID(this.isID());
+
+		if (this.getNumOfItems() > 1)
+			item.setNumOfItems(this.getNumOfItems());
+
+		try {
+			item = DbManager.ItemQueries.ADD_ITEM(item);
+		} catch (Exception e) {
+			Logger.error("e");
+			return null;
+		}
+
+		//		for (String effectName : this.effectNames)
+		//			item.addPermanentEnchantment(effectName, 0);
+		//transfer enchantments to item
+		if (this.prefix.length() != 0)
+			item.addPermanentEnchantment(this.prefix, 0);
+		if (this.suffix.length() != 0)
+			item.addPermanentEnchantment(this.suffix, 0);
+
+		this.junk();
+		return item;
+	}
+
+	public synchronized Item promoteToItemForNPC(NPC looter) {
+
+		if (looter == null)
+			return null;
+
+		if (isDeleted)
+			return null;
+
+		if (this.getItemBase().getType().equals(ItemType.GOLD))
+			return null;
+
+		Item item = this;
+		item.setOwner(looter);
+		item.containerType = Enum.ItemContainerType.INVENTORY;
+		item.setIsID(true);
+
+		if (this.getNumOfItems() > 1)
+			item.setNumOfItems(this.getNumOfItems());
+
+		try {
+			item = DbManager.ItemQueries.ADD_ITEM(item);
+		} catch (Exception e) {
+			Logger.error(e);
+			return null;
+		}
+		item.containerType = Enum.ItemContainerType.INVENTORY;
+
+		//		for (String effectName : this.effectNames)
+		//			item.addPermanentEnchantment(effectName, 0);
+		//transfer enchantments to item
+		try{
+			for (String enchant:this.getEffectNames()){
+				item.addPermanentEnchantment(enchant, 0);
+			}
+		}catch(Exception e){
+			Logger.error(e.getMessage());
+		}
+
+		DbManager.NPCQueries.REMOVE_FROM_PRODUCTION_LIST(this.getObjectUUID(),looter.getObjectUUID());
+		looter.removeItemFromForge(this);
+		this.junk();
+		return item;
+	}
+
+	public synchronized void recycle(NPC vendor){
+
+		//remove from production list for npc in db
+
+		DbManager.NPCQueries.REMOVE_FROM_PRODUCTION_LIST(this.getObjectUUID(),vendor.getObjectUUID());
+		this.removeFromCache();
+		isDeleted = true;
+	}
+
+	/**
+	 * Junks the item and marks it as deleted
+	 */
+	@Override
+	protected synchronized void junk() {
+		this.removeFromCache();
+		isDeleted = true;
+	}
+
+	/**
+	 * Get the MobLoot object from its Id number
+	 *
+	 * @param id Id Number
+	 * @return MobLoot object
+	 */
+	public static MobLoot getFromCache(int id) {
+		return (MobLoot) DbManager.getFromCache(Enum.GameObjectType.MobLoot, id);
+	}
+
+	/**
+	 * Determines if this object has been marked as deleted.
+	 *
+	 * @return True if deleted.
+	 */
+	public boolean isDeleted() {
+		return this.isDeleted;
+	}
+
+	public boolean noSteal() {
+		return this.noSteal;
+	}
+
+	public void addPermanentEnchantment(String enchantID, int rank, int value, boolean prefix) {
+		AbstractPowerAction apa = PowersManager.getPowerActionByIDString(enchantID);
+		if (apa == null)
+			return;
+		apa.applyEffectForItem(this, rank);
+
+		//limit to 2 effects
+		//		if (this.effectNames.size() < 2)
+		//			this.effectNames.add(enchantID);
+		if (prefix)
+			this.prefix = enchantID;
+		else
+			this.suffix = enchantID;
+
+		this.getEffectNames().add(enchantID);
+	}
+
+	/**
+	 * Get the next available Id number.
+	 *
+	 * @return Id number
+	 */
+	private static int generateId() {
+		int id = LastUsedId.decrementAndGet();
+
+		//TODO Add a way to reclaim disposed IDs if this becomes a problem
+		if (id == (-10000))
+			Logger.warn("Only 10,000 Id numbers remain useable. Server restart suggested.");
+		else if (id < Integer.MIN_VALUE + 1000)
+			Logger.warn("Only " + (Integer.MIN_VALUE + id)
+					+ " Id numbers remain useable! Server restart suggested.");
+		else if (id == Integer.MIN_VALUE)
+			throw new UnsupportedOperationException("MobLoot has no remaining Id numbers! Restart server immediately!");
+		else if ((id % 10000) == 0)
+			Logger.info( id + " of " + Integer.MIN_VALUE + " Id numbers consumed.");
+
+		return id;
+	}
+
+	/* *****
+	 * All of the following methods are overridden from
+	 * the superclass and intentionally not implemented.
+	 * *****
+	 */
+	/**
+	 * Not implemented
+	 */
+	@Override
+	@Deprecated
+	public void setOwnerID(int id) {
+	}
+
+	/**
+	 * Not implemented
+	 */
+	@Override
+	@Deprecated
+	public synchronized void decrementChargesRemaining() {
+	}
+
+	/**
+	 * Not implemented
+	 */
+	@Override
+	@Deprecated
+	protected boolean equipItem(NPC npc, byte slot) {
+		return false;
+	}
+
+	/**
+	 * Not implemented
+	 */
+	@Override
+	@Deprecated
+	protected boolean equipItem(PlayerCharacter pc, byte slot) {
+		return false;
+	}
+
+	/**
+	 * Not implemented
+	 */
+	@Override
+	@Deprecated
+	protected boolean moveItemToBank(NPC npc) {
+		return false;
+	}
+
+	/**
+	 * Not implemented
+	 */
+	@Override
+	@Deprecated
+	protected boolean moveItemToBank(PlayerCharacter pc) {
+		return false;
+	}
+
+	/**
+	 * Not implemented
+	 */
+	@Override
+	@Deprecated
+	protected boolean moveItemToInventory(Corpse corpse) {
+		return false;
+	}
+
+	/**
+	 * Not implemented
+	 */
+	@Override
+	@Deprecated
+	protected boolean moveItemToInventory(NPC npc) {
+		return false;
+	}
+
+	/**
+	 * Not implemented
+	 */
+	@Override
+	@Deprecated
+	protected boolean moveItemToInventory(PlayerCharacter pc) {
+		return false;
+	}
+
+	/**
+	 * Not implemented
+	 */
+	@Override
+	@Deprecated
+	protected boolean moveItemToVault(Account a) {
+		return false;
+	}
+
+	/**
+	 * Not implemented
+	 */
+	@Override
+	@Deprecated
+	public void setLastOwner(AbstractWorldObject value) {
+	}
+
+	/**
+	 * Not implemented
+	 */
+	@Override
+	@Deprecated
+	public void updateDatabase() {
+	}
+
+	/**
+	 * Not implemented
+	 */
+	@Override
+	@Deprecated
+	protected void validateItemContainer() {
+	}
+
+	public String getPrefix() {
+		return prefix;
+	}
+
+	public void setPrefix(String prefix) {
+		this.prefix = prefix;
+	}
+
+	public String getSuffix() {
+		return suffix;
+	}
+
+	public void setSuffix(String suffix) {
+		this.suffix = suffix;
+	}
+
+	public int getFidelityEquipID() {
+		return fidelityEquipID;
+	}
+
+	public void setFidelityEquipID(int fidelityEquipID) {
+		this.fidelityEquipID = fidelityEquipID;
+	}
+
+
+
+}
diff --git a/src/engine/objects/MobLootBase.java b/src/engine/objects/MobLootBase.java
new file mode 100644
index 00000000..f3ce7925
--- /dev/null
+++ b/src/engine/objects/MobLootBase.java
@@ -0,0 +1,59 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.objects;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.HashMap;
+
+public class MobLootBase  {
+
+	private int mobBaseID;
+	private int lootTableID;
+	private float chance;
+
+	public static HashMap<Integer, ArrayList<MobLootBase>> MobLootSet = new HashMap<>();
+
+
+	/**
+	 * ResultSet Constructor
+	 */
+
+	public MobLootBase(ResultSet rs) throws SQLException {
+		this.mobBaseID = rs.getInt("mobBaseID");
+        this.lootTableID = rs.getInt("lootTable");
+        this.chance = rs.getFloat("chance");
+	}
+
+	public MobLootBase(int mobBaseID, int lootTableID, int chance) {
+		super();
+		this.mobBaseID = mobBaseID;
+        this.lootTableID = lootTableID;
+        this.chance = chance;
+
+	}
+
+	public int getMobBaseID() {
+		return mobBaseID;
+	}
+	public float getChance() {
+		return chance;
+	}
+
+	public int getLootTableID() {
+		return lootTableID;
+	}
+
+	public void setLootTableID(int lootTableID) {
+		this.lootTableID = lootTableID;
+	}
+
+}
diff --git a/src/engine/objects/MobbaseGoldEntry.java b/src/engine/objects/MobbaseGoldEntry.java
new file mode 100644
index 00000000..5a6a1dbd
--- /dev/null
+++ b/src/engine/objects/MobbaseGoldEntry.java
@@ -0,0 +1,57 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.objects;
+
+import engine.gameManager.DbManager;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.HashMap;
+
+public class MobbaseGoldEntry {
+
+	private float chance;
+	private int min;
+	private int max;
+
+	public static HashMap<Integer, MobbaseGoldEntry> MobbaseGoldMap = new HashMap<>();
+
+	/**
+	 * ResultSet Constructor
+	 */
+
+	public MobbaseGoldEntry(ResultSet rs) throws SQLException {
+
+		this.chance = rs.getFloat("chance");
+		this.min = rs.getInt("min");
+		this.max = (rs.getInt("max"));
+	}
+
+	public static void LoadMobbaseGold() {
+		MobbaseGoldMap = DbManager.MobBaseQueries.LOAD_GOLD_FOR_MOBBASE();
+	}
+
+	public float getChance() {
+		return chance;
+	}
+
+	public int getMin() {
+		return min;
+	}
+
+	public int getMax() {
+		return max;
+	}
+
+
+
+
+
+}
diff --git a/src/engine/objects/NPC.java b/src/engine/objects/NPC.java
new file mode 100644
index 00000000..e6609400
--- /dev/null
+++ b/src/engine/objects/NPC.java
@@ -0,0 +1,1866 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.objects;
+
+import engine.Enum;
+import engine.Enum.*;
+import engine.InterestManagement.WorldGrid;
+import engine.ai.MobileFSM.STATE;
+import engine.exception.SerializationException;
+import engine.gameManager.*;
+import engine.job.JobContainer;
+import engine.job.JobScheduler;
+import engine.jobs.UpgradeNPCJob;
+import engine.math.Bounds;
+import engine.math.Vector3f;
+import engine.math.Vector3fImmutable;
+import engine.net.ByteBufferWriter;
+import engine.net.Dispatch;
+import engine.net.DispatchMessage;
+import engine.net.client.ClientConnection;
+import engine.net.client.msg.*;
+import engine.server.MBServerStatics;
+import org.joda.time.DateTime;
+import org.pmw.tinylog.Logger;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ThreadLocalRandom;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+import static engine.net.client.msg.ErrorPopupMsg.sendErrorPopup;
+import static engine.objects.MobBase.loadEquipmentSet;
+
+public class NPC extends AbstractCharacter {
+
+	//This is called every 10 minutes to remove items from static npc inventory to make room to buy more.
+	private static int NUM_ITEMS_TO_JUNK = 30;
+
+	// Used for thread safety
+	public final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
+
+	protected int loadID;
+	protected boolean isMob;
+	protected MobBase mobBase;
+	protected String name;
+	protected Building building;
+	protected Contract contract;
+	protected int dbID;
+	protected int currentID;
+	private DateTime upgradeDateTime = null;
+
+	//used by static npcs
+	protected Zone parentZone;
+	protected float statLat;
+	protected float statLon;
+	protected float statAlt;
+	protected float sellPercent; //also train percent
+	protected float buyPercent;
+	protected int vendorID;
+	protected ArrayList<Integer> modTypeTable;
+	protected ArrayList<Integer> modSuffixTable;
+	protected ArrayList<Byte> itemModTable;
+	protected int symbol;
+	public static int SVR_CLOSE_WINDOW = 4;
+
+	public static ArrayList<Integer>Oprhans = new ArrayList<>();
+
+	// Variables NOT to be stored in db
+	protected boolean isStatic = false;
+	private ArrayList<MobLoot> rolling = new ArrayList<>();
+	private ArrayList<Mob> siegeMinions = new ArrayList<>();
+	private ConcurrentHashMap<Mob, Integer> siegeMinionMap = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW);
+	private HashSet<Integer> canRoll = null;
+	public static HashMap<Integer, ArrayList<String>> _pirateNames = new HashMap<>();
+
+	public ReentrantReadWriteLock minionLock = new ReentrantReadWriteLock();
+
+	private int parentZoneID;
+
+	public ArrayList<ProducedItem> forgedItems = new ArrayList<>();
+	private int fidalityID;
+	private int buildingLevel;
+	private int buildingFloor;
+	public HashMap<Integer, MobEquipment> equip = null;
+	private String nameOverride = "";
+	private int equipmentSetID = 0;
+	private int slot;
+	private Regions region = null;
+
+	public Vector3fImmutable inBuildingLoc = Vector3fImmutable.ZERO;
+	private int repairCost = 5;
+
+	public int classID = 0;
+	public int professionID = 0;
+	public int extraRune = 0;
+	public int extraRune2 = 0;
+
+
+	/**
+	 * No Id Constructor
+	 */
+	public NPC( String name, short statStrCurrent, short statDexCurrent, short statConCurrent,
+			short statIntCurrent, short statSpiCurrent, short level, int exp, boolean sit, boolean walk, boolean combat, Vector3fImmutable bindLoc,
+			Vector3fImmutable currentLoc, Vector3fImmutable faceDir, short healthCurrent, short manaCurrent, short stamCurrent, Guild guild,
+			byte runningTrains, int npcType, boolean isMob, Building building, int contractID, Zone parent) {
+		super(name, "", statStrCurrent, statDexCurrent, statConCurrent, statIntCurrent, statSpiCurrent, level, exp,
+				bindLoc, currentLoc, faceDir, guild, runningTrains);
+		this.loadID = npcType;
+		this.isMob = isMob;
+		this.contract = DbManager.ContractQueries.GET_CONTRACT(contractID);
+
+		if (this.contract != null)
+			this.mobBase = MobBase.getMobBase(this.contract.getMobbaseID());
+		else
+			this.mobBase = MobBase.getMobBase(loadID);
+
+		this.name = name;
+		this.buyPercent = 0.33f;
+		this.sellPercent = 1f;
+		this.building = building;
+
+		this.parentZone = parent;
+
+		initializeMob();
+		clearStatic();
+
+		this.dbID = MBServerStatics.NO_DB_ROW_ASSIGNED_YET;
+		this.currentID = MBServerStatics.NO_DB_ROW_ASSIGNED_YET;
+	}
+
+	/**
+	 * Normal Constructor
+	 */
+	public NPC( String name, short statStrCurrent, short statDexCurrent, short statConCurrent,
+			short statIntCurrent, short statSpiCurrent, short level, int exp, boolean sit, boolean walk, boolean combat, Vector3fImmutable bindLoc,
+			Vector3fImmutable currentLoc, Vector3fImmutable faceDir, short healthCurrent, short manaCurrent, short stamCurrent, Guild guild,
+			byte runningTrains, int npcType, boolean isMob, Building building, int contractID, Zone parent, int newUUID) {
+		super( name, "", statStrCurrent, statDexCurrent, statConCurrent, statIntCurrent, statSpiCurrent, level, exp,
+				bindLoc, currentLoc, faceDir, guild, runningTrains, newUUID);
+		this.loadID = npcType;
+		this.isMob = isMob;
+
+		if (this.contract != null)
+			this.mobBase = MobBase.getMobBase(this.contract.getMobbaseID());
+		else
+			this.mobBase = MobBase.getMobBase(loadID);
+
+		this.building = building;
+		this.name = name;
+		this.buyPercent = 0.33f;
+		this.sellPercent = 1f;
+
+		this.parentZone = parent;
+		this.dbID = newUUID;
+		this.currentID = newUUID;
+
+		initializeMob();
+		clearStatic();
+	}
+
+	/**
+	 * ResultSet Constructor
+	 */
+	public NPC(ResultSet rs) throws SQLException {
+
+		super(rs);
+
+		java.util.Date sqlDateTime;
+
+		try{
+			this.dbID = rs.getInt(1);
+			this.currentID = this.dbID;
+			this.setObjectTypeMask(MBServerStatics.MASK_NPC);
+			int contractID = rs.getInt("npc_contractID");
+			this.parentZoneID = rs.getInt("parent");
+
+			this.gridObjectType = GridObjectType.STATIC;
+			this.contract = DbManager.ContractQueries.GET_CONTRACT(contractID);
+			this.fidalityID = (rs.getInt("fidalityID"));
+			this.equipmentSetID = rs.getInt("equipmentSet");
+
+			if (this.equipmentSetID == 0 && this.contract != null)
+				this.equipmentSetID = this.contract.equipmentSet;
+
+			if (this.contract != null)
+				this.loadID = this.contract.getMobbaseID();
+			else
+				this.loadID = 2011; //default to human
+
+			int mobBaseOverride = rs.getInt("npc_raceID");
+
+			if ((this.fidalityID != 0) || (mobBaseOverride != 0))
+				this.loadID = mobBaseOverride;
+
+			this.mobBase = MobBase.getMobBase(this.loadID);
+			this.level = rs.getByte("npc_level");
+			this.isMob = false;
+			int buildingID = rs.getInt("npc_buildingID");
+
+			try{
+				this.building = BuildingManager.getBuilding(buildingID);
+
+				if (this.building != null)
+					this.building.fidelityNpcs.put(currentID, this.building.fidelityNpcs.size() + 1);
+
+			}catch(Exception e){
+				this.building = null;
+				Logger.error( e.getMessage());
+			}
+
+			this.name = rs.getString("npc_name");
+			this.buyPercent = rs.getFloat("npc_buyPercent");
+
+			if (this.buyPercent == 1)
+				this.buyPercent = .33f;
+
+			this.buyPercent = .33f;
+			this.sellPercent = 1;
+
+			this.setRot(new Vector3f(0, rs.getFloat("npc_rotation"), 0));
+
+			this.statLat = rs.getFloat("npc_spawnX");
+			this.statAlt = rs.getFloat("npc_spawnY");
+			this.statLon = rs.getFloat("npc_spawnZ");
+
+			this.slot = rs.getInt("npc_slot");
+
+			if (this.contract != null) {
+				this.symbol = this.contract.getIconID();
+				this.modTypeTable = this.contract.getNPCModTypeTable();
+				this.modSuffixTable = this.contract.getNpcModSuffixTable();
+				this.itemModTable = this.contract.getItemModTable();
+				int VID = this.contract.getVendorID();
+
+				if (VID != 0)
+					this.vendorID = VID;
+				else
+					this.vendorID = 1; //no vendor items
+			}
+
+			int guildID = rs.getInt("npc_guildID");
+
+			if (this.fidalityID != 0){
+				if (this.building != null)
+					this.guild = this.building.getGuild();
+				else
+					this.guild = Guild.getGuild(guildID);
+			}else
+				if (this.building != null)
+					this.guild = this.building.getGuild();
+				else
+					this.guild = Guild.getGuild(guildID);
+
+			if (guildID != 0 && (this.guild == null || this.guild.isErrant()))
+				NPC.Oprhans.add(currentID);
+			else if(this.building == null && buildingID > 0) {
+				NPC.Oprhans.add(currentID);
+			}
+
+			if (this.guild == null)
+				this.guild = Guild.getErrantGuild();
+
+			// Set upgrade date JodaTime DateTime object
+			// if one exists in the database.
+
+			sqlDateTime = rs.getTimestamp("upgradeDate");
+
+			if (sqlDateTime != null)
+				upgradeDateTime = new DateTime(sqlDateTime);
+			else
+				upgradeDateTime = null;
+
+			// Submit upgrade job if NPC is currently set to rank.
+
+			if (this.upgradeDateTime != null)
+				submitUpgradeJob();
+			this.buildingFloor = (rs.getInt("npc_buildingFloor"));
+			this.buildingLevel = (rs.getInt("npc_buildingLevel"));
+			this.setParentZone(ZoneManager.getZoneByUUID(this.parentZoneID));
+
+
+			if (this.fidalityID != 0)
+				this.nameOverride = rs.getString("npc_name");
+
+		}catch(Exception e){
+			Logger.error(e);
+			e.printStackTrace();
+		}
+
+		try{
+			initializeMob();
+		}catch(Exception e){
+			Logger.error( e.toString());
+		}
+
+	}
+
+	//This method restarts an upgrade timer when a building is loaded from the database.
+	// Submit upgrade job for this building based upon it's current upgradeDateTime
+
+	public final void submitUpgradeJob() {
+
+		JobContainer jc;
+
+		if (this.getUpgradeDateTime() == null) {
+			Logger.error("Attempt to submit upgrade job for non-ranking NPC");
+			return;
+		}
+
+		// Submit upgrade job for future date or current instant
+
+		if (this.getUpgradeDateTime().isAfter(DateTime.now()))
+			jc = JobScheduler.getInstance().scheduleJob(new UpgradeNPCJob(this),
+					this.getUpgradeDateTime().getMillis());
+		else
+			JobScheduler.getInstance().scheduleJob(new UpgradeNPCJob(this), 0);
+
+	}
+
+	public void setRank(int newRank) {
+
+		DbManager.NPCQueries.SET_PROPERTY(this, "npc_level", newRank);
+		this.level = (short) newRank;
+	}
+
+	public int getDBID() {
+		return this.dbID;
+	}
+
+	@Override
+	public int getObjectUUID() {
+		return currentID;
+	}
+
+	private void clearStatic() {
+		this.parentZone = null;
+		this.statLat = 0f;
+		this.statLon = 0f;
+		this.statAlt = 0f;
+	}
+
+	private void initializeMob() {
+
+		if (this.mobBase != null) {
+			this.healthMax = this.mobBase.getHealthMax();
+			this.manaMax = 0;
+			this.staminaMax = 0;
+			this.setHealth(this.healthMax);
+			this.mana.set(this.manaMax);
+			this.stamina.set(this.staminaMax);
+			//this.firstName = this.minionMobBase.getFirstName();
+			//this.lastName = "";
+			//this.level = (short)(this.minionMobBase.getLevel());
+		}
+
+		//add this npc to building
+		if (this.building != null && this.loadID != 0 && this.fidalityID == 0) {
+
+			if (building.getBlueprint() != null){
+
+				int maxSlots;
+				maxSlots = building.getBlueprint().getSlotsForRank(this.building.getRank());
+
+				for (int slot = 1; slot < maxSlots + 1; slot++) {
+
+					if (!this.building.getHirelings().containsValue(slot)) {
+						this.building.getHirelings().put(this, slot);
+						break;
+					}
+				}
+				//npc created by an admin without a blueprint, just default max slot size to 10.
+			}else{
+
+				int maxSlots = 10;
+
+				for (int slot = 1; slot < maxSlots + 1; slot++) {
+					if (!this.building.getHirelings().containsValue(slot)) {
+						this.building.getHirelings().put(this, slot);
+						break;
+					}
+				}
+			}
+		}
+
+		//TODO set these correctly later
+		this.rangeHandOne = 8;
+		this.rangeHandTwo = -1;
+		this.minDamageHandOne = 1;
+		this.maxDamageHandOne = 4;
+		this.minDamageHandTwo = 1;
+		this.maxDamageHandTwo = 4;
+		this.atrHandOne = 300;
+		this.atrHandOne = 300;
+		this.defenseRating = 200;
+		this.isActive = true;
+
+		this.charItemManager.load();
+	}
+
+	public static NPC getFromCache(int id) {
+		return (NPC) DbManager.getFromCache(GameObjectType.NPC, id);
+	}
+
+	/*
+	 * Getters
+	 */
+	public int getLoadID() {
+		return loadID;
+	}
+
+	public boolean isMob() {
+		return this.isMob;
+	}
+
+	public MobBase getMobBase() {
+		return this.mobBase;
+	}
+
+	public Contract getContract() {
+		return this.contract;
+	}
+
+	public int getContractID() {
+
+		if (this.contract != null)
+			return this.contract.getObjectUUID();
+
+		return 0;
+	}
+
+	public boolean isStatic() {
+		return this.isStatic;
+	}
+
+	public Building getBuilding() {
+		return this.building;
+	}
+
+	public void setName(String value) {
+		this.name = value;
+	}
+
+	public static boolean UpdateName(NPC npc, String value) {
+
+		if (!DbManager.NPCQueries.UPDATE_NAME(npc, value))
+			return false;
+
+		npc.name = value;
+		return true;
+
+	}
+
+	public void setBuilding(Building value) {
+		this.building = value;
+	}
+
+	@Override
+	public String getFirstName() {
+		return this.name;
+	}
+
+	@Override
+	public String getName() {
+		return this.name;
+	}
+
+	@Override
+	public String getLastName() {
+		return "";
+	}
+
+	@Override
+	public Vector3fImmutable getBindLoc() {
+		return this.bindLoc;
+	}
+
+	@Override
+	public int getGuildUUID() {
+
+		if (this.guild == null)
+			return 0;
+
+		return this.guild.getObjectUUID();
+	}
+
+	/*
+	 * Serialization
+	 */
+
+	public static void __serializeForClientMsg(NPC npc,ByteBufferWriter writer) throws SerializationException {
+	}
+
+
+	public static void serializeNpcForClientMsgOtherPlayer(NPC npc,ByteBufferWriter writer, boolean hideAsciiLastName)
+			throws SerializationException {
+		serializeForClientMsgOtherPlayer(npc,writer);
+	}
+
+
+	public static void serializeForClientMsgOtherPlayer(NPC npc,ByteBufferWriter writer)
+			throws SerializationException {
+
+		writer.putInt(0);
+		writer.putInt(0);
+
+		//num Runes
+		int cnt = 3;
+		boolean isVamp = false, isHealer = false, isArcher = false, isTrainer = false;
+		int contractID = 0, classID = 0;
+		int extraRune = 0;
+
+			if (npc.contract != null) {
+				contractID = npc.contract.getContractID();
+				classID = npc.contract.getClassID();
+				extraRune = npc.contract.getExtraRune();
+				
+				if (extraRune == contractID)
+					extraRune = 0;
+
+			}
+
+		if ((contractID > 252642 && contractID < 252647) || contractID == 252652) {
+			isVamp = true;
+			cnt++;
+		}
+
+		if (contractID == 252582 || contractID == 252579 || contractID == 252581
+				|| contractID == 252584 || contractID == 252597 || contractID == 252598
+				|| contractID == 252628 || extraRune == 252582 || extraRune == 252579
+				|| extraRune == 252581 || extraRune == 252584 || extraRune == 252597
+				|| extraRune == 252598 || extraRune == 252628) {
+			isHealer = true;
+			cnt++;
+		}
+
+		if (contractID == 252570) {
+			isArcher = true;
+			cnt++;
+		}
+
+		if (classID != 0)
+			cnt++;
+		
+				if (extraRune != 0 && extraRune != contractID) {
+					cnt++;
+				}
+
+
+		writer.putInt(cnt);
+
+		//Race
+		writer.putInt(1);
+		writer.putInt(0);
+
+		if (npc.mobBase != null)
+			writer.putInt(npc.mobBase.getLoadID());
+		else
+			writer.putInt(2011);
+
+		writer.putInt(GameObjectType.NPCRaceRune.ordinal());
+		writer.putInt(npc.currentID);
+
+		//Class/Trainer/Whatever
+		writer.putInt(5);
+		writer.putInt(0);
+
+		if (npc.contract != null) {
+			writer.putInt(contractID);
+		}else
+			writer.putInt(2500);
+
+		writer.putInt(GameObjectType.NPCClassRune.ordinal());
+		writer.putInt(npc.currentID);
+
+		//vampire trainer
+		cnt = 0;
+		
+		if (extraRune != 0)
+			cnt = serializeExtraRune(npc,extraRune, cnt, writer);
+		if (isVamp)
+			cnt = serializeExtraRune(npc,252647, cnt, writer);
+
+		//Healer trainer
+		if (isHealer) {
+			//			int healerRune = 2501;
+			//			if (npc.getLevel() >= 60)
+			//healerRune = 252592;
+			cnt = serializeExtraRune(npc,252592, cnt, writer);
+		}
+
+		if (classID != 0) {
+				writer.putInt(4);
+			writer.putInt(0);
+			writer.putInt(classID);
+			writer.putInt(GameObjectType.NPCExtraRune.ordinal());
+			writer.putInt(npc.currentID);
+		}
+
+		//Scout trainer
+		if (isArcher) {
+			cnt = serializeExtraRune(npc,252654, cnt, writer);
+		}
+
+		//		if (extraRune != 0 && extraRune != contractID) {
+		//			writer.putInt(3);
+		//			writer.putInt(0);
+		//			writer.putInt(extraRune);
+		//			writer.putInt(GameObjectType.NPCExtraRune.ordinal());
+		//			writer.putInt(npc.getObjectUUID());
+		//		}
+
+		//Shopkeeper
+		writer.putInt(5);
+		writer.putInt(0);
+		writer.putInt(0x3DACC);
+		writer.putInt(GameObjectType.NPCShopkeeperRune.ordinal());
+		writer.putInt(npc.currentID);
+
+		//Send Stats
+		writer.putInt(5);
+		writer.putInt(0x8AC3C0E6); //Str
+		writer.putInt(0);
+		writer.putInt(0xACB82E33); //Dex
+		writer.putInt(0);
+		writer.putInt(0xB15DC77E); //Con
+		writer.putInt(0);
+		writer.putInt(0xE07B3336); //Int
+		writer.putInt(0);
+		writer.putInt(0xFF665EC3); //Spi
+		writer.putInt(0);
+
+		if (!npc.nameOverride.isEmpty()){
+			writer.putString(npc.nameOverride);
+			writer.putInt(0);
+		}else
+			if (npc.contract != null) {
+
+				if (npc.contract.isTrainer()) {
+					writer.putString(npc.name + ", " + npc.contract.getName());
+					writer.putString("");
+				} else {
+					writer.putString(npc.name);
+					writer.putString(npc.contract.getName());
+				}
+			} else {
+				writer.putString(npc.name);
+				writer.putString("");
+			}
+
+		writer.putInt(0);
+		writer.putInt(0);
+		writer.putInt(0);
+		writer.putInt(0);
+		//writer.putInt(0); wHy do i see 4 in recording?
+
+		writer.put((byte) 0);
+		writer.putInt(npc.getObjectType().ordinal());
+		writer.putInt(npc.currentID);
+		//		writer.putLong(npc.getCompositeID());
+
+		writer.putFloat(1.0f);
+		writer.putFloat(1.0f);
+		writer.putFloat(1.0f);
+
+		if (npc.region != null){
+			writer.putVector3f(npc.inBuildingLoc);
+		}else{
+			writer.putFloat(npc.getLoc().getX());
+			writer.putFloat(npc.getLoc().getY());
+			writer.putFloat(npc.getLoc().getZ());
+		}
+
+		//Rotation
+		float radians = (float) Math.asin(npc.getRot().y) * 2;
+
+		if (npc.building != null)
+			if (npc.building.getBounds() != null && npc.building.getBounds().getQuaternion() != null)
+				radians += (npc.building.getBounds().getQuaternion()).angleY;
+
+		writer.putFloat(radians);
+
+		//Running Speed
+		writer.putInt(0);
+
+		// get a copy of the equipped items.
+
+		if (npc.equip != null){
+			writer.putInt(npc.equip.size());
+
+			for (MobEquipment me: npc.equip.values())
+				MobEquipment.serializeForClientMsg(me,writer);
+		}else
+			writer.putInt(0);
+
+		writer.putInt((npc.level / 10));
+		writer.putInt(npc.level);
+		writer.putInt(npc.getIsSittingAsInt()); //Standing
+		writer.putInt(npc.getIsWalkingAsInt()); //Walking
+		writer.putInt(npc.getIsCombatAsInt()); //Combat
+		writer.putInt(2); //Unknown
+		writer.putInt(1); //Unknown - Headlights?
+		writer.putInt(0);
+
+		if (npc.building != null && npc.region != null){
+			writer.putInt(npc.building.getObjectType().ordinal());
+			writer.putInt(npc.building.getObjectUUID());
+		}else{
+			writer.putInt(0); //<-Building Object Type
+			writer.putInt(0); //<-Building Object ID
+		}
+		writer.put((byte) 0);
+		writer.put((byte) 0);
+		writer.put((byte) 0);
+
+		//npc dialog menus from contracts
+
+		if (npc.contract != null) {
+			ArrayList<Integer> npcMenuOptions = npc.contract.getNPCMenuOptions();
+			writer.putInt(npcMenuOptions.size());
+			for (Integer val : npcMenuOptions) {
+				writer.putInt(val);
+			}
+
+		} else {
+			writer.putInt(0);
+		}
+
+		writer.put((byte) 1);
+
+		if (npc.building != null) {
+			writer.putInt(GameObjectType.StrongBox.ordinal());
+			writer.putInt(npc.currentID);
+			writer.putInt(GameObjectType.StrongBox.ordinal());
+			writer.putInt(npc.building.getObjectUUID());
+		} else {
+			writer.putLong(0);
+			writer.putLong(0);
+		}
+
+		if (npc.contract != null) {
+			writer.putInt(npc.contract.getIconID());
+		} else {
+			writer.putInt(0); //npc icon ID
+		}
+		//		for (int i=0;i<5;i++)
+		//			writer.putInt(0);
+		writer.putInt(0);
+		writer.putShort((short)0);
+
+		
+		if (npc.contract != null && npc.contract.isTrainer()) {
+			writer.putInt(classID);
+		} else {
+			writer.putInt(0);
+		}
+
+		if (npc.contract != null && npc.contract.isTrainer()) {
+			writer.putInt(classID);
+		} else {
+			writer.putInt(0);
+		}
+		writer.putInt(0);
+		writer.putInt(0);
+
+		writer.putFloat(4);
+		writer.putInt(0);
+		writer.putInt(0);
+		writer.putInt(0);
+		writer.put((byte) 0);
+
+		//Pull guild info from building if linked to one
+
+
+
+		Guild.serializeForClientMsg(npc.guild,writer, null, true);
+
+
+		writer.putInt(1);
+		writer.putInt(0x8A2E);
+		//		writer.putInt((npc.contract != null) ? npc.contract.getContractID() : 0x8A2E);
+		writer.putInt(0);
+		writer.putInt(0);
+
+		//TODO Guard
+		writer.put((byte) 0); //Is guard..
+
+		writer.putFloat(1500f); //npc.healthMax
+		writer.putFloat(1500f); //npc.health
+
+		//TODO Peace Zone
+		writer.put((byte) 1); //0=show tags, 1=don't
+		writer.putInt(0);
+		writer.put((byte) 0);
+	}
+
+	public void removeMinions() {
+
+		for (Mob toRemove : this.siegeMinionMap.keySet()) {
+
+			toRemove.setState(STATE.Disabled);
+
+			try {
+				toRemove.clearEffects();
+			}catch(Exception e){
+				Logger.error( e.getMessage());
+			}
+
+			if (toRemove.getParentZone() != null)
+				toRemove.getParentZone().zoneMobSet.remove(toRemove);
+
+			WorldGrid.RemoveWorldObject(toRemove);
+			DbManager.removeFromCache(toRemove);
+
+			PlayerCharacter petOwner = toRemove.getOwner();
+
+			if (petOwner != null) {
+
+				petOwner.setPet(null);
+				toRemove.setOwner(null);
+
+				PetMsg petMsg = new PetMsg(5, null);
+				Dispatch dispatch = Dispatch.borrow(petOwner, petMsg);
+				DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.PRIMARY);
+
+			}
+		}
+	}
+
+	private static int serializeExtraRune(NPC npc,int runeID, int cnt, ByteBufferWriter writer) {
+		writer.putInt(5);
+		writer.putInt(0);
+		writer.putInt(runeID);
+		if (cnt == 0) {
+			writer.putInt(GameObjectType.NPCClassRuneTwo.ordinal());
+		} else {
+			writer.putInt(GameObjectType.NPCClassRuneThree.ordinal());
+		}
+		writer.putInt(npc.currentID);
+		return cnt + 1;
+	}
+
+
+	@Override
+	public float getSpeed() {
+		if (this.isWalk()) {
+			return MBServerStatics.WALKSPEED;
+		} else {
+			return MBServerStatics.RUNSPEED;
+		}
+	}
+
+	@Override
+	public float getPassiveChance(String type, int AttackerLevel, boolean fromCombat) {
+		//TODO add this later for dodge
+		return 0f;
+	}
+
+	/**
+	 * @ Kill this Character
+	 */
+	@Override
+	public void killCharacter(AbstractCharacter attacker) {
+		//TODO Handle Death
+		killCleanup();
+
+		//TODO Send death message if needed
+	}
+
+	@Override
+	public void killCharacter(String reason) {
+		//TODO Handle Death
+		killCleanup();
+
+		//Orphan inventory so it can be looted
+		//if (!this.inSafeZone)
+		if (this.charItemManager != null) {
+			this.charItemManager.orphanInventory();
+		}
+
+		//TODO Send death message if needed
+		//Question? How would a mob die to water?
+	}
+
+	private void killCleanup() {
+		//TODO handle cleanup from death here
+
+		//set so character won't load
+		this.load = false;
+
+		//Create Corpse and add to world
+		//Corpse.makeCorpse(this);
+		//TODO damage equipped items
+		//TODO cleanup any timers
+	}
+
+	public Zone getParentZone() {
+		return this.parentZone;
+	}
+
+	public int getParentZoneID() {
+		if (this.parentZone != null) {
+			return this.parentZone.getObjectUUID();
+		}
+		return 0;
+	}
+
+	public void setParentZone(Zone zone) {
+		if (ConfigManager.serverType.equals(ServerType.LOGINSERVER)) {
+			return;
+		}
+
+		if (this.contract == null)
+			return;
+		//update ZoneManager's zone building list
+		if (zone != null) {
+			if (this.parentZone != null) {
+				if (zone.getObjectUUID() != this.parentZone.getObjectUUID()) {
+					this.parentZone.zoneNPCSet.remove(this);
+					zone.zoneNPCSet.add(this);
+				}
+			} else {
+				zone.zoneNPCSet.add(this);
+			}
+		} else if (this.parentZone != null) {
+			this.parentZone.zoneNPCSet.remove(this);
+		}
+
+		if (this.parentZone == null)
+			this.parentZone = zone;
+
+		if (!this.parentZone.isPlayerCity())
+			this.sellPercent = 1;
+
+
+		if (this.building != null){
+
+			BuildingModelBase buildingModel = BuildingModelBase.getModelBase(this.building.getMeshUUID());
+
+			Vector3fImmutable slotLocation = Vector3fImmutable.ZERO;
+
+			if (buildingModel != null){
+
+
+				int putSlot;
+				BuildingLocation buildingLocation = null;
+
+				//-1 slot means no slot available in building.
+				if (this.slot != -1)
+					putSlot = this.slot;
+				else
+					putSlot = NPC.getBuildingSlot(this);
+
+
+				buildingLocation = buildingModel.getSlotLocation(putSlot);
+
+				if (buildingLocation != null){
+					slotLocation = buildingLocation.getLoc();
+					this.setRot(new Vector3f(buildingLocation.getRot()));
+				}
+
+				else{
+					//only log if npc has been loaded in building.
+					if (this.building.getHirelings().containsKey(this) && putSlot != -1) {
+						Logger.error("could not slot npc : " + currentID + " in slot " + putSlot + " for building Mesh " + this.building.getMeshUUID());
+					}
+				}
+			}
+
+
+			Vector3fImmutable buildingWorldLoc = ZoneManager.convertLocalToWorld(this.building, slotLocation);
+
+			//Set floor and level here after building World Location.
+
+			this.region = BuildingManager.GetRegion(this.building, buildingWorldLoc.x, buildingWorldLoc.y, buildingWorldLoc.z);
+
+
+			if (this.region != null){
+
+
+				this.buildingFloor = region.getRoom();
+				this.buildingLevel = region.getLevel();
+				this.inBuildingLoc = ZoneManager.convertWorldToLocal(building, this.getLoc());
+
+			}else{
+				this.buildingFloor = -1;
+				this.buildingLevel = -1;
+			}
+			this.setBindLoc(new Vector3fImmutable(buildingWorldLoc.x, buildingWorldLoc.y, buildingWorldLoc.z));
+			if (ConfigManager.serverType.equals(ServerType.WORLDSERVER))
+				this.setLoc(new Vector3fImmutable(buildingWorldLoc.x, buildingWorldLoc.y, buildingWorldLoc.z));
+
+		}else{
+			this.setBindLoc(new Vector3fImmutable(this.statLat + zone.absX, this.statAlt + zone.absY, this.statLon + zone.absZ));
+			if (ConfigManager.serverType.equals(ServerType.WORLDSERVER))
+				this.setLoc(new Vector3fImmutable(this.statLat + zone.absX, this.statAlt + zone.absY, this.statLon + zone.absZ));
+
+		}
+		//create npc profits 
+		if (this.parentZone != null){
+			if (this.parentZone.isPlayerCity())
+				if (NPC.GetNPCProfits(this) == null)
+					NPCProfits.CreateProfits(this);
+		}
+
+	}
+
+
+
+	@Override
+	public  Vector3fImmutable getLoc() {
+
+		return super.getLoc();
+	}
+
+	public float getSpawnX() {
+		return this.statLat;
+	}
+
+	public float getSpawnY() {
+		return this.statAlt;
+	}
+
+	public float getSpawnZ() {
+		return this.statLon;
+	}
+
+	//Sets the relative position to a parent zone
+	public void setRelPos(Zone zone, float locX, float locY, float locZ) {
+
+		//update ZoneManager's zone building list
+		if (zone != null) {
+			if (this.parentZone != null) {
+				if (zone.getObjectUUID() != this.parentZone.getObjectUUID()) {
+					this.parentZone.zoneNPCSet.remove(this);
+					zone.zoneNPCSet.add(this);
+				}
+			} else {
+				zone.zoneNPCSet.add(this);
+			}
+		} else if (this.parentZone != null) {
+			this.parentZone.zoneNPCSet.remove(this);
+		}
+
+		this.statLat = locX;
+		this.statAlt = locY;	
+		this.statLon = locZ;
+		this.parentZone = zone;
+	}
+
+	public float getSellPercent() {
+		return this.sellPercent;
+	}
+
+	public void setSellPercent(float sellPercent) {
+		this.sellPercent = sellPercent;
+	}
+
+	public float getBuyPercent() {
+		return this.buyPercent;
+	}
+	public float getBuyPercent(PlayerCharacter player) {
+		if (NPC.GetNPCProfits(this) == null || this.guild == null)
+			return this.buyPercent;
+		NPCProfits profits = NPC.GetNPCProfits(this);
+		if (player.getGuild().equals(this.guild))
+			return  profits.buyGuild;
+		if (player.getGuild().getNation().equals(this.guild.getNation()))
+			return profits.buyNation;
+
+		return profits.buyNormal;		
+	}
+
+	public float getSellPercent(PlayerCharacter player) {
+		if (NPC.GetNPCProfits(this) == null || this.guild == null)
+			return 1 + this.sellPercent;
+		NPCProfits profits = NPC.GetNPCProfits(this);
+		if (player.getGuild().equals(this.guild))
+			return 1 + profits.sellGuild;
+		if (player.getGuild().getNation().equals(this.guild.getNation()))
+			return 1 + profits.sellNation;
+
+		return 1 + profits.sellNormal;
+	}
+
+	public void setBuyPercent(float buyPercent) {
+		this.buyPercent = buyPercent;
+	}
+
+	@Override
+	public boolean canBeLooted() {
+		return !this.isAlive();
+	}
+
+	// *** Refactor : this has a useInit flag that can be removed
+
+	public static NPC createNPC(String name, int contractID, Vector3fImmutable spawn, Guild guild, boolean isMob, Zone parent, short level, boolean useInit, Building building) {
+		NPC npcWithoutID = new NPC(name, (short) 0, (short) 0, (short) 0, (short) 0,
+				(short) 0, (short) 1, 0, false, false, false, spawn, spawn, Vector3fImmutable.ZERO,
+				(short) 1, (short) 1, (short) 1, guild, (byte) 0, 0, isMob, building, contractID, parent);
+
+		npcWithoutID.setLevel(level);
+		if (parent != null) {
+			npcWithoutID.setRelPos(parent, spawn.x - parent.absX, spawn.y - parent.absY, spawn.z - parent.absZ);
+		}
+
+		if (npcWithoutID.mobBase == null) {
+			return null;
+		}
+		NPC npc;
+		try {
+			npc = DbManager.NPCQueries.ADD_NPC(npcWithoutID, isMob);
+			npc.setObjectTypeMask(MBServerStatics.MASK_NPC);
+		} catch (Exception e) {
+			Logger.error( e);
+			npc = null;
+		}
+
+
+		return npc;
+	}
+
+	public static NPC getNPC(int id) {
+
+		if (id == 0)
+			return null;
+		NPC npc = (NPC) DbManager.getFromCache(GameObjectType.NPC, id);
+		if (npc != null)
+			return npc;
+
+		return DbManager.NPCQueries.GET_NPC(id);
+	}
+
+	public ArrayList<Integer> getModTypeTable() {
+		return this.modTypeTable;
+	}
+
+	@Override
+	public void updateDatabase() {
+		DbManager.NPCQueries.updateDatabase(this);
+	}
+
+	public int getSymbol() {
+		return symbol;
+	}
+
+	public ArrayList<Integer> getModSuffixTable() {
+		return modSuffixTable;
+	}
+
+	public ArrayList<Byte> getItemModTable() {
+		return itemModTable;
+	}
+
+	public boolean isRanking() {
+
+		return this.upgradeDateTime != null;
+	}
+
+	@Override
+	public void runAfterLoad() {
+
+		if (ConfigManager.serverType.equals(ServerType.LOGINSERVER))
+			return;
+
+		if (this.fidalityID != 0)
+			DbManager.NPCQueries.LOAD_RUNES_FOR_FIDELITY_NPC(this);
+
+		try{
+
+			this.equip = loadEquipmentSet(this.equipmentSetID);
+
+		}catch(Exception e){
+			Logger.error(e.getMessage());
+		}
+
+		if (this.equip == null)
+			this.equip = new HashMap<>();
+
+		try{
+
+			DbManager.NPCQueries.LOAD_ALL_ITEMS_TO_PRODUCE(this);
+
+			for (ProducedItem producedItem : this.forgedItems){
+				MobLoot ml = new MobLoot(this, ItemBase.getItemBase(producedItem.getItemBaseID()), false);
+
+				DbManager.NPCQueries.UPDATE_ITEM_ID(producedItem.getID(), currentID, ml.getObjectUUID());
+
+				if (producedItem.isInForge()){
+
+					if (producedItem.getPrefix() != null && !producedItem.getPrefix().isEmpty()){
+						ml.addPermanentEnchantment(producedItem.getPrefix(), 0, 0, true);
+						ml.setPrefix(producedItem.getPrefix());
+					}
+
+					if (producedItem.getSuffix() != null && !producedItem.getSuffix().isEmpty()){
+						ml.addPermanentEnchantment(producedItem.getSuffix(), 0, 0, false);
+						ml.setSuffix(producedItem.getSuffix());
+					}
+
+					if (!producedItem.isRandom())
+						ml.setIsID(true);
+
+					ml.loadEnchantments();
+
+					ml.setValue(producedItem.getValue());
+					ml.setDateToUpgrade(producedItem.getDateToUpgrade().getMillis());
+					ml.containerType = Enum.ItemContainerType.FORGE;
+					this.addItemToForge(ml);
+				}else{
+					if (producedItem.getPrefix() != null && !producedItem.getPrefix().isEmpty()){
+						ml.addPermanentEnchantment(producedItem.getPrefix(), 0, 0, true);
+						ml.setPrefix(producedItem.getPrefix());
+					}
+
+					if (producedItem.getSuffix() != null && !producedItem.getSuffix().isEmpty()){
+						ml.addPermanentEnchantment(producedItem.getSuffix(), 0, 0, false);
+						ml.setSuffix(producedItem.getSuffix());
+					}
+
+					ml.setDateToUpgrade(producedItem.getDateToUpgrade().getMillis());
+					ml.containerType = Enum.ItemContainerType.INVENTORY;
+					ml.setIsID(true);
+
+					this.charItemManager.addItemToInventory(ml);
+				}
+				ml.setValue(producedItem.getValue());
+			}
+
+			// Create NPC bounds object
+			Bounds npcBounds = Bounds.borrow();
+			npcBounds.setBounds(this.getLoc());
+
+		}catch (Exception e){
+			Logger.error( e.getMessage());
+		}
+	}
+
+	public void removeFromZone() {
+		this.parentZone.zoneNPCSet.remove(this);
+	}
+
+	@Override
+	protected ConcurrentHashMap<Integer, CharacterPower> initializePowers() {
+		return new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW);
+	}
+
+	public DateTime getUpgradeDateTime() {
+		lock.readLock().lock();
+		try {
+			return upgradeDateTime;
+		} finally {
+			lock.readLock().unlock();
+		}
+	}
+
+	public void setUpgradeDateTime(DateTime upgradeDateTime) {
+
+		if (!DbManager.NPCQueries.updateUpgradeTime(this, upgradeDateTime)){
+			Logger.error( "Failed to set upgradeTime for building " + currentID);
+			return;
+		}
+
+		this.upgradeDateTime = upgradeDateTime;
+	}
+
+	public ArrayList<MobLoot> getRolling() {
+		synchronized(rolling){
+			return rolling;
+		}
+	}
+
+	public  int getRollingCount(){
+		synchronized(this.rolling){
+			return rolling.size();
+		}
+	}
+
+	public void addItemToForge(MobLoot item){
+		synchronized(this.rolling){
+			this.rolling.add(item);
+		}
+	}
+
+	public void removeItemFromForge(Item item){
+		synchronized(this.rolling){
+			this.rolling.remove(item);
+		}
+	}
+
+	public ArrayList<Building> getProtectedBuildings() {
+
+		ArrayList<Building> protectedBuildings = new ArrayList<>();
+
+		if (this.building == null)
+			return protectedBuildings;
+
+		if (this.building.getCity() == null)
+			return protectedBuildings;
+
+		for (Building b : this.building.getCity().getParent().zoneBuildingSet) {
+
+			if (b.getBlueprint() == null)
+				continue;
+
+			if (b.getProtectionState().equals(ProtectionState.CONTRACT))
+				protectedBuildings.add(b);
+
+			if (b.getProtectionState().equals(ProtectionState.PENDING))
+				protectedBuildings.add(b);
+		}
+
+		return protectedBuildings;
+	}
+
+	@Override
+	public Guild getGuild(){
+		if (this.building != null)
+			return building.getGuild();
+		return this.guild;
+	}
+
+	public ArrayList<Mob> getSiegeMinions() {
+		return siegeMinions;
+	}
+
+	public HashSet<Integer> getCanRoll() {
+
+		if (this.canRoll == null){
+			this.canRoll = DbManager.ItemQueries.GET_ITEMS_FOR_VENDOR(this.vendorID);
+
+			if (this.contract.getVendorID() == 102){
+
+				for (int i = 0;i<this.getRank();i++){
+					int subID = i + 1;
+					this.canRoll.add(910010 + subID);
+				}
+
+				if (this.getRank() == 7)
+					this.canRoll.add(910018);
+			}
+		}
+
+		return this.canRoll;
+	}
+
+	public int getRollingTimeInSeconds(int itemID){
+
+		ItemBase ib = ItemBase.getItemBase(itemID);
+
+		if (ib == null)
+			return 0;
+
+		if (ib.getType() == ItemType.SCROLL)
+			return this.getRank() * 60 * 60 * 3;
+
+		float time;
+
+		if (this.building == null)
+			return 600;
+
+		float rank = this.building.getRank() - 1;
+		float rate = (float) (2.5 * rank);
+		time = (20 - rate);
+		time *= 60;
+		return (int) time;
+	}
+
+	public ConcurrentHashMap<Mob, Integer> getSiegeMinionMap() {
+		return siegeMinionMap;
+	}
+
+	public void setSiegeMinionMap(ConcurrentHashMap<Mob, Integer> siegeMinionMap) {
+		this.siegeMinionMap = siegeMinionMap;
+	}
+
+	// Method removes the npc from the game simulation
+	// and deletes it from the database.
+
+	public boolean remove() {
+
+		Building building;
+
+		// Remove npc from it's building
+
+		building = this.building;
+
+		if (building != null) {
+			building.getHirelings().remove(this);
+			this.removeMinions();
+		}
+
+		// Delete npc from database
+
+		if (DbManager.NPCQueries.DELETE_NPC(this) == 0) {
+			return false;
+		}
+
+		// Remove npc from the simulation
+
+		this.removeFromCache();
+		WorldGrid.RemoveWorldObject(this);
+		WorldGrid.removeObject(this);
+		return true;
+	}
+
+	public static void loadAllPirateNames() {
+
+		DbManager.NPCQueries.LOAD_PIRATE_NAMES();
+	}
+
+	public static String getPirateName(int mobBaseID) {
+
+		ArrayList<String> nameList = null;
+
+		// If we cannot find name for this mobbase then
+		// fallback to human male
+
+		if (_pirateNames.containsKey(mobBaseID))
+			nameList = _pirateNames.get(mobBaseID);
+		else
+			nameList = _pirateNames.get(2111);
+
+		if (nameList == null) {
+			Logger.error("Null name list for 2111!");
+		}
+
+		return nameList.get(ThreadLocalRandom.current().nextInt(nameList.size()));
+
+	}
+
+	public synchronized Mob createSiegeMob(int loadID, Guild guild, Zone parent, Vector3fImmutable loc, short level) {
+
+		MobBase minionMobBase;
+		Mob mob;
+
+		if (siegeMinionMap.size() == 3)
+			return null;
+
+		minionMobBase = MobBase.getMobBase(loadID);
+
+		if (minionMobBase == null)
+			return null;
+
+		mob = new Mob(minionMobBase, guild, parent, level,new Vector3fImmutable(1,1,1), 0,false);
+
+		mob.despawned = true;
+		DbManager.addToCache(mob);
+
+		if (parent != null)
+			mob.setRelPos(parent, loc.x - parent.absX, loc.y - parent.absY, loc.z - parent.absZ);
+
+		mob.setObjectTypeMask(MBServerStatics.MASK_MOB | mob.getTypeMasks());
+
+		// mob.setMob();
+		mob.setSiege(true);
+		mob.setParentZone(parent);
+
+		int slot = 0;
+
+		if (!siegeMinionMap.containsValue(1))
+			slot = 1;
+		else if (!siegeMinionMap.containsValue(2))
+			slot = 2;
+
+		siegeMinionMap.put(mob, slot);
+		mob.setInBuildingLoc(this.building, this);
+	
+		Vector3fImmutable buildingWorldLoc = ZoneManager.convertLocalToWorld(this.building, mob.getInBuildingLoc());
+		mob.setBindLoc(buildingWorldLoc);
+		mob.setLoc(buildingWorldLoc);
+		
+		mob.setSpawnTime(10);
+		mob.setNpcOwner(this);
+		mob.setState(STATE.Awake);
+
+		return mob;
+	}
+
+	public int getUpgradeCost() {
+
+		int upgradeCost;
+
+		upgradeCost = Integer.MAX_VALUE;
+
+		if (this.getRank() < 7)
+			return (this.getRank() * 100650) + 21450;
+
+		return upgradeCost;
+	}
+
+	public int getUpgradeTime() {
+
+		int upgradeTime; // expressed in hours
+
+		upgradeTime = Integer.MAX_VALUE;
+
+		if (this.getRank() < 7)
+			return (this.getRank() * 8);
+
+		return 0;
+	}
+
+	public static boolean ISGuardCaptain(int contractID){
+		return MinionType.ContractToMinionMap.containsKey(contractID);
+	}
+
+	public synchronized Item produceItem(int playerID,int amount, boolean isRandom, int pToken, int sToken, String customName, int itemID) {
+
+		Zone serverZone;
+		City city;
+		Item item = null;
+
+		PlayerCharacter player = null;
+
+		if (playerID != 0)
+			player = SessionManager.getPlayerCharacterByID(playerID);
+
+		try{
+
+			if (this.getRollingCount() >= this.getRank()) {
+
+				if (player != null)
+					ChatManager.chatSystemInfo(player, this.getName() + " " + this.getContract().getName() + " slots are full");
+
+				return null;
+			}
+
+			// Cannot roll items without a warehouse.
+			// Due to the fact fillForge references the
+			// warehouse and early exits.  *** Refactor???
+
+			serverZone = this.building.getParentZone();
+
+			if (serverZone == null)
+				return null;
+
+			city = City.GetCityFromCache(serverZone.getPlayerCityUUID());
+
+			if (city == null) {
+
+				if (player != null)
+					ErrorPopupMsg.sendErrorMsg(player, "Could not find city."); // Production denied: This building must be protected to gain access to warehouse resources.
+
+				return null;
+			}
+
+			if (this.building == null){
+
+				if (player != null)
+					ErrorPopupMsg.sendErrorMsg(player, "Could not find building."); // Production denied: This building must be protected to gain ac
+
+				return null;
+			}
+
+			//TODO create Normal Items.
+
+			if (amount == 0)
+				amount = 1;
+
+			if (isRandom)
+				item = ItemFactory.randomRoll(this, player,amount, itemID);
+			else
+				item = ItemFactory.fillForge(this, player,amount,itemID, pToken,sToken, customName);
+
+			if (item == null)
+				return null;
+
+			ItemProductionMsg	outMsg = new ItemProductionMsg(this.building, this, item, 8, true);
+			DispatchMessage.dispatchMsgToInterestArea(this, outMsg, DispatchChannel.SECONDARY, 700, false, false);
+			
+		} catch(Exception e){
+			Logger.error(e);
+		}
+		return item;
+	}
+
+	public synchronized boolean completeItem(int itemUUID) {
+
+		MobLoot targetItem;
+
+		try {
+			targetItem = MobLoot.getFromCache(itemUUID);
+
+			if (targetItem == null)
+				return false;
+
+			if (!this.getCharItemManager().forgeContains(targetItem, this))
+				return false;
+
+			if (!DbManager.NPCQueries.UPDATE_ITEM_TO_INVENTORY(targetItem.getObjectUUID(), currentID))
+				return false;
+
+			targetItem.setIsID(true);
+
+			this.rolling.remove(targetItem);
+			this.getCharItemManager().addItemToInventory(targetItem);
+
+			//remove from client forge window
+
+			ItemProductionMsg	outMsg1 = new ItemProductionMsg(this.building, this, targetItem, 9, true);
+			DispatchMessage.dispatchMsgToInterestArea(this, outMsg1, DispatchChannel.SECONDARY, MBServerStatics.STRUCTURE_LOAD_RANGE, false, false);
+			ItemProductionMsg outMsg = new ItemProductionMsg(this.building, this, targetItem, 10, true);
+			DispatchMessage.dispatchMsgToInterestArea(this, outMsg, DispatchChannel.SECONDARY, MBServerStatics.STRUCTURE_LOAD_RANGE, false, false);
+
+		} catch(Exception e) {
+			Logger.error( e.getMessage());
+		}
+		return true;
+	}
+
+	public int getFidalityID() {
+		return fidalityID;
+	}
+
+	public int getBuildingLevel() {
+		return buildingLevel;
+	}
+
+	public int getBuildingFloor() {
+		return buildingFloor;
+	}
+
+	public HashMap<Integer, MobEquipment> getEquip() {
+		return equip;
+	}
+
+	public static int getBuildingSlot(NPC npc){
+		int slot = -1;
+
+		if (npc.building == null)
+			return -1;
+
+
+
+		BuildingModelBase buildingModel = BuildingModelBase.getModelBase(npc.building.getMeshUUID());
+
+		if (buildingModel == null)
+			return -1;
+
+		if (npc.fidalityID != 0){
+
+			if (npc.building.fidelityNpcs.get(npc.currentID) != null){
+				slot = npc.building.fidelityNpcs.get(npc.currentID);
+			}
+
+		} else{
+			if (npc.building.getHirelings().containsKey(npc))
+				slot =  (npc.building.getHirelings().get(npc));
+		}
+
+		if (buildingModel.getNPCLocation(slot) == null)
+			return -1;
+
+
+		return slot;
+	}
+
+	public int getEquipmentSetID() {
+		return equipmentSetID;
+	}
+
+	public static boolean UpdateSlot(NPC npc,int slot){
+
+		if (!DbManager.NPCQueries.UPDATE_SLOT(npc, slot))
+			return false;
+
+		npc.slot = slot;
+		return true;
+	}
+
+	public static boolean UpdateEquipSetID(NPC npc, int equipSetID){
+
+		if (!EquipmentSetEntry.EquipmentSetMap.containsKey(equipSetID))
+			return false;
+
+		if (!DbManager.NPCQueries.UPDATE_EQUIPSET(npc, equipSetID))
+			return false;
+
+		npc.equipmentSetID = equipSetID;
+
+		return true;
+	}
+
+	public static boolean UpdateRaceID(NPC npc, int raceID){
+
+		if (!DbManager.NPCQueries.UPDATE_MOBBASE(npc, raceID))
+			return false;
+
+		npc.loadID = raceID;
+		npc.mobBase = MobBase.getMobBase(npc.loadID);
+		return true;
+	}
+
+	public String getNameOverride() {
+		return nameOverride;
+	}
+
+	public static NPCProfits GetNPCProfits(NPC npc){
+		return NPCProfits.ProfitCache.get(npc.currentID);
+	}
+
+	public int getRepairCost() {
+		return repairCost;
+	}
+
+	public void setRepairCost(int repairCost) {
+		this.repairCost = repairCost;
+	}
+	
+	public void processUpgradeNPC(PlayerCharacter player) {
+
+		int rankCost;
+		Building building;
+		DateTime dateToUpgrade;
+
+
+		this.lock.writeLock().lock();
+		try{
+			
+	
+			building = this.getBuilding();
+
+			// Cannot upgrade an npc not within a building
+
+			if (building == null)
+				return;
+			
+			// Cannot upgrade an npc at max rank
+
+			if (this.getRank() == 7)
+				return;
+
+			// Cannot upgrade an npc who is currently ranking
+
+			if (this.isRanking())
+				return;
+
+			rankCost = this.getUpgradeCost();
+
+			// SEND NOT ENOUGH GOLD ERROR
+
+			if (!building.hasFunds(rankCost)){
+				sendErrorPopup(player, 127);
+				return;
+			}
+
+			if (rankCost > building.getStrongboxValue()) {
+				sendErrorPopup(player, 127);
+				return;
+			}
+
+			try {
+
+				if (!building.transferGold(-rankCost,false))
+					return;
+
+				dateToUpgrade = DateTime.now().plusHours(this.getUpgradeTime());
+
+				this.setUpgradeDateTime(dateToUpgrade);
+
+				// Schedule upgrade job
+
+				this.submitUpgradeJob();
+
+			} catch (Exception e) {
+				PlaceAssetMsg.sendPlaceAssetError(player.getClientConnection(), 1, "A Serious error has occurred. Please post details for to ensure transaction integrity");
+			}
+		}catch(Exception e){
+			Logger.error(e);
+		}finally{
+		this.lock.writeLock().unlock();	
+		}
+	}
+	
+	public void processRedeedNPC( ClientConnection origin) {
+
+		// Member variable declaration
+		PlayerCharacter player;
+		Contract contract;
+		CharacterItemManager itemMan;
+		ItemBase itemBase;
+		Item item;
+
+		this.lock.writeLock().lock();
+		
+		try{
+			
+		
+			if (building == null)
+				return;
+		player = SessionManager.getPlayerCharacter(origin);
+		itemMan = player.getCharItemManager();
+
+			contract = this.getContract();
+
+			if (!player.getCharItemManager().hasRoomInventory((short)1)){
+				ErrorPopupMsg.sendErrorPopup(player, 21);
+				return;
+			}
+
+
+			if (!building.getHirelings().containsKey(this))
+				return;
+
+			if (!this.remove()) {
+				PlaceAssetMsg.sendPlaceAssetError(player.getClientConnection(), 1, "A Serious error has occurred. Please post details for to ensure transaction integrity");
+				return;
+			}
+
+			building.getHirelings().remove(this);
+
+			itemBase = ItemBase.getItemBase(contract.getContractID());
+
+			if (itemBase == null) {
+				Logger.error("Could not find Contract for npc: " + this.getObjectUUID());
+				return;
+			}
+
+			boolean itemWorked = false;
+
+			item = new Item( itemBase, player.getObjectUUID(), OwnerType.PlayerCharacter, (byte) ((byte) this.getRank() - 1), (byte) ((byte) this.getRank() - 1),
+					(short) 1, (short) 1, true, false,  Enum.ItemContainerType.INVENTORY, (byte) 0,
+                    new ArrayList<>(),"");
+			item.setNumOfItems(1);
+			item.containerType = Enum.ItemContainerType.INVENTORY;
+
+			try {
+				item = DbManager.ItemQueries.ADD_ITEM(item);
+				itemWorked = true;
+			} catch (Exception e) {
+				Logger.error(e);
+			}
+			if (itemWorked) {
+				itemMan.addItemToInventory(item);
+				itemMan.updateInventory();
+			}
+
+			ManageCityAssetsMsg mca = new ManageCityAssetsMsg();
+			mca.actionType = SVR_CLOSE_WINDOW;
+			mca.setTargetType(building.getObjectType().ordinal());
+			mca.setTargetID(building.getObjectUUID());
+			origin.sendMsg(mca);
+		
+		}catch(Exception e){
+			Logger.error(e);
+		}finally{
+			this.lock.writeLock().unlock();
+		}
+
+	}
+		
+		
+
+
+}
diff --git a/src/engine/objects/NPCProfits.java b/src/engine/objects/NPCProfits.java
new file mode 100644
index 00000000..c96ac55b
--- /dev/null
+++ b/src/engine/objects/NPCProfits.java
@@ -0,0 +1,106 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.objects;
+
+import engine.Enum.ProfitType;
+import engine.gameManager.DbManager;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.HashMap;
+
+public class NPCProfits  {
+
+
+	public int	npcUID;
+	public float	buyNormal;
+	public float	buyGuild;
+	public float	buyNation;
+	public float	sellNormal;
+	public float	sellGuild;
+	public float	sellNation;
+	
+	public static NPCProfits defaultProfits = new NPCProfits(0,.33f,.33f,.33f,1,1,1);
+	
+ 
+
+	
+	public static HashMap <Integer,NPCProfits> ProfitCache = new HashMap<>();
+
+	/**
+	 * ResultSet Constructor
+	 */
+
+	public NPCProfits(ResultSet rs) throws SQLException {
+	
+		npcUID = rs.getInt("npcUID");
+		buyNormal = rs.getFloat("buy_normal");
+		buyGuild = rs.getFloat("buy_guild");
+		buyNation = rs.getFloat("buy_nation");
+		sellNormal = rs.getFloat("sell_normal");
+		sellGuild = rs.getFloat("sell_guild");
+		sellNation = rs.getFloat("sell_nation");
+
+	}
+
+	public NPCProfits(int npcUID, float buyNormal, float buyGuild, float buyNation, float sellNormal,
+			float sellGuild, float sellNation) {
+		super();
+		this.npcUID = npcUID;
+		this.buyNormal = buyNormal;
+		this.buyGuild = buyGuild;
+		this.buyNation = buyNation;
+		this.sellNormal = sellNormal;
+		this.sellGuild = sellGuild;
+		this.sellNation = sellNation;
+	}
+	
+	public static boolean UpdateProfits(NPC npc, NPCProfits profit, ProfitType profitType, float value){
+		
+		try {
+			if (!DbManager.NPCQueries.UPDATE_PROFITS(npc, profitType, value))
+				return false;
+		}catch(Exception e){
+			e.printStackTrace();
+		}
+		
+		switch (profitType){
+		case BuyNormal:
+			profit.buyNormal = value;
+			break;
+		case BuyGuild:
+			profit.buyGuild = value;
+			break;
+		case BuyNation:
+			profit.buyNation = value;
+			break;
+		case SellNormal:
+			profit.sellNormal = value;
+			break;
+		case SellGuild:
+			profit.sellGuild = value;
+			break;
+		case SellNation:
+			profit.sellNation = value;
+			break;
+			
+		}
+		return true;
+	}
+
+
+	public static boolean CreateProfits(NPC npc){
+		DbManager.NPCQueries.CREATE_PROFITS(npc);
+		NPCProfits profits = new NPCProfits(npc.getObjectUUID(),.33f,.33f,.33f,1,1,1);
+		NPCProfits.ProfitCache.put(npc.getObjectUUID(), profits);
+	return true;
+	}
+	
+}
diff --git a/src/engine/objects/NPCRune.java b/src/engine/objects/NPCRune.java
new file mode 100644
index 00000000..9f4ad3fb
--- /dev/null
+++ b/src/engine/objects/NPCRune.java
@@ -0,0 +1,119 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.objects;
+
+import engine.gameManager.DbManager;
+import engine.net.ByteBufferWriter;
+import org.pmw.tinylog.Logger;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+
+
+public class NPCRune extends AbstractGameObject {
+
+	private final RuneBase runeBase;
+	private final int player;
+	private final ArrayList<SkillReq> skillsGranted;
+	private final ArrayList<RuneBaseEffect> effectsGranted;
+
+	/**
+	 * No Table ID Constructor
+	 */
+	public NPCRune(RuneBase runeBase, int characterID) {
+		super();
+		this.runeBase = runeBase;
+		this.player = characterID;
+		if (this.runeBase != null)
+			this.skillsGranted = DbManager.SkillReqQueries.GET_REQS_FOR_RUNE(this.runeBase.getObjectUUID());
+		else
+			this.skillsGranted = new ArrayList<>();
+		this.effectsGranted = new ArrayList<>();
+	}
+
+	/**
+	 * Normal Constructor
+	 */
+	public NPCRune(RuneBase runeBase, int characterID, int newUUID) {
+		super(newUUID);
+		this.runeBase = runeBase;
+		this.player = characterID;
+		if (this.runeBase == null)
+			this.skillsGranted = DbManager.SkillReqQueries.GET_REQS_FOR_RUNE(this.runeBase.getObjectUUID());
+		else
+			this.skillsGranted = new ArrayList<>();
+		this.effectsGranted = new ArrayList<>();
+	}
+	/**
+	 * ResultSet Constructor
+	 */
+	public NPCRune(ResultSet rs) throws SQLException {
+		super(rs);
+
+		this.runeBase = RuneBase.getRuneBase(rs.getInt("RuneBaseID"));
+		this.player = rs.getInt("NpcID");
+		if (this.runeBase != null) {
+			this.skillsGranted = DbManager.SkillReqQueries.GET_REQS_FOR_RUNE(this.runeBase.getObjectUUID());
+			this.effectsGranted = DbManager.RuneBaseEffectQueries.GET_EFFECTS_FOR_RUNEBASE(this.runeBase.getObjectUUID());
+		} else {
+			Logger.error("Failed to find RuneBase for NPCRune " + this.getObjectUUID());
+			this.skillsGranted = new ArrayList<>();
+			this.effectsGranted =  new ArrayList<>();
+		}
+	}
+
+	/*
+	 * Getters
+	 */
+	public RuneBase getRuneBase() {
+		return this.runeBase;
+	}
+
+	public int getRuneBaseID() {
+		if (this.runeBase != null)
+			return this.runeBase.getObjectUUID();
+		return 0;
+	}
+
+	public int getPlayerID() {
+		return this.player;
+	}
+
+	public ArrayList<SkillReq> getSkillsGranted() {
+		return this.skillsGranted;
+	}
+
+	public ArrayList<RuneBaseEffect> getEffectsGranted() {
+		return this.effectsGranted;
+	}
+
+	/*
+	 * Serializing
+	 */
+	
+	public static void serializeForClientMsg(NPCRune npcRune,ByteBufferWriter writer) {
+		if (npcRune.runeBase != null) {
+			writer.putInt(npcRune.runeBase.getType());
+			writer.putInt(0);
+			writer.putInt(npcRune.runeBase.getObjectUUID());
+			writer.putInt(npcRune.getObjectType().ordinal());
+			writer.putInt(npcRune.getObjectUUID());
+		} else {
+			for (int i = 0; i < 5; i++)
+				writer.putInt(0);
+		}
+	}
+
+	@Override
+	public void updateDatabase() {
+
+	}
+}
diff --git a/src/engine/objects/Nation.java b/src/engine/objects/Nation.java
new file mode 100644
index 00000000..59b69294
--- /dev/null
+++ b/src/engine/objects/Nation.java
@@ -0,0 +1,121 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+ package engine.objects;
+
+import engine.net.ByteBufferWriter;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+
+public class Nation extends AbstractWorldObject {
+
+	private final String name;
+	private GuildTag gt;
+	private String motd = "";
+	private int primaryGuildID = 0;
+
+	/**
+	 * No Id Constructor
+	 */
+	public Nation( String name, GuildTag gt) {
+		super();
+		this.name = name;
+		this.gt = gt;
+	}
+
+	/**
+	 * Normal Constructor
+	 */
+	public Nation(String name, GuildTag gt, int newUUID) {
+		super(newUUID);
+		this.name = name;
+		this.gt = gt;
+	}
+
+	/**
+	 * ResultSet Constructor
+	 */
+	public Nation(ResultSet rs) throws SQLException {
+		super(rs);
+
+		this.name = rs.getString("name");
+
+		this.gt = new GuildTag(	rs.getInt("backgroundColor01"),
+								rs.getInt("backgroundColor02"),
+								rs.getInt("symbolColor"),
+								rs.getInt("symbol"),
+								rs.getInt("backgroundDesign"));
+		this.motd = rs.getString("motd");
+		this.primaryGuildID = rs.getInt("primaryGuild");
+	}
+
+	/*
+	 * Getters
+	 */
+	@Override
+	public String getName() {
+		return this.name;
+	}
+
+	public GuildTag getGuildTag() {
+		return this.gt;
+	}
+
+	public String getMOTD() {
+		return this.motd;
+	}
+
+	public void setMOTD(String value) {
+		this.motd = value;
+	}
+
+	public int getPrimaryGuildID() {
+		return this.primaryGuildID;
+	}
+
+	public void setPrimaryGuildID(int value) {
+		this.primaryGuildID = value;
+	}
+
+	/*
+	 * Utils
+	 */
+	private static Nation n;
+
+	public static Nation getErrantNation() {
+		if (n == null) {
+			n = new Nation("None", GuildTag.ERRANT, 0);
+		}
+		return n;
+	}
+
+
+
+	/*
+	 * Serialization
+	 */
+
+	public static void serializeForTrack(Nation nation,ByteBufferWriter writer) {
+		writer.putInt(nation.getObjectType().ordinal());
+		writer.putInt(nation.getObjectUUID());
+		writer.put((byte)1);
+		GuildTag._serializeForDisplay(nation.gt,writer);
+	}
+
+
+	@Override
+	public void updateDatabase() {
+
+	}
+
+	@Override
+	public void runAfterLoad() {}
+}
diff --git a/src/engine/objects/PlayerBonuses.java b/src/engine/objects/PlayerBonuses.java
new file mode 100644
index 00000000..60492929
--- /dev/null
+++ b/src/engine/objects/PlayerBonuses.java
@@ -0,0 +1,500 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.objects;
+
+import engine.Enum;
+import engine.Enum.ModType;
+import engine.Enum.SourceType;
+import engine.gameManager.ChatManager;
+import engine.gameManager.ConfigManager;
+import engine.gameManager.PowersManager;
+import engine.powers.DamageShield;
+import engine.powers.EffectsBase;
+import engine.powers.effectmodifiers.AbstractEffectModifier;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.concurrent.ConcurrentHashMap;
+
+
+public class PlayerBonuses {
+
+	//First bonus set
+	private ConcurrentHashMap<AbstractEffectModifier, Float> bonusFloats = new ConcurrentHashMap<>();
+	private ConcurrentHashMap<AbstractEffectModifier, DamageShield> bonusDamageShields = new ConcurrentHashMap<>();
+	private ConcurrentHashMap<AbstractEffectModifier, String> bonusStrings = new ConcurrentHashMap<>();
+	private ConcurrentHashMap<ModType, HashSet<SourceType>> bonusLists = new ConcurrentHashMap<>();
+	private ConcurrentHashMap<ModType, HashMap<SourceType, Boolean>> bonusBools = new ConcurrentHashMap<>();
+	private ConcurrentHashMap<SourceType, Float> skillBonuses = new ConcurrentHashMap<>();
+	private ConcurrentHashMap<ModType, Float> regens = new ConcurrentHashMap<>();
+
+	//If active == 0 then all gets come from the A list and all puts go to the B list
+	//If active == 1 then all gets come from the B list and all puts go to the A list
+	//They alternate each time bonuses are calculated so the one being updated isn't read from.
+	
+
+	/**
+	 * Generic Constructor
+	 */
+	public PlayerBonuses(PlayerCharacter pc) {
+	}
+	
+	public static void InitializeBonuses(PlayerCharacter player){
+		if (player.bonuses == null)
+			return;
+		if (ConfigManager.serverType.equals(Enum.ServerType.LOGINSERVER))
+			return;
+		
+		player.bonuses.calculateRuneBaseEffects(player);
+	}
+
+	public PlayerBonuses(Mob mob) {
+		clearRuneBaseEffects();
+	}
+
+	public static PlayerBonuses grantBonuses(AbstractCharacter ac) {
+
+		if (ac.getObjectType().equals(Enum.GameObjectType.PlayerCharacter))
+			return new PlayerBonuses((PlayerCharacter)ac);
+		else if (ac.getObjectType().equals(Enum.GameObjectType.Mob))
+			return new PlayerBonuses((Mob)ac);
+		else
+			return null;
+	}
+
+	public void clearRuneBaseEffects() {
+		
+			this.bonusBools.clear();
+			this.bonusFloats.clear();
+			this.bonusStrings.clear();
+			this.bonusDamageShields.clear();
+			this.bonusLists.clear();
+			this.skillBonuses.clear();
+			this.regens.put(ModType.HealthRecoverRate, (float) 1);
+			this.regens.put(ModType.ManaRecoverRate, (float) 1);
+			this.regens.put(ModType.StaminaRecoverRate, (float) 1);
+	}
+	
+	
+
+	public void calculateRuneBaseEffects(PlayerCharacter pc) {
+		//Clear everything
+		clearRuneBaseEffects();
+
+		//recalculate race
+		
+		
+		if (pc.getRace() != null){
+			
+			
+			if (pc.getRace().getEffectsList() != null)
+			for (MobBaseEffects raceEffect: pc.getRace().getEffectsList()){
+				EffectsBase eb = PowersManager.getEffectByToken(raceEffect.getToken());
+				
+				if (eb == null)
+					continue;
+				if (pc.getLevel() < raceEffect.getReqLvl())
+					continue;
+				for (AbstractEffectModifier modifier: eb.getModifiers()){
+					modifier.applyBonus(pc, raceEffect.getRank());
+				}
+				
+			}
+			
+			if (SkillsBase.runeSkillsCache.containsKey(pc.getRaceID())){
+				for (int skillToken : SkillsBase.runeSkillsCache.get(pc.getRaceID()).keySet()){
+					float amount = SkillsBase.runeSkillsCache.get(pc.getRaceID()).get(skillToken);
+					
+					SkillsBase sb = SkillsBase.tokenCache.get(skillToken);
+					
+					if (sb == null)
+						continue;
+					if (this.skillBonuses.containsKey(sb.sourceType) == false)
+						this.skillBonuses.put(sb.sourceType, amount);
+					else
+						this.skillBonuses.put(sb.sourceType, this.skillBonuses.get(sb.sourceType) + amount);
+
+				}
+			}
+		}
+		
+		//calculate baseclass effects
+		if (pc.getBaseClass() != null){
+			
+			if (pc.getBaseClass().getEffectsList() != null)
+				for (MobBaseEffects classEffect: pc.getBaseClass().getEffectsList()){
+					EffectsBase eb = PowersManager.getEffectByToken(classEffect.getToken());
+					
+					if (eb == null)
+						continue;
+					if (pc.getLevel() < classEffect.getReqLvl())
+						continue;
+					for (AbstractEffectModifier modifier: eb.getModifiers()){
+						modifier.applyBonus(pc, classEffect.getRank());
+					}
+					
+				}
+			
+			if (SkillsBase.runeSkillsCache.containsKey(pc.getBaseClassID())){
+				for (int skillToken : SkillsBase.runeSkillsCache.get(pc.getBaseClassID()).keySet()){
+					float amount = SkillsBase.runeSkillsCache.get(pc.getBaseClassID()).get(skillToken);
+					
+					SkillsBase sb = SkillsBase.tokenCache.get(skillToken);
+					
+					if (sb == null)
+						continue;
+					if (this.skillBonuses.containsKey(sb.sourceType) == false)
+						this.skillBonuses.put(sb.sourceType, amount);
+					else
+						this.skillBonuses.put(sb.sourceType, this.skillBonuses.get(sb.sourceType) + amount);
+				}
+			}
+			
+		}
+		
+		//calculate promotionClass Effects
+		if (pc.getPromotionClass() != null){
+			if (pc.getPromotionClass().getEffectsList() != null)
+				for (MobBaseEffects promoEffect: pc.getPromotionClass().getEffectsList()){
+					EffectsBase eb = PowersManager.getEffectByToken(promoEffect.getToken());
+					
+					if (eb == null)
+						continue;
+					if (pc.getLevel() < promoEffect.getReqLvl())
+						continue;
+					for (AbstractEffectModifier modifier: eb.getModifiers()){
+						modifier.applyBonus(pc, promoEffect.getRank());
+					}
+					
+				}
+			
+			if (SkillsBase.runeSkillsCache.containsKey(pc.getPromotionClassID())){
+				for (int skillToken : SkillsBase.runeSkillsCache.get(pc.getPromotionClassID()).keySet()){
+					float amount = SkillsBase.runeSkillsCache.get(pc.getPromotionClassID()).get(skillToken);
+					
+					SkillsBase sb = SkillsBase.tokenCache.get(skillToken);
+					
+					if (sb == null)
+						continue;
+					if (this.skillBonuses.containsKey(sb.sourceType) == false)
+						this.skillBonuses.put(sb.sourceType, amount);
+					else
+						this.skillBonuses.put(sb.sourceType, this.skillBonuses.get(sb.sourceType) + amount);
+
+				}
+			}
+			
+		}
+		
+		for(CharacterRune runes : pc.getRunes()){
+			RuneBase characterRune = RuneBase.getRuneBase(runes.getRuneBaseID());
+			
+			if (characterRune.getEffectsList() != null)
+				for (MobBaseEffects runeEffect: characterRune.getEffectsList()){
+					EffectsBase eb = PowersManager.getEffectByToken(runeEffect.getToken());
+					
+					if (eb == null)
+						continue;
+					if (pc.getLevel() < runeEffect.getReqLvl())
+						continue;
+					for (AbstractEffectModifier modifier: eb.getModifiers()){
+						modifier.applyBonus(pc, runeEffect.getRank());
+					}
+					
+				}
+			
+			if (SkillsBase.runeSkillsCache.containsKey(runes.getRuneBaseID())){
+				for (int skillToken : SkillsBase.runeSkillsCache.get(runes.getRuneBaseID()).keySet()){
+					float amount = SkillsBase.runeSkillsCache.get(runes.getRuneBaseID()).get(skillToken);
+					
+					SkillsBase sb = SkillsBase.tokenCache.get(skillToken);
+					
+					if (sb == null)
+						continue;
+					if (this.skillBonuses.containsKey(sb.sourceType) == false)
+						this.skillBonuses.put(sb.sourceType, amount);
+					else
+						this.skillBonuses.put(sb.sourceType, this.skillBonuses.get(sb.sourceType) + amount);
+
+				}
+			}
+			
+		}
+
+		//Update seeInvis if needed
+		
+			float seeInvis = this.getFloat(ModType.SeeInvisible, SourceType.None);
+			if (pc.getSeeInvis() < seeInvis)
+				pc.setSeeInvis((short)seeInvis);
+		
+	}
+	
+
+	public void grantEffect(RuneBaseEffect rbe) {
+	}
+	
+	
+	public void setFloat(AbstractEffectModifier mod, float val) {
+		if (val != 0)
+			this.bonusFloats.put(mod, val);
+		else 
+			this.bonusFloats.remove(mod);
+	}
+
+	public void setString(AbstractEffectModifier mod, String val) {
+		if (!val.isEmpty())
+			this.bonusStrings.put(mod, val);
+		else 
+			this.bonusStrings.remove(mod);
+	}
+
+	public void setList(ModType mod, HashSet<SourceType> val) {
+		if (!val.equals(null))
+			this.bonusLists.put(mod, val);
+		else 
+			this.bonusLists.remove(mod);
+	}
+
+
+	
+
+	public void addFloat(AbstractEffectModifier mod, Float val) {
+		if (this.bonusFloats.containsKey(mod) == false)
+			this.bonusFloats.put(mod, val);
+		else
+			this.bonusFloats.put(mod, this.bonusFloats.get(mod) + val);
+	}
+	
+	public void multFloat(AbstractEffectModifier mod, Float val) {
+		if (this.bonusFloats.containsKey(mod) == false)
+			this.bonusFloats.put(mod, val);
+		else
+			this.bonusFloats.put(mod,this.bonusFloats.get(mod) + (val * ( this.bonusFloats.get(mod) + val)));
+	}
+	
+	public void multRegen(ModType mod, Float val) {
+			this.regens.put(mod,this.regens.get(mod) + (this.regens.get(mod) * val));
+	}
+
+	
+	public boolean getBool(ModType modType, SourceType sourceType) {
+		
+		if (this.bonusBools.containsKey(modType) == false)
+			return false;
+		
+		if (this.bonusBools.get(modType).containsKey(sourceType) == false)
+			return false;
+		
+		return this.bonusBools.get(modType).get(sourceType);
+	
+	}
+	
+	public float getSkillBonus(SourceType sourceType) {
+		
+		if (this.skillBonuses.containsKey(sourceType) == false)
+			return 0;
+	return this.skillBonuses.get(sourceType);
+	}
+
+	
+	public float getFloat(ModType modType, SourceType sourceType) {
+		float amount = 0;
+		for (AbstractEffectModifier mod : this.bonusFloats.keySet()){
+				if (mod.getPercentMod() != 0)
+					continue;
+			if (mod.modType.equals(modType) == false || mod.sourceType.equals(sourceType) == false)
+				continue;
+			
+			if (this.bonusFloats.get(mod) == null)
+				continue;
+			
+			amount += this.bonusFloats.get(mod);
+		}
+		return amount;
+	}
+	
+	public float getFloatPercentPositive(ModType modType, SourceType sourceType) {
+		float amount = 0;
+		for (AbstractEffectModifier mod : this.bonusFloats.keySet()){
+		
+				if (mod.getPercentMod() == 0 && !modType.equals(ModType.AdjustAboveDmgCap))
+					continue;
+				
+			
+			if (mod.modType.equals(modType) == false || mod.sourceType.equals(sourceType) == false)
+				continue;
+			
+			
+			if (this.bonusFloats.get(mod) == null)
+				continue;
+			
+			if (this.bonusFloats.get(mod) < 0)
+				continue;
+			amount += this.bonusFloats.get(mod);
+		}
+		
+		return amount;
+	}
+	
+	public float getFloatPercentAll(ModType modType, SourceType sourceType) {
+		float amount = 0;
+		for (AbstractEffectModifier mod : this.bonusFloats.keySet()){
+		
+				if (mod.getPercentMod() == 0 && !modType.equals(ModType.AdjustAboveDmgCap))
+					continue;
+				
+			
+			if (mod.modType.equals(modType) == false || mod.sourceType.equals(sourceType) == false)
+				continue;
+			
+			if (this.bonusFloats.get(mod) == null)
+				continue;
+			
+			amount += this.bonusFloats.get(mod);
+		}
+		
+		return amount;
+	}
+	
+	public float getRegen(ModType modType) {
+		return this.regens.get(modType);
+	}
+	
+	
+	
+	public float getFloatPercentNullZero(ModType modType, SourceType sourceType) {
+		float amount = 0;
+		for (AbstractEffectModifier mod : this.bonusFloats.keySet()){
+		
+				if (mod.getPercentMod() == 0)
+					continue;
+			if (mod.modType.equals(modType) == false || mod.sourceType.equals(sourceType) == false)
+				continue;
+			
+			if (this.bonusFloats.get(mod) == null)
+				continue;
+			
+			amount += this.bonusFloats.get(mod);
+		}
+		return amount;
+	}
+	
+	public float getFloatPercentNegative(ModType modType, SourceType sourceType) {
+		float amount = 0;
+		for (AbstractEffectModifier mod : this.bonusFloats.keySet()){
+		
+				if (mod.getPercentMod() == 0)
+					continue;
+			if (mod.modType.equals(modType) == false || mod.sourceType.equals(sourceType) == false)
+				continue;
+			
+			if (this.bonusFloats.get(mod) == null)
+				continue;
+			
+			if (this.bonusFloats.get(mod) > 0)
+				continue;
+			
+			
+			amount += this.bonusFloats.get(mod);
+		}
+		return amount;
+	}
+
+
+	
+	
+
+	public HashSet<SourceType> getList(ModType modType) {
+		if (this.bonusLists.containsKey(modType))
+			return this.bonusLists.get(modType);
+		else
+			return null;
+	}
+	
+	public ConcurrentHashMap<AbstractEffectModifier, DamageShield> getDamageShields() {
+		return this.bonusDamageShields;
+	}
+
+	public void addDamageShield(AbstractEffectModifier mod , DamageShield ds) {
+		this.bonusDamageShields.put(mod, ds);
+	}
+	
+	
+
+	public void updateIfHigher(AbstractEffectModifier mod, Float val) {
+		
+		if (this.bonusFloats.containsKey(mod) == false){
+			this.bonusFloats.put(mod, val);
+			return;
+		}
+		float oldVal = this.getFloat(mod.modType, mod.sourceType);
+		
+		if (oldVal > val)
+			return;
+		
+		this.bonusFloats.put(mod,val);
+		
+	}
+
+
+	//Read maps
+
+	public void printBonusesToClient(PlayerCharacter pc) {
+		
+		
+		
+		for (ModType modType: this.bonusBools.keySet()){
+			for (SourceType sourceType: this.bonusBools.get(modType).keySet()){
+				ChatManager.chatSystemInfo(pc, modType.name() + "-" + sourceType.name() + " = " + this.bonusBools.get(modType).get(sourceType));
+			}	
+		}
+		
+		for (ModType modType : ModType.values()){
+			
+			if (modType.equals(ModType.StaminaRecoverRate) || modType.equals(ModType.HealthRecoverRate) || modType.equals(ModType.ManaRecoverRate))
+				ChatManager.chatSystemInfo(pc, modType.name()  + " = " + this.getRegen(modType));
+			else
+			for (SourceType sourceType : SourceType.values()){
+				float amount = this.getFloat(modType, sourceType);
+				float percentAmount = this.getFloatPercentPositive(modType, sourceType);
+				float percentAmountNegative = this.getFloatPercentNegative(modType, sourceType);
+				
+				if (amount != 0)
+					ChatManager.chatSystemInfo(pc, modType.name() + "-" + (sourceType.equals(SourceType.None) == false ? sourceType.name() : "") + " = " + amount);	
+				
+				if (percentAmount != 0)
+					ChatManager.chatSystemInfo(pc, "Percent : " + modType.name() + "-" + (sourceType.equals(SourceType.None) == false ? sourceType.name() : "") + " = " + percentAmount);	
+
+				if (percentAmountNegative != 0)
+					ChatManager.chatSystemInfo(pc, "Negative Percent : " + modType.name() + "-" + (sourceType.equals(SourceType.None) == false ? sourceType.name() : "") + " = " + percentAmountNegative);	
+
+			}
+		}
+
+	}
+	
+	
+	public void setBool(ModType modType, SourceType sourceType , boolean val) {
+		if (val == true){
+			
+			if (this.bonusBools.get(modType) == null){
+				HashMap<SourceType, Boolean> sourceMap = new HashMap<>();
+				this.bonusBools.put(modType, sourceMap);
+			}
+				
+				this.bonusBools.get(modType).put(sourceType, val);
+				return;
+		}
+		
+		if (this.bonusBools.containsKey(modType))
+		this.bonusBools.get(modType).remove(sourceType);
+	}
+	
+}
diff --git a/src/engine/objects/PlayerCharacter.java b/src/engine/objects/PlayerCharacter.java
new file mode 100644
index 00000000..ef6f623a
--- /dev/null
+++ b/src/engine/objects/PlayerCharacter.java
@@ -0,0 +1,5659 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.objects;
+
+import engine.Enum;
+import engine.Enum.*;
+import engine.InterestManagement.HeightMap;
+import engine.InterestManagement.InterestManager;
+import engine.InterestManagement.RealmMap;
+import engine.InterestManagement.WorldGrid;
+import engine.ai.MobileFSM.STATE;
+import engine.db.archive.CharacterRecord;
+import engine.db.archive.DataWarehouse;
+import engine.db.archive.PvpRecord;
+import engine.exception.MsgSendException;
+import engine.exception.SerializationException;
+import engine.gameManager.*;
+import engine.job.JobContainer;
+import engine.job.JobScheduler;
+import engine.jobs.DeferredPowerJob;
+import engine.jobs.FinishSpireEffectJob;
+import engine.jobs.NoTimeJob;
+import engine.math.Bounds;
+import engine.math.FastMath;
+import engine.math.Vector3fImmutable;
+import engine.net.ByteBufferWriter;
+import engine.net.Dispatch;
+import engine.net.DispatchMessage;
+import engine.net.client.ClientConnection;
+import engine.net.client.msg.*;
+import engine.net.client.msg.login.CommitNewCharacterMsg;
+import engine.powers.EffectsBase;
+import engine.server.MBServerStatics;
+import engine.server.login.LoginServer;
+import engine.server.login.LoginServerMsgHandler;
+import engine.util.MiscUtils;
+import org.joda.time.DateTime;
+import org.pmw.tinylog.Logger;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.*;
+import java.util.Map.Entry;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.locks.ReadWriteLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+
+public class PlayerCharacter extends AbstractCharacter {
+
+	//This object is to be used as the lock in a synchronized statement
+	//any time the name of a PlayerCharacter needs to be set or
+	//changed.  It ensures the uniqueness check and subsequent
+	//database update can happen exclusively.
+	private static final Object FirstNameLock = new Object();
+
+	private final Account account;
+	private final Race race;
+	private BaseClass baseClass;
+	private PromotionClass promotionClass;
+	protected ArrayList<CharacterRune> runes;
+
+	private final byte skinColor;
+	private final byte hairColor;
+	private final byte beardColor;
+
+	private final byte hairStyle;
+	private final byte beardStyle;
+	private long channelMute = 0; // none muted.
+
+	//All Guild information should be held here
+	private final AtomicInteger guildStatus;
+
+	private final AtomicInteger strMod = new AtomicInteger(); // Stat Modifiers
+	private final AtomicInteger dexMod = new AtomicInteger();
+	private final AtomicInteger conMod = new AtomicInteger();
+	private final AtomicInteger intMod = new AtomicInteger();
+	private final AtomicInteger spiMod = new AtomicInteger();
+	private final ReadWriteLock teleportLock = new ReentrantReadWriteLock(true);
+	public final ReadWriteLock respawnLock = new ReentrantReadWriteLock(true);
+
+	private ConcurrentHashMap<Integer, String> ignoredPlayerIDs = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW);
+
+	public boolean notDeleted; // <-Use this for deleting character
+
+	// ===========================================
+	// Variables NOT to put into the database!!!! (session only)
+	// ===========================================
+	public short statStrMax; // Max Base Stats
+	public short statDexMax;
+	public short statConMax;
+	public short statIntMax;
+	public short statSpiMax;
+	public short statStrMin; // Min Base Stats
+	public short statDexMin;
+	public short statConMin;
+	public short statIntMin;
+	public short statSpiMin;
+
+	// Current Stats before Equip and Effect
+	// Modifiers
+	public short statStrBase;
+	public short statDexBase;
+	public short statConBase;
+	public short statIntBase;
+	public short statSpiBase;
+	public short trainedStatPoints = 0;
+
+	private boolean lfGroup = false;
+	private boolean lfGuild = false;
+	private boolean recruiting = false;
+	private MovementState movementState = MovementState.IDLE;
+	private MovementState lastMovementState = MovementState.IDLE;
+
+	private int overFlowEXP = 0;
+
+	private int lastGuildToInvite;
+	private int lastGroupToInvite;
+	private boolean follow = false;
+	private final HashMap<Integer, Long> summoners = new HashMap<>();
+	private final HashSet<AbstractWorldObject> loadedObjects = new HashSet<>();
+	private HashSet<AbstractWorldObject> loadedStaticObjects = new HashSet<>();
+	private Vector3fImmutable lastStaticLoc = new Vector3fImmutable(0.0f, 0.0f, 0.0f);
+	private final ConcurrentHashMap<Integer, LinkedList<Long>> chatChanFloodList = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW);
+	private final ConcurrentHashMap<Integer, Long> killMap = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW);
+	private GameObjectType lastTargetType;
+	private int lastTargetID;
+	private int hidden = 0; // current rank of hide/sneak/invis
+	private int seeInvis = 0; // current rank of see invis
+	private float speedMod;
+	private float raceRunMod;
+	private boolean teleportMode = false; // Teleport on MoveToPoint
+	private final AtomicInteger trainsAvailable = new AtomicInteger(0); // num skill trains not used
+	private float dexPenalty;
+	private long lastPlayerAttackTime = 0;
+	private long lastMobAttackTime = 0;
+	private long lastUsedPowerTime = 0;
+	private long lastTargetOfUsedPowerTime = 0;
+	private long lastUpdateTime = System.currentTimeMillis();
+	private long lastStamUpdateTime = System.currentTimeMillis();
+	private boolean safeZone = false;
+	public boolean isCSR = false;
+	private int bindBuildingID;
+	private int lastContract;
+	private boolean noTeleScreen = false;
+
+	private int lastRealmID = -2;
+	private int subRaceID = 0;
+
+	private boolean hasAnniversery = false;
+
+	//TODO Public fields break OO!!!
+	public boolean newChar;
+
+	private DeferredPowerJob weaponPower;
+	private NPC lastNPCDialog;
+
+	private Mob pet;
+	public final ArrayList<Mob> necroPets = new ArrayList<>();
+	//Used for skill/Power calculation optimization
+	private CharacterTitle title = CharacterTitle.NONE;
+	private boolean asciiLastName = true;
+
+	private int spamCount = 0;
+
+	private boolean initialized = false;
+	
+	private boolean enteredWorld = false;
+
+	private boolean canBreathe = true;
+	/*
+    DataWarehouse based kill/death tracking.
+    These sets contain the last 10 UUID's
+	 */
+
+	public LinkedList<Integer> pvpKills;
+	public LinkedList<Integer> pvpDeaths;
+	private String hash;
+	public int lastBuildingAccessed = 0;
+
+	private ArrayList<GuildHistory> guildHistory = new ArrayList<>();
+
+	public double timeLoggedIn = 0;
+
+	public boolean RUN_MAGICTREK = true;
+	
+	public int spellsCasted = 0;
+	public int pingCount = 0;
+	public long startPing = 0;
+	public double ping = 0;
+	private boolean wasTripped75 = false;
+	private boolean wasTripped50 = false;
+	private boolean wasTripped25 = false;
+	
+	private float characterHeight = 0;
+	public float centerHeight = 0;
+	private boolean lastSwimming = false;
+	
+	private boolean isTeleporting = false;
+	public float landingAltitude = 0;
+	
+	public int bindBuilding = 0;
+	public FriendStatus friendStatus = FriendStatus.Available;
+
+	/**
+	 * No Id Constructor
+	 */
+	public PlayerCharacter( String firstName, String lastName, short strMod, short dexMod, short conMod, short intMod,
+			short spiMod, Guild guild, byte runningTrains, Account account, Race race, BaseClass baseClass, byte skinColor, byte hairColor,
+			byte beardColor, byte hairStyle, byte beardStyle) {
+		super(firstName, lastName, (short) 1, (short) 1, (short) 1, (short) 1, (short) 1, (short) 1, 0,
+				Vector3fImmutable.ZERO, Vector3fImmutable.ZERO, Vector3fImmutable.ZERO,
+				guild, runningTrains);
+
+		this.runes = new ArrayList<>();
+		this.account = account;
+		this.notDeleted = true;
+		this.race = race;
+		this.baseClass = baseClass;
+		this.skinColor = skinColor;
+		this.hairColor = hairColor;
+		this.beardColor = beardColor;
+		this.hairStyle = hairStyle;
+		this.beardStyle = beardStyle;
+		this.lfGroup = false;
+		this.lfGuild = false;
+
+		this.strMod.set(strMod);
+		this.dexMod.set(dexMod);
+		this.conMod.set(conMod);
+		this.intMod.set(intMod);
+		this.spiMod.set(spiMod);
+
+		this.guildStatus = new AtomicInteger(0);
+		this.bindBuildingID = -1;
+	}
+
+	/**
+	 * ResultSet Constructor
+	 */
+	public PlayerCharacter(ResultSet rs) throws SQLException {
+		super(rs, true);
+
+		this.runes = DbManager.CharacterRuneQueries.GET_RUNES_FOR_CHARACTER(this.getObjectUUID());
+		int accountID = rs.getInt("parent");
+		this.account = DbManager.AccountQueries.GET_ACCOUNT(accountID);
+		this.gridObjectType = GridObjectType.DYNAMIC;
+
+		this.notDeleted = rs.getBoolean("char_isActive");
+
+		int raceID = rs.getInt("char_raceID");
+		this.race = Race.getRace(raceID);
+
+		int baseClassID = rs.getInt("char_baseClassID");
+		this.baseClass = DbManager.BaseClassQueries.GET_BASE_CLASS(baseClassID);
+
+		int promotionClassID = rs.getInt("char_promotionClassID");
+		this.promotionClass = DbManager.PromotionQueries.GET_PROMOTION_CLASS(promotionClassID);
+
+		this.skinColor = rs.getByte("char_skinColor");
+		this.hairColor = rs.getByte("char_hairColor");
+		this.beardColor = rs.getByte("char_beardColor");
+		this.hairStyle = rs.getByte("char_hairStyle");
+		this.beardStyle = rs.getByte("char_beardStyle");
+
+		this.lfGroup = false;
+		this.lfGuild = false;
+
+		//TODO Unify game object with database after DB overhaul
+		this.guildStatus = new AtomicInteger(0);
+
+		Guild guild = Guild.getGuild(this.getGuildUUID());
+		if (guild != null && guild.isGuildLeader(this.getObjectUUID()))
+			this.setGuildLeader(true);
+		else
+			this.setGuildLeader(false);
+
+		this.hasAnniversery = rs.getBoolean("anniversery");
+
+		this.setInnerCouncil(rs.getBoolean("guild_isInnerCouncil"));
+		this.setFullMember(rs.getBoolean("guild_isFullMember"));
+		this.setTaxCollector(rs.getBoolean("guild_isTaxCollector"));
+		this.setRecruiter(rs.getBoolean("guild_isRecruiter"));
+		this.setGuildTitle(rs.getInt("guild_title"));
+
+		if (this.account != null)
+			this.ignoredPlayerIDs = DbManager.PlayerCharacterQueries.GET_IGNORE_LIST(this.account.getObjectUUID(), false);
+
+		this.strMod.set(rs.getShort("char_strMod"));
+		this.dexMod.set(rs.getShort("char_dexMod"));
+		this.conMod.set(rs.getShort("char_conMod"));
+		this.intMod.set(rs.getShort("char_intMod"));
+		this.spiMod.set(rs.getShort("char_spiMod"));
+
+		this.bindBuildingID = rs.getInt("char_bindBuilding");
+
+		this.hash = rs.getString("hash");
+
+
+		// For debugging skills
+		// CharacterSkill.printSkills(this);
+	}
+
+	public void setGuildTitle(int value) {
+		if (GuildStatusController.getTitle(this.guildStatus) == value)
+			return;
+		DbManager.PlayerCharacterQueries.SET_GUILD_TITLE(this, value);
+		GuildStatusController.setTitle(guildStatus, value);
+	}
+
+	public void setFullMember(boolean value) {
+		if (GuildStatusController.isFullMember(this.guildStatus) == value)
+			return;
+		DbManager.PlayerCharacterQueries.SET_FULL_MEMBER(this, value);
+		GuildStatusController.setFullMember(guildStatus, value);
+	}
+
+	public void setRecruiter(boolean value) {
+		if (GuildStatusController.isRecruiter(this.guildStatus) == value)
+			return;
+		DbManager.PlayerCharacterQueries.SET_RECRUITER(this, value);
+		GuildStatusController.setRecruiter(guildStatus, value);
+	}
+
+	public void setTaxCollector(boolean value) {
+		if (GuildStatusController.isTaxCollector(this.guildStatus) == value)
+			return;
+		DbManager.PlayerCharacterQueries.SET_TAX_COLLECTOR(this, value);
+		GuildStatusController.setTaxCollector(guildStatus, value);
+	}
+
+	public void setInnerCouncil(boolean value) {
+		
+		// dont update if its the same.
+		if (GuildStatusController.isInnerCouncil(this.guildStatus) == value)
+			return;
+		
+		DbManager.PlayerCharacterQueries.SET_INNERCOUNCIL(this, value);
+		GuildStatusController.setInnerCouncil(guildStatus, value);
+	}
+
+	public void setGuildLeader(boolean value) {
+		if (GuildStatusController.isGuildLeader(this.guildStatus) == value)
+			return;
+		
+		GuildStatusController.setGuildLeader(guildStatus, value);
+		if (value == true){
+			this.setInnerCouncil(true);
+			this.setFullMember(true);
+		}
+	}
+
+	//END -> Guild Status Interface
+	public void resetGuildStatuses() {
+		this.setInnerCouncil(false);
+		this.setFullMember(false);
+		this.setGuildTitle(0);
+		this.setTaxCollector(false);
+		this.setRecruiter(false);
+		this.setGuildLeader(false);
+	}
+
+	/*
+	 * Getters
+	 */
+	public byte getHairStyle() {
+		return hairStyle;
+	}
+
+	public byte getBeardStyle() {
+		return beardStyle;
+	}
+
+	public void setWeaponPower(DeferredPowerJob value) {
+		this.weaponPower = value;
+	}
+
+	public DeferredPowerJob getWeaponPower() {
+		return this.weaponPower;
+	}
+
+	public void setSafeZone(boolean value) {
+		this.safeZone = value;
+	}
+
+	public boolean inSafeZone() {
+		return this.safeZone;
+	}
+
+	public boolean isInSafeZone() {
+
+		Zone zone = ZoneManager.findSmallestZone(this.getLoc());
+
+		if (zone != null){
+			return zone.getSafeZone() == (byte) 1;
+		}
+
+		return false;
+		//return this.safeZone;
+	}
+
+	/**
+	 * @return the account
+	 */
+	public Account getAccount() {
+		return account;
+	}
+
+	public void deactivateCharacter() {
+		this.notDeleted = false;
+		DbManager.PlayerCharacterQueries.SET_DELETED(this);
+		DbManager.removeFromCache(this);
+	}
+
+	public void activateCharacter() {
+		this.notDeleted = true;
+		DbManager.PlayerCharacterQueries.SET_DELETED(this);
+	}
+
+	public boolean isDeleted() {
+		return !this.notDeleted;
+	}
+
+	public ArrayList<CharacterRune> getRunes() {
+		return this.runes;
+	}
+
+	public CharacterRune getRune(int runeID) {
+		if (this.runes == null)
+			return null;
+		for (CharacterRune cr : this.runes) {
+			if (cr.getRuneBase() != null && cr.getRuneBase().getObjectUUID() == runeID)
+				return cr;
+		}
+		return null;
+	}
+
+	public boolean addRune(CharacterRune value) {
+		if (this.runes.size() > 12) // Max Runes
+			return false;
+		if (this.runes.indexOf(value) != -1) // Already contains rune
+			return false;
+		this.runes.add(value);
+		return true;
+	}
+
+	public boolean removeRune(CharacterRune value) {
+		int index = this.runes.indexOf(value);
+		if (index == -1)
+			return false;
+		this.runes.remove(index);
+		return true;
+	}
+
+	public CharacterRune removeRune(int runeID) {
+		Iterator<CharacterRune> it = this.runes.iterator();
+		while (it.hasNext()) {
+			CharacterRune cr = it.next();
+			if (cr != null) {
+				RuneBase rb = cr.getRuneBase();
+				if (rb != null)
+					if (runeID == rb.getObjectUUID()) {
+						it.remove();
+						DbManager.CharacterRuneQueries.DELETE_CHARACTER_RUNE(cr);
+						return cr;
+					}
+			}
+		}
+		return null;
+	}
+
+	/**
+	 * @ Kill this Character
+	 */
+	@Override
+	public void killCharacter(AbstractCharacter attacker) {
+
+		killCleanup();
+
+		// *** Mobs have a separate combat path?  Crazy shit!
+		// *** Mobs don't get Experience for killing players. everything else is done in killCleanup();
+
+		if (attacker.getObjectType().equals(GameObjectType.PlayerCharacter) == false){
+
+			Zone zone = ZoneManager.findSmallestZone(this.getLoc());
+
+			//DeathShroud
+
+			if (zone.getSafeZone() == 0)
+				PowersManager.applyPower(this, this, Vector3fImmutable.ZERO, 1672601862, 40, false);
+
+			//enable this to give players deathshroud if mobs kill player.
+
+			//        	  Zone zone = ZoneManager.findSmallestZone(this.getLoc());
+			//        	if (zone.getSafeZone() == 0)
+			//                PowersManager.applyPower(this, this, Vector3fImmutable.ZERO, 1672601862, 40, false);
+			return;
+		}
+
+
+		// Death to other player.
+		// TODO Send PvP and guild/nation message
+		PlayerCharacter att = (PlayerCharacter) attacker;
+		String message = this.getFirstName();
+		if (this.guild != null && (!(this.guild.getName().equals("Errant"))))
+			message += " of " + this.guild.getName();
+		message += " was killed by " + att.getFirstName();
+		if (att.guild != null && (!(att.guild.getName().equals("Errant"))))
+			message += " of " + att.guild.getName();
+		message += "!";
+
+
+		//see if we shold grant xp to attacker
+		boolean doPVPEXP = false;
+		long lastKill = att.getLastKillOfTarget(this.getObjectUUID());
+		if ((System.currentTimeMillis() - lastKill) > MBServerStatics.PLAYER_KILL_XP_TIMER)
+			if (attacker.getLevel() > 39 && this.getLevel() > 39) {
+				Guild aN = null;
+				Guild tN = null;
+				if (attacker.getGuild() != null)
+					aN = attacker.getGuild().getNation();
+				if (this.getGuild() != null)
+					tN = this.getGuild().getNation();
+				if (aN == null || tN == null || aN.isErrant() || Guild.sameGuild(aN, tN) || this.isDeathShroud()) {
+					//skip giving xp if same guild or attacker is errant, or target is in death shroud.
+				} else {
+					doPVPEXP = true;
+				}
+			}
+		//apply death shroud to non safeholds.
+		Zone zone = ZoneManager.findSmallestZone(this.getLoc());
+
+		//DeathShroud
+
+		if (zone.getSafeZone() == 0)
+			PowersManager.applyPower(this, this, Vector3fImmutable.ZERO, 1672601862, 40, false);
+
+		if (doPVPEXP){
+			Group g = GroupManager.getGroup((PlayerCharacter) attacker);
+			Experience.doExperience((PlayerCharacter) attacker, this, g);
+		}
+
+		ChatManager.chatPVP(message);
+
+		/*
+            Update kill / death tracking lists
+            Each character on list is unique.  Only once!
+		 */
+
+		PlayerCharacter aggressorCharacter = (PlayerCharacter)attacker;
+
+		boolean containsVictim = true;
+		boolean containsAttacker = true;
+
+		containsVictim = aggressorCharacter.pvpKills.contains(this.getObjectUUID());
+		containsAttacker = aggressorCharacter.pvpKills.contains(this.getObjectUUID());
+
+		// Rorate attacker's kill list
+
+		if ((aggressorCharacter.pvpKills.size() == 10) && containsVictim == false)
+			aggressorCharacter.pvpKills.removeLast();
+
+		if (containsVictim == false)
+			aggressorCharacter.pvpKills.addFirst(this.getObjectUUID());
+
+		// Rotate the poor victim's deathlist
+
+		if ((this.pvpDeaths.size() == 10) && containsAttacker == false)
+			this.pvpDeaths.removeLast();
+
+		if (containsAttacker == false)
+			this.pvpDeaths.addFirst(this.getObjectUUID());
+
+		// DataWarehouse: store pvp event
+
+		PvpRecord pvpRecord = PvpRecord.borrow((PlayerCharacter) attacker, this, this.getLoc(), doPVPEXP);
+		DataWarehouse.pushToWarehouse(pvpRecord);
+
+		// Mark kill time in killmap
+
+		att.updateKillMap(this.getObjectUUID());
+	}
+
+	@Override
+	public void killCharacter(String reason) {
+
+		killCleanup();
+		Zone zone = ZoneManager.findSmallestZone(this.getLoc());
+
+		if (zone.getSafeZone() == 0)
+			PowersManager.applyPower(this, this, Vector3fImmutable.ZERO, 1672601862, 40, false);
+
+		// Send death message if needed
+		if (reason.equals("Water")) {
+
+			TargetedActionMsg targetedActionMsg = new TargetedActionMsg(this, true);
+			Dispatch dispatch = Dispatch.borrow(this, targetedActionMsg);
+			DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.PRIMARY);
+
+			String message = this.getFirstName();
+
+			if (this.guild != null && (!(this.guild.getName().equals("Errant"))))
+				message += " of " + this.guild.getName();
+			else
+				message += this.getLastName();
+			message += " was killed by water!";
+
+			ChatManager.chatPVP(message);
+
+		}
+	}
+
+	private void killCleanup() {
+		this.stopMovement(this.getLoc());
+		
+		this.health.set(-1);
+		//remove pet
+		if (this.pet != null)
+			this.dismissPet();
+		
+		this.dismissNecroPets();
+		// remove flight job.
+		
+		this.setTakeOffTime(0);
+		this.setDesiredAltitude(0);
+		this.altitude = (float) 0;
+		
+	this.getCharItemManager().closeTradeWindow();
+
+		//increment live counter. This is to prevent double kills from casts
+		this.liveCounter++;
+
+		//remove any effects
+		try {
+			this.clearEffects();
+		}catch(Exception e){
+			Logger.error("PlayerCharacter.KillCleanup", e.getMessage());
+		}
+
+		//remove the SIT flag
+		this.setSit(false);
+
+
+
+		// sends a kill message to ensure the Player falls over.
+
+		this.respawnLock.writeLock().lock();
+		
+		try{
+			if (SessionManager.getPlayerCharacterByID(this.getObjectUUID()) == null && !this.enteredWorld){
+				WorldGrid.RemoveWorldObject(this);			
+				this.respawn(false, false,true);
+			}else{
+				TargetedActionMsg killmsg = new TargetedActionMsg(this, true);
+				DispatchMessage.dispatchMsgToInterestArea(this, killmsg, DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, false, false);
+			}
+		}catch(Exception e){
+			Logger.error(e);
+		}finally{
+			this.respawnLock.writeLock().unlock();
+		}
+		
+		// TODO damage equipped items
+		if (this.charItemManager != null)
+			this.charItemManager.damageAllGear();
+
+		// TODO cleanup any timers
+		//recalculate inventory weights
+		if (this.charItemManager != null) {
+			this.charItemManager.endTrade(true);
+			this.charItemManager.calculateWeights();
+			this.charItemManager.updateInventory();
+		}
+
+
+
+
+
+
+	}
+
+
+	public void updateKillMap(int target) {
+		this.killMap.put(target, System.currentTimeMillis());
+	}
+
+	public long getLastKillOfTarget(int target) {
+		if (this.killMap.containsKey(target))
+			return this.killMap.get(target);
+		return 0L;
+	}
+
+	public boolean isDeathShroud() {
+		return this.effects != null && this.effects.containsKey("DeathShroud");
+	}
+
+	public void setSafeMode() {
+		PowersManager.applyPower(this, this, Vector3fImmutable.ZERO, -1661758934, 40, false);
+	}
+
+	public boolean safemodeInvis() {
+
+		if (!this.effects.containsKey("Invisible"))
+			return false;
+
+		Effect eff = this.effects.get("Invisible");
+
+		if (eff == null)
+			return false;
+
+		return eff.getEffectToken() == -1661751254;
+
+	}
+
+	public void respawn(boolean setAlive, boolean enterWorld, boolean makeCorpse) {
+
+		// Recalculate everything
+
+		
+		this.recalculatePlayerStats(true);
+		this.setCombat(false);
+
+		// Set Health to 1/4 max
+
+	
+		
+		Corpse corpse = null;
+		
+		if (makeCorpse){
+			try {
+				corpse = Corpse.makeCorpse(this, enterWorld);
+			} catch (Exception e) {
+				Logger.error( e);
+			}
+			//if we're not making corpse, just purge inventory. used for characters dead while logged out.
+		}
+		
+		if (!setAlive){
+			if (corpse == null && makeCorpse) {
+				Logger.error("Corpse not created.");
+			}
+			else {
+				if(makeCorpse && corpse != null){
+					InterestManager.forceLoad(corpse);
+				}
+			}
+			return;
+		}
+		
+		this.setHealth((float) (healthMax * .25));
+			this.isAlive.set(true);
+
+
+		// Put player in safe mode
+		// Teleport the player to his bind loc
+		// or to a ruin as apporpriate.
+		
+			Building bindBuilding = BuildingManager.getBuildingFromCache(this.getBindBuildingID());
+
+			if (enterWorld) {
+				this.stopMovement(this.getBindLoc());
+			}
+			else if (bindBuilding != null) {
+				if (bindBuilding.getParentZone().equals(ZoneManager.findSmallestZone(this.getLoc())))
+					this.teleport(Ruins.getRandomRuin().getLocation());
+				else
+					this.teleport(this.getBindLoc());
+			} else // no bind building found for player, teleport to ruins.
+					this.teleport(Ruins.getRandomRuin().getLocation());
+
+		this.lastUpdateTime = System.currentTimeMillis();
+		this.lastStamUpdateTime = System.currentTimeMillis();
+		
+		this.update();
+		
+		PowersManager.applyPower(this, this, Vector3fImmutable.ZERO, -1661758934, 40, false);
+		
+		if (corpse == null && makeCorpse) {
+			Logger.error("Corpse not created.");
+		}
+		else {
+			if(makeCorpse && corpse != null){
+				InterestManager.forceLoad(corpse);
+			}
+		}
+	}
+
+	public Effect addCityEffect(String name, EffectsBase eb, int trains, int duration, boolean onEnter, City city) {
+		JobContainer jc = null;
+		if (onEnter) {
+			NoTimeJob ntj = new NoTimeJob(this, name, eb, trains); //infinite timer
+			ntj.setEffectSourceType(city.getObjectType().ordinal());
+			ntj.setEffectSourceID(city.getObjectUUID());
+			jc = new JobContainer(ntj);
+		} else {
+			FinishSpireEffectJob fsej = new FinishSpireEffectJob(this, name, eb, trains);
+			fsej.setEffectSourceType(city.getObjectType().ordinal());
+			fsej.setEffectSourceID(city.getObjectUUID());
+			jc = JobScheduler.getInstance().scheduleJob(fsej, duration);
+		}
+
+		if (this.effects.get(name) != null)
+			this.effects.get(name).cancelJob();
+
+		Effect eff = new Effect(jc, eb, trains);
+		this.effects.put(name, eff);
+		applyAllBonuses();
+		eff.sendSpireEffect(this.getClientConnection(), onEnter);
+		return eff;
+	}
+
+	/**
+	 * @return the race
+	 */
+	public Race getRace() {
+		return race;
+	}
+
+	public int getRaceID() {
+		if (race != null)
+			return race.getRaceRuneID();
+		return 0;
+	}
+
+	/**
+	 * @return the baseClass
+	 */
+	public BaseClass getBaseClass() {
+		return baseClass;
+	}
+
+	public int getBaseClassID() {
+		if (baseClass != null)
+			return baseClass.getObjectUUID();
+		return 0;
+	}
+
+	public int getBaseClassToken() {
+		if (this.baseClass == null)
+			return 0;
+		else
+			return this.baseClass.getToken();
+	}
+
+	public boolean setBaseClass(int value) {
+		BaseClass bs = BaseClass.getBaseClass(value);
+		if (bs != null) {
+			this.baseClass = bs;
+			return true;
+		}
+		return false;
+	}
+
+	@Override
+	public Vector3fImmutable getBindLoc() {
+
+		Vector3fImmutable bindLocation;
+
+		// Return garbage and early exit if this is the login server.
+		// getBindLoc() does a TOL lookup, which also then loads the
+		// city and other garbage not needed on the login server.
+
+		if (ConfigManager.serverType.equals(ServerType.LOGINSERVER))
+			return Vector3fImmutable.ZERO;
+		
+		Building bindBuilding = PlayerCharacter.getUpdatedBindBuilding(this);
+		
+		//handle rented room binds.
+		
+		
+		if (bindBuilding == null){
+			bindLocation = Enum.Ruins.getRandomRuin().getLocation();
+			return bindLocation;
+		}
+			
+
+	
+		bindLocation = BuildingManager.GetBindLocationForBuilding(bindBuilding);
+
+		if (bindLocation == null)
+			bindLocation = Enum.Ruins.getRandomRuin().getLocation();
+
+		return bindLocation;
+
+	}
+
+	public int getInventoryCapacity() {
+		return statStrBase * 3;
+	}
+
+	public int getInventoryCapacityRemaining() {
+		return (this.getInventoryCapacity() - this.charItemManager.getInventoryWeight());
+	}
+
+	/**
+	 * @return the PromotionClass
+	 */
+	public PromotionClass getPromotionClass() {
+		return promotionClass;
+	}
+
+	public int getPromotionClassID() {
+		if (promotionClass != null)
+			return promotionClass.getObjectUUID();
+		return 0;
+	}
+
+	public boolean setPromotionClass(int value) {
+
+		PromotionClass promotionClass = PromotionClass.GetPromtionClassFromCache(value);
+		
+		if (promotionClass == null)
+			return false;
+		
+		
+		if (!DbManager.PlayerCharacterQueries.SET_PROMOTION_CLASS(this, value))
+			return false;
+		
+			this.promotionClass = promotionClass;
+
+			// Warehouse this event
+			CharacterRecord.updatePromotionClass(this);
+			return true;
+	}
+
+	/**
+	 * @return the skinColor
+	 */
+	public byte getSkinColor() {
+		return skinColor;
+	}
+
+	/**
+	 * @return the hairColor
+	 */
+	public byte getHairColor() {
+		return hairColor;
+	}
+
+	/**
+	 * @return the beardColor
+	 */
+	public byte getBeardColor() {
+		return beardColor;
+	}
+
+	/**
+	 * @return the lfGroup
+	 */
+	public boolean isLfGroup() {
+		return lfGroup;
+	}
+
+	public int getIsLfGroupAsInt() {
+		if (lfGroup)
+			return 2;
+		return 1;
+	}
+	
+
+	public final void toggleLFGroup() {
+		this.lfGroup = !this.lfGroup;
+	}
+
+	public final void toggleLFGuild() {
+		this.lfGuild = !this.lfGuild;
+	}
+
+	public final void toggleRecruiting() {
+		this.recruiting = !this.recruiting;
+	}
+
+	public final void setLFGroup(final boolean value) {
+		this.lfGroup = value;
+	}
+
+	public final void setLFGuild(final boolean value) {
+		this.lfGuild = value;
+	}
+
+	public final void setRecruiting(final boolean value) {
+		this.recruiting = value;
+	}
+
+	public final boolean isLFGroup() {
+		return this.lfGroup;
+	}
+
+	public final boolean isLFGuild() {
+		return this.lfGuild;
+	}
+
+	public final boolean isRecruiting() {
+		return this.recruiting;
+	}
+
+	/**
+	 * @return the lfGuild
+	 */
+	public boolean isLfGuild() {
+		return lfGuild;
+	}
+
+	public final int getHeadlightsAsInt() {
+		if (this.lfGroup)
+			if (this.lfGuild)
+				if (this.recruiting)
+					return 14; // LFGroup + LFGuild + Recruiting
+				else
+					return 6; // LFGroup + LFGuild
+			else if (this.recruiting)
+				return 10; // LFGroup + Recruiting
+			else
+				return 2; // LFGroup only
+		else if (this.lfGuild)
+			if (this.recruiting)
+				return 12; // LFGuild + Recruiting
+			else
+				return 4; // LFGuild only
+		else if (this.recruiting)
+			return 8; // Recruiting only
+		else
+			return 0; // No Headlights
+	}
+
+	public int getIsLfGuildAsInt() {
+		if (lfGuild)
+			return 2;
+		return 1;
+	}
+
+	public int getStrMax() {
+		return this.statStrMax;
+	}
+	public int getDexMax() {
+		return this.statDexMax;
+	}
+	public int getConMax() {
+		return this.statConMax;
+	}
+	public int getIntMax() {
+		return this.statIntMax;
+	}
+	public int getSpiMax() {
+		return this.statSpiMax;
+	}
+
+	public void addStr(int amount) {
+
+		boolean worked = false;
+		short newStr = (short) 0;
+		while (!worked) {
+
+			if ((this.unusedStatPoints - this.trainedStatPoints) <= 0)
+				return;
+
+			newStr = (short) (this.statStrBase + amount);
+			short mod = (short) this.strMod.get();
+			short newStrMod = (short) (mod + amount);
+
+			if (newStr > this.statStrMax) {
+				newStrMod += (this.statStrMax - newStr);
+				newStr = this.statStrMax;
+			}
+			worked = this.strMod.compareAndSet(mod, newStrMod);
+		}
+		this.trainedStatPoints++;
+		this.statStrBase = newStr;
+		this.addDatabaseJob("Stats", MBServerStatics.THIRTY_SECONDS);
+		this.applyBonuses();
+		this.calculateSkills();
+	}
+
+	public void addDex(int amount) {
+
+		boolean worked = false;
+		short newDex = (short) 0;
+
+		while (!worked) {
+
+			if ((this.unusedStatPoints - this.trainedStatPoints) <= 0)
+				return;
+
+			newDex = (short) (this.statDexBase + amount);
+			short mod = (short) this.dexMod.get();
+			short newDexMod = (short) (mod + amount);
+
+			if (newDex > this.statDexMax) {
+				newDexMod += (this.statDexMax - newDex);
+				newDex = this.statDexMax;
+			}
+
+			worked = this.dexMod.compareAndSet(mod, newDexMod);
+		}
+		this.trainedStatPoints++;
+		this.statDexBase = newDex;
+		this.addDatabaseJob("Stats", MBServerStatics.THIRTY_SECONDS);
+		this.applyBonuses();
+		this.calculateSkills();
+	}
+
+	public void addCon(int amount) {
+		boolean worked = false;
+		short newCon = (short) 0;
+		while (!worked) {
+
+			if ((this.unusedStatPoints - this.trainedStatPoints) <= 0)
+				return;
+
+			newCon = (short) (this.statConBase + amount);
+			short mod = (short) this.conMod.get();
+			short newConMod = (short) (mod + amount);
+
+			if (newCon > this.statConMax) {
+				newConMod += (this.statConMax - newCon);
+				newCon = this.statConMax;
+			}
+			worked = this.conMod.compareAndSet(mod, newConMod);
+		}
+		this.trainedStatPoints++;
+		this.statConBase = newCon;
+		this.addDatabaseJob("Stats", MBServerStatics.THIRTY_SECONDS);
+		this.applyBonuses();
+		this.calculateSkills();
+	}
+
+	public void addInt(int amount) {
+		boolean worked = false;
+		short newInt = (short) 0;
+		while (!worked) {
+
+			if ((this.unusedStatPoints - this.trainedStatPoints) <= 0)
+				return;
+
+			newInt = (short) (this.statIntBase + amount);
+			short mod = (short) this.intMod.get();
+			short newIntMod = (short) (mod + amount);
+
+			if (newInt > this.statIntMax) {
+				newIntMod += (this.statIntMax - newInt);
+				newInt = this.statIntMax;
+			}
+			worked = this.intMod.compareAndSet(mod, newIntMod);
+		}
+		this.trainedStatPoints++;
+		this.statIntBase = newInt;
+		this.addDatabaseJob("Stats", MBServerStatics.THIRTY_SECONDS);
+		this.applyBonuses();
+		this.calculateSkills();
+	}
+
+	public void addSpi(int amount) {
+		boolean worked = false;
+		short newSpi = (short) 0;
+
+		while (!worked) {
+
+			if ((this.unusedStatPoints - this.trainedStatPoints) <= 0)
+				return;
+
+			newSpi = (short) (this.statSpiBase + amount);
+			short mod = (short) this.spiMod.get();
+			short newSpiMod = (short) (mod + amount);
+
+			if (newSpi > this.statSpiMax) {
+				newSpiMod += (this.statSpiMax - newSpi);
+				newSpi = this.statSpiMax;
+			}
+			worked = this.spiMod.compareAndSet(mod, newSpiMod);
+		}
+		this.trainedStatPoints++;
+		this.statSpiBase = newSpi;
+		this.addDatabaseJob("Stats", MBServerStatics.THIRTY_SECONDS);
+		this.applyBonuses();
+		this.calculateSkills();
+	}
+
+	public boolean refineStr() {
+		boolean worked = false;
+		short newStr = (short) 0;
+
+		while (!worked) {
+
+			newStr = (short) (this.statStrBase - 1);
+			short mod = (short) this.strMod.get();
+
+			if (mod == 0)
+				return false;
+
+			short newStrMod = (short) (mod - 1);
+
+			if (newStr < this.statStrMin)
+				return false;
+
+			if (!canRefineLower(MBServerStatics.STAT_STR_ID))
+				return false;
+
+			worked = this.strMod.compareAndSet(mod, newStrMod);
+		}
+		this.trainedStatPoints--;
+		this.statStrBase = newStr;
+		this.addDatabaseJob("Stats", MBServerStatics.THIRTY_SECONDS);
+		this.applyBonuses();
+		this.calculateSkills();
+		return true;
+	}
+
+	public boolean refineDex() {
+		boolean worked = false;
+		short newDex = (short) 0;
+
+		while (!worked) {
+			newDex = (short) (this.statDexBase - 1);
+			short mod = (short) this.dexMod.get();
+
+			if (mod == 0)
+				return false;
+
+			short newDexMod = (short) (mod - 1);
+
+			if (newDex < this.statDexMin)
+				return false;
+
+			if (!canRefineLower(MBServerStatics.STAT_DEX_ID))
+				return false;
+
+			worked = this.dexMod.compareAndSet(mod, newDexMod);
+		}
+		this.trainedStatPoints--;
+		this.statDexBase = newDex;
+		this.addDatabaseJob("Stats", MBServerStatics.THIRTY_SECONDS);
+		this.applyBonuses();
+		this.calculateSkills();
+		return true;
+	}
+
+	public boolean refineCon() {
+		boolean worked = false;
+		short newCon = (short) 0;
+
+		while (!worked) {
+			newCon = (short) (this.statConBase - 1);
+			short mod = (short) this.conMod.get();
+
+			if (mod == 0)
+				return false;
+
+			short newConMod = (short) (mod - 1);
+
+			if (newCon < this.statConMin)
+				return false;
+
+			if (!canRefineLower(MBServerStatics.STAT_CON_ID))
+				return false;
+
+			worked = this.conMod.compareAndSet(mod, newConMod);
+		}
+		this.trainedStatPoints--;
+		this.statConBase = newCon;
+		this.addDatabaseJob("Stats", MBServerStatics.THIRTY_SECONDS);
+		this.applyBonuses();
+		this.calculateSkills();
+		return true;
+	}
+
+	public boolean refineInt(RefineMsg msg) {
+		boolean worked = false;
+		short newInt = (short) 0;
+
+		while (!worked) {
+			newInt = (short) (this.statIntBase - 1);
+			short mod = (short) this.intMod.get();
+
+			if (mod == 0)
+				return false;
+			short newIntMod = (short) (mod
+					- 1);
+
+			if (newInt < this.statIntMin)
+				return false;
+
+			if (!canRefineLower(MBServerStatics.STAT_INT_ID))
+				return false;
+
+			worked = this.intMod.compareAndSet(mod, newIntMod);
+		}
+		this.trainedStatPoints--;
+		this.statIntBase = newInt;
+		this.addDatabaseJob("Stats", MBServerStatics.THIRTY_SECONDS);
+
+		verifySkillMax(msg);
+
+		this.applyBonuses();
+		this.calculateSkills();
+		return true;
+	}
+
+	public boolean refineSpi() {
+		boolean worked = false;
+		short newSpi = (short) 0;
+		while (!worked) {
+			newSpi = (short) (this.statSpiBase - 1);
+			short mod = (short) this.spiMod.get();
+			if (mod == 0)
+				return false;
+			short newSpiMod = (short) (mod - 1);
+			if (newSpi < this.statSpiMin)
+				return false;
+			if (!canRefineLower(MBServerStatics.STAT_SPI_ID))
+				return false;
+			worked = this.spiMod.compareAndSet(mod, newSpiMod);
+		}
+		this.trainedStatPoints--;
+		this.statSpiBase = newSpi;
+		this.addDatabaseJob("Stats", MBServerStatics.THIRTY_SECONDS);
+		this.applyBonuses();
+		this.calculateSkills();
+		return true;
+	}
+
+	//this verifies stat doesn't fall too low to keep runes applied while refining
+	private boolean canRefineLower(int stat) {
+		for (CharacterRune cr : this.runes) {
+			if (cr != null) {
+				RuneBase rb = cr.getRuneBase();
+				if (rb != null) {
+					ArrayList<RuneBaseAttribute> attrs = rb.getAttrs();
+
+					if (attrs != null)
+						for (RuneBaseAttribute rba : attrs) {
+							int attrID = rba.getAttributeID();
+							int mod = rba.getModValue();
+							if (stat == MBServerStatics.STAT_STR_ID) {
+								if (attrID == MBServerStatics.RUNE_STR_MIN_NEEDED_ATTRIBUTE_ID && ((int) this.statStrBase <= mod))
+									return false;
+							} else if (stat == MBServerStatics.STAT_DEX_ID) {
+								if (attrID == MBServerStatics.RUNE_DEX_MIN_NEEDED_ATTRIBUTE_ID && ((int) this.statDexBase <= mod))
+									return false;
+							} else if (stat == MBServerStatics.STAT_CON_ID) {
+								if (attrID == MBServerStatics.RUNE_CON_MIN_NEEDED_ATTRIBUTE_ID && ((int) this.statConBase <= mod))
+									return false;
+							} else if (stat == MBServerStatics.STAT_INT_ID) {
+								if (attrID == MBServerStatics.RUNE_INT_MIN_NEEDED_ATTRIBUTE_ID && ((int) this.statIntBase <= mod))
+									return false;
+							} else if (stat == MBServerStatics.STAT_SPI_ID)
+								if (attrID == MBServerStatics.RUNE_SPI_MIN_NEEDED_ATTRIBUTE_ID && ((int) this.statSpiBase <= mod))
+									return false;
+						}
+				}
+			}
+		}
+		return true;
+	}
+
+	//checked on refining int to see if skills need refined also.
+	private void verifySkillMax(RefineMsg msg) {
+
+		ConcurrentHashMap<String, CharacterSkill> skills = getSkills();
+
+		//make sure no skills are over the max number of trains
+		int maxTrains = CharacterSkill.getMaxTrains((int) this.statIntBase);
+
+		RefineMsg rm = new RefineMsg(msg.getNpcType(), msg.getNpcID(), 0, 0);
+
+		for (CharacterSkill skill : skills.values()) {
+
+			while (skill.getNumTrains() > maxTrains) {
+				boolean worked = skill.refine(this, false); //refine skill, do not recalculate everything
+				if (worked) {
+					rm.setToken(skill.getToken());
+
+					Dispatch dispatch = Dispatch.borrow(this, rm);
+					DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
+
+				} else {
+					Logger.error("Failed to force refine of skill " + skill.getObjectUUID() + " by character " + this.getObjectUUID());
+					break;
+				}
+			}
+		}
+	}
+
+	public int getClassToken() {
+		if (this.promotionClass != null)
+			return this.promotionClass.getToken();
+		else if (this.baseClass != null)
+			return this.baseClass.getToken();
+		return 0;
+	}
+
+	public int getRaceToken() {
+
+		if (this.race == null)
+			return 0;
+
+		return this.race.getToken();
+	}
+
+	public void setLastTarget(GameObjectType type, int id) {
+		this.lastTargetType = type;
+		this.lastTargetID = id;
+	}
+
+	public GameObjectType getLastTargetType() {
+		return this.lastTargetType;
+	}
+
+	public int getLastTargetID() {
+		return this.lastTargetID;
+	}
+
+	public synchronized int getBindBuildingID() {
+		return this.bindBuildingID;
+	}
+
+	public synchronized void setBindBuildingID(int value) {
+		DbManager.PlayerCharacterQueries.SET_BIND_BUILDING(this, value);
+		this.bindBuildingID = value;
+	}
+	
+	public static Building getUpdatedBindBuilding(PlayerCharacter player){
+		Building returnBuilding = null;
+
+		//update bindBuilding based on Guild or nation TOL;
+
+		if(player.getBindBuildingID() == 0) {
+
+			returnBuilding = PlayerCharacter.getBindBuildingForGuild(player);
+			
+			if (returnBuilding != null)
+				player.setBindBuildingID(returnBuilding.getObjectUUID());
+			return returnBuilding;
+		}
+			returnBuilding = BuildingManager.getBuildingFromCache(player.getBindBuildingID());
+			
+			if (returnBuilding == null){
+				returnBuilding = PlayerCharacter.getBindBuildingForGuild(player);
+				
+				if (returnBuilding != null)
+					player.setBindBuildingID(returnBuilding.getObjectUUID());
+			}	
+			return returnBuilding;
+	}
+	
+	public static Building getBindBuildingForGuild(PlayerCharacter player){
+
+		Building returnBuilding;
+
+		if (player.getGuild() == null || player.getGuild().isErrant())
+			return null;
+
+		if (player.getGuild().getOwnedCity() == null){
+
+		if (player.getGuild().getNation().getOwnedCity() == null)
+			return null;
+
+		if (player.getGuild().getNation().getOwnedCity().getTOL() == null)
+			return null;
+		
+		returnBuilding = player.getGuild().getNation().getOwnedCity().getTOL();
+		player.setBindBuildingID(returnBuilding.getObjectUUID());
+		return  returnBuilding;
+		}
+		
+		if (player.getGuild().getOwnedCity().getTOL() == null)
+			return null;
+		
+		returnBuilding = player.getGuild().getOwnedCity().getTOL();
+		return returnBuilding;
+	}
+
+	public AbstractGameObject getLastTarget() {
+		if (this.lastTargetType == GameObjectType.unknown)
+			return null;
+
+		switch (this.lastTargetType) {
+		// Make sure these only return an object that is
+		// already in the GOM, and doesn't reload from the DB
+		case PlayerCharacter:
+			return DbManager.getFromCache(GameObjectType.PlayerCharacter, this.lastTargetID);
+
+		case Building:
+			return DbManager.getFromCache(GameObjectType.Building, this.lastTargetID);
+
+		case NPC:
+			return NPC.getFromCache(this.lastTargetID);
+
+		case Mob:
+			return Mob.getFromCache(this.lastTargetID);
+
+		case Item:
+			return DbManager.getFromCache(GameObjectType.Item, this.lastTargetID);
+
+		case Corpse:
+			return DbManager.getFromCache(GameObjectType.Corpse, this.lastTargetID);
+
+		default:
+
+			// Ignore exception for MobLoot?  ***Check
+			if (this.lastTargetType != GameObjectType.MobLoot)
+				Logger.error( "getLastTarget() unhandled object type: "
+						+ this.lastTargetType.toString());
+		}
+		return null;
+	}
+
+	public Vector3fImmutable getLastStaticLoc() {
+		return this.lastStaticLoc;
+	}
+
+	public void setLastStaticLoc(Vector3fImmutable value) {
+		this.lastStaticLoc = value;
+	}
+
+	public int getHidden() {
+		return this.hidden;
+	}
+
+	public void setHidden(int value) {
+		this.hidden = value;
+	}
+
+	public int getSeeInvis() {
+		if (this.getDebug(8)) //<-added for see invis debug devcmd
+			return 10000;
+		return this.seeInvis;
+	}
+
+	public void setSeeInvis(int value) {
+		this.seeInvis = value;
+	}
+
+	public long getLastPlayerAttackTime() {
+		return this.lastPlayerAttackTime;
+	}
+
+	public void setLastPlayerAttackTime() {
+		this.lastPlayerAttackTime = System.currentTimeMillis();
+	}
+
+	public void setLastMobAttackTime() {
+		this.lastMobAttackTime = System.currentTimeMillis();
+	}
+
+	public void setLastUsedPowerTime() {
+		this.lastUsedPowerTime = System.currentTimeMillis();
+	}
+
+	public void setLastTargetOfUsedPowerTime() {
+		this.lastTargetOfUsedPowerTime = System.currentTimeMillis();
+	}
+
+	public void setLastNPCDialog(NPC value) {
+		this.lastNPCDialog = value;
+	}
+
+	public NPC getLastNPCDialog() {
+		return this.lastNPCDialog;
+	}
+
+	public void setLastContract(int value) {
+		this.lastContract = value;
+	}
+
+	public void setPet(Mob mob) {
+
+		if (mob == null)
+			return;
+
+		this.pet = mob;
+	}
+
+	public Mob getPet() {
+		return this.pet;
+	}
+
+	public Mob getNecroPet(int i) {
+		return this.necroPets.get(i);
+	}
+
+	
+public static void auditNecroPets(PlayerCharacter player){
+	int removeIndex =0;
+	while(player.necroPets.size() >= 10){
+		
+		
+		if (removeIndex == player.necroPets.size())
+			break;
+	
+		Mob toRemove = player.necroPets.get(removeIndex);
+		
+		if (toRemove == null){
+			removeIndex++;
+			continue;
+		}
+		toRemove.dismissNecroPet(true);
+		player.necroPets.remove(toRemove);
+		removeIndex++;
+		
+		
+	}
+}
+
+public static void resetNecroPets(PlayerCharacter player){
+	for (Mob necroPet: player.necroPets)
+		if (necroPet.isPet())
+			necroPet.setMob();
+}
+	
+	public void spawnNecroPet(Mob mob) {
+		if (mob == null)
+			return;
+		if (mob.getMobBaseID() != 12021 && mob.getMobBaseID() != 12022)
+			return;
+		
+		PlayerCharacter.auditNecroPets(this);
+		PlayerCharacter.resetNecroPets(this);
+	
+		this.necroPets.add(mob);	
+	}
+	
+
+	public void dismissPet() {
+		if (this.pet != null) {
+			this.pet.dismiss();
+			this.pet = null;
+		}
+	}
+	
+public void dismissNecroPets() {
+
+	
+	if (this.necroPets.isEmpty())
+		return;
+
+		for (Mob necroPet: this.necroPets){
+			
+			try{
+				necroPet.dismissNecroPet(true);
+			}catch(Exception e){
+				necroPet.setState(STATE.Disabled);
+				Logger.error(e);
+			}
+				}
+		this.necroPets.clear();
+	}
+
+
+	//called to verify player has correct item equipped for casting.
+	public boolean validEquip(int slot, String type) {
+
+		if (this.charItemManager == null)
+			return false;
+
+		Item item = this.charItemManager.getEquipped(slot);
+
+		if (item == null)
+			return false;
+
+		ItemBase ib = item.getItemBase();
+		if (ib != null) {
+
+			if ((ib.getType().equals(ItemType.WEAPON))
+					&& (ib.getSkillRequired().equals(type) || ib.getMastery().equals(type)))
+				return true;
+
+			return (ib.getType().equals(ItemType.ARMOR))
+					&& (ib.getSkillRequired().equals(type));
+		}
+
+		return false;
+	}
+
+	public short getPCLevel() {
+		short level = (short) Experience.getLevel(this.exp);
+		if (this.promotionClass == null && level >= 10)
+			return (short) 10;
+		
+		if (this.overFlowEXP > 0)
+			return this.level;
+		
+		return level;
+	}
+
+	@Override
+	public float getSpeed() {
+
+		float speed;
+
+		if (this.getAltitude() > 0)
+			if (this.walkMode) {
+				speed = race.getRaceType().getRunSpeed().getFlyWalk();
+			}
+			else {
+				speed = race.getRaceType().getRunSpeed().getFlyRun();
+			}
+		else if (this.lastSwimming == true)
+			speed = MBServerStatics.SWIMSPEED;
+		else
+			if (this.walkMode) {
+				if (this.isCombat())
+					speed = race.getRaceType().getRunSpeed().getWalkCombat();
+				else
+				speed = race.getRaceType().getRunSpeed().getWalkStandard();
+			}
+			else {
+				if (this.isCombat())
+					speed = race.getRaceType().getRunSpeed().getRunCombat();
+				else
+				speed = race.getRaceType().getRunSpeed().getRunStandard();
+			}
+
+		float endSpeed = speed * this.speedMod;
+
+		if (endSpeed > 41 && !this.isCSR)
+			endSpeed = 41;
+
+		return endSpeed;
+	}
+
+	public synchronized void grantXP(int xp) {
+		// Stop players from getting experience past the cap
+		if (this.exp + xp >= Experience.getBaseExperience(MBServerStatics.LEVELCAP))
+			xp = Experience.getBaseExperience(MBServerStatics.LEVELCAP) - this.exp + 1;
+
+		if (xp == 0)
+			xp = 1;
+
+		boolean isNewLevel = false;
+		boolean charReloadRequired = false;
+		int remainingXP = xp;
+		int neededXP = 0;
+
+		// handle players that have not yet promoted.
+		ClientConnection origin = this.getClientConnection();
+
+		//not promoted at level 10, start checking for negative EXP
+		if (this.promotionClass == null && this.getLevel() == 10) {
+
+			if (this.getExp() == Experience.getBaseExperience(11)){
+				if (this.overFlowEXP == 110000)
+					return;
+
+				if (this.overFlowEXP + xp > 110000){
+					remainingXP = 110000 - this.overFlowEXP;
+					this.overFlowEXP = 110000;
+
+
+				}
+				else{
+					this.overFlowEXP += remainingXP;
+				}
+
+				GrantExperienceMsg gem = new GrantExperienceMsg(this, remainingXP);
+				Dispatch dispatch = Dispatch.borrow(this, gem);
+				DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
+
+				this.addDatabaseJob("EXP", MBServerStatics.FIVE_MINUTES);
+				return;
+				//didnt reach level 11 EXP to start overflow, add exp normally till we get here;
+			}else{
+
+				//Player exp begins negative exp, add remaing exp after level 11 to overflow
+				if (this.getExp() + remainingXP >= Experience.getBaseExperience(11)){
+
+					this.overFlowEXP = remainingXP - (Experience.getBaseExperience(11) - this.getExp());
+					this.exp = Experience.getBaseExperience(11);
+
+					GrantExperienceMsg grantExperienceMsg = new GrantExperienceMsg(this, remainingXP);
+					Dispatch dispatch = Dispatch.borrow(this, grantExperienceMsg);
+					DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
+
+					this.addDatabaseJob("EXP", MBServerStatics.FIVE_MINUTES);
+					return;
+
+					//didnt reach negative exp yet, just do normal exp gain.
+				}else{
+					this.exp += remainingXP;
+					GrantExperienceMsg grantExperienceMsg = new GrantExperienceMsg(this, remainingXP);
+					remainingXP = 0;
+					Dispatch dispatch = Dispatch.borrow(this, grantExperienceMsg);
+					DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
+
+					this.addDatabaseJob("EXP", MBServerStatics.FIVE_MINUTES);
+					return;
+				}
+			}
+		}
+
+		if (this.overFlowEXP > 0){
+
+			int nextLevel;
+
+			if (level == 10)
+				nextLevel = 12;
+			else
+				nextLevel = level + 2;
+
+			int nextLevelEXP = Experience.getBaseExperience(nextLevel);
+
+			// if overflow > 0, u have level 11 experience + overflow, but level is still 10 due to just promoting.
+			//Use level + 2 experience for next level.
+			this.overFlowEXP += 1;
+
+			if (this.getExp() + this.overFlowEXP >= nextLevelEXP){
+
+				int expToNextLevel = nextLevelEXP - this.getExp();
+				this.overFlowEXP -= expToNextLevel;
+				this.exp  += expToNextLevel;
+				this.level++;
+				charReloadRequired = true;
+
+				GrantExperienceMsg grantExperienceMsg = new GrantExperienceMsg(this, 1);
+				Dispatch dispatch = Dispatch.borrow(this, grantExperienceMsg);
+				DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
+
+				SetObjectValueMsg upm = new SetObjectValueMsg(this, 9);
+				DispatchMessage.dispatchMsgToInterestArea(this, upm,  DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, false, false);
+				checkGuildStatus();
+				this.addDatabaseJob("EXP", MBServerStatics.FIVE_MINUTES);
+				//  double overflow exp used up, remaining overflow will just add to level + 1.
+			}else if (this.getExp() + this.overFlowEXP >= Experience.getBaseExperience(level + 1)){
+				int nextExperience = Experience.getBaseExperience(level + 1) + this.overFlowEXP;
+				this.exp = nextExperience;
+				this.level ++;
+				charReloadRequired = true;
+				this.overFlowEXP = 0;
+				GrantExperienceMsg grantExperienceMsg = new GrantExperienceMsg(this, 1);
+				Dispatch dispatch = Dispatch.borrow(this, grantExperienceMsg);
+				DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
+				SetObjectValueMsg upm = new SetObjectValueMsg(this, 9);
+				DispatchMessage.dispatchMsgToInterestArea(this, upm,  DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, false, false);
+				checkGuildStatus();
+				this.addDatabaseJob("EXP", MBServerStatics.FIVE_MINUTES);
+			}
+
+		}else{
+			// Hand out each Level one at a time.
+			isNewLevel = Experience.getLevel(exp + remainingXP) > this.getLevel();
+
+			if (isNewLevel) {
+				neededXP = Experience.getBaseExperience(this.getLevel() + 1) - this.exp;
+
+				charReloadRequired = true;
+				this.exp += neededXP;
+				this.level++;
+
+				GrantExperienceMsg grantExperienceMsg = new GrantExperienceMsg(this, neededXP);
+				Dispatch dispatch = Dispatch.borrow(this, grantExperienceMsg);
+				DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
+
+				remainingXP -= neededXP;
+
+				//Send newLevel.
+				SetObjectValueMsg upm = new SetObjectValueMsg(this, 9);
+				DispatchMessage.dispatchMsgToInterestArea(this, upm,  DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, false, false);
+				checkGuildStatus();
+			} else {
+
+				this.exp += remainingXP;
+				GrantExperienceMsg grantExperienceMsg = new GrantExperienceMsg(this, remainingXP);
+				remainingXP = 0;
+				Dispatch dispatch = Dispatch.borrow(this, grantExperienceMsg);
+				DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
+
+				this.addDatabaseJob("EXP", MBServerStatics.FIVE_MINUTES);
+			}
+		}
+		
+		if (charReloadRequired) {
+			this.update();
+			this.incVer();
+			this.recalculate();
+			this.calculateMaxHealthManaStamina();
+			this.setHealth(this.healthMax);
+			this.mana.set(this.manaMax);
+			this.stamina.set(this.staminaMax);
+			//LoadJob.reloadCharacter(this);
+			DbManager.PlayerCharacterQueries.SET_PROPERTY(this, "char_experience", this.exp);
+			//			updateDatabase();
+			DbManager.AccountQueries.INVALIDATE_LOGIN_CACHE(this.getObjectUUID(), "character");
+		}
+	}
+
+	//This checks if a player meets the requirements to be in current guild.
+	public void checkGuildStatus() {
+
+		Guild g = this.guild;
+
+		if (g == null || g.isErrant() || GuildStatusController.isGuildLeader(guildStatus))
+			return;
+
+		//check level
+		int curLevel = (int) getPCLevel();
+		if (curLevel < g.getRepledgeMin() || curLevel >= g.getRepledgeKick()) {
+			//TODO kick from guild
+			g.removePlayer(this,GuildHistoryType.LEAVE);
+			ChatManager.chatGuildInfo(this, "You no longer meet the level requirements for the guild.");
+		}
+	}
+
+	public void calculateSpeedMod() {
+		// get base race speed modifer
+
+
+		//this is retarded. *** Refactor
+		//        if (this.race != null) {
+		//            int ID = this.race.getObjectUUID();
+		//            if (ID == 2004 || ID == 2005)
+		//                this.raceRunMod = 1.21f; // centaur run bonus 22%
+		////            else if (ID == 2017)
+		////                this.raceRunMod = 1.14f; // mino run bonus 15%
+		//            else
+		//                this.raceRunMod = 1;
+		//        } else
+		//            this.raceRunMod = 1;
+
+
+		float bonus = 1f;
+
+		//        // TODO: hardcoded, as didnt have time to introduce DB column to base object
+		//        if (baseClass.getName().equals("Fighter") || baseClass.getName().equals("Rogue"))
+		//            bonus += .05f;
+
+		// get running skill
+		if (this.skills != null) {
+			CharacterSkill running = this.skills.get("Running");
+			if (running != null){
+
+				float runningBonus = (float) (Math.log(Math.round(running.getModifiedAmount())*.01f) / Math.log(2) *.50f);
+				runningBonus = (float) (Math.pow(2, runningBonus) - 1);
+				runningBonus += 1;
+				runningBonus *= .25f;
+				bonus += runningBonus;
+
+			}
+		}
+
+		if (this.bonuses != null)
+			// get rune and effect bonuses
+			bonus += this.bonuses.getFloatPercentNullZero(ModType.Speed, SourceType.None);
+
+		// TODO get equip bonus
+		this.update();
+		this.speedMod = bonus;
+	}
+
+	public ClientConnection getClientConnection() {
+		return SessionManager.getClientConnection(this);
+	}
+
+	/*
+	 * Serializing
+	 */
+	
+	public static void __serializeForClientMsg(PlayerCharacter playerCharacter,ByteBufferWriter writer) throws SerializationException {
+		serializeForClientCommon(playerCharacter,writer, true, false, false, false);
+	}
+
+	public static void serializeForClientMsgLogin(PlayerCharacter playerCharacter,ByteBufferWriter writer) throws SerializationException {
+		serializeForClientCommon(playerCharacter,writer, true, false, false, false);
+	}
+
+	public static void serializeForClientMsgCommit(PlayerCharacter playerCharacter,ByteBufferWriter writer) throws SerializationException {
+		serializeForClientCommon(playerCharacter,writer, true, true, false, false);
+	}
+
+	public static void serializeForClientMsgFull(PlayerCharacter playerCharacter,ByteBufferWriter writer) throws SerializationException {
+		serializeForClientCommon(playerCharacter,writer, false, false, false, false);
+	}
+
+	
+	public static void serializeForClientMsgOtherPlayer(PlayerCharacter playerCharacter,ByteBufferWriter writer) throws SerializationException {
+		serializeForClientCommon(playerCharacter,writer, false, false, true, false);
+	}
+
+	
+	public static void serializePlayerForClientMsgOtherPlayer(PlayerCharacter playerCharacter,ByteBufferWriter writer, boolean hideAsciiLastName) throws SerializationException {
+		serializeForClientCommon(playerCharacter,writer, false, false, true, hideAsciiLastName);
+	}
+
+	// TODO what is a Fresh Char?
+	private static void serializeForClientCommon(PlayerCharacter playerCharacter,ByteBufferWriter writer, boolean loginData, boolean freshChar, boolean otherPlayer, boolean hideAsciiLastName)
+			throws SerializationException {
+
+		/*
+		 * RUNES
+		 */
+		// Handle Applied Runes
+		writer.putInt(0); // Pad
+		writer.putInt(0); // Pad
+
+		// Put number of runes
+		//We need to send all runes to everyone, otherwise playerCharacter will cause major issues
+		if (playerCharacter.promotionClass != null)
+			writer.putInt(playerCharacter.runes.size() + 3);
+		else
+			writer.putInt(playerCharacter.runes.size() + 2);
+
+		// Cant forget that Race and baseClass are technically Runes :0
+		if (playerCharacter.subRaceID != 0){
+			writer.putInt(1); // For Race
+			writer.putInt(0); // Pad
+			writer.putInt(playerCharacter.subRaceID);
+
+			writer.putInt(Enum.GameObjectType.Race.ordinal());
+			writer.putInt(playerCharacter.subRaceID);
+		}else
+			playerCharacter.race.serializeForClientMsg(writer);
+		if (playerCharacter.promotionClass != null) {
+			BaseClass.serializeForClientMsg(playerCharacter.baseClass,writer, 2);
+			PromotionClass.serializeForClientMsg(playerCharacter.promotionClass,writer);
+		} else
+			BaseClass.serializeForClientMsg(playerCharacter.baseClass,writer, 3);
+
+		// Put runes.
+
+		for (CharacterRune rb : playerCharacter.runes) {
+			CharacterRune.serializeForClientMsg(rb,writer);
+		}
+
+		/*
+		 * STATS
+		 */
+		// Number of Stats to follow
+		writer.putInt(5);
+
+		writer.putInt(MBServerStatics.STAT_STR_ID); // Strength ID
+		writer.putInt(freshChar ? 0 : playerCharacter.getStrMod());
+
+		writer.putInt(MBServerStatics.STAT_SPI_ID); // Spirit ID
+		writer.putInt(freshChar ? 0 : playerCharacter.getSpiMod());
+
+		writer.putInt(MBServerStatics.STAT_CON_ID); // Constitution ID
+		writer.putInt(freshChar ? 0 : playerCharacter.getConMod());
+
+		writer.putInt(MBServerStatics.STAT_DEX_ID); // Dexterity ID
+		writer.putInt(freshChar ? 0 : playerCharacter.getDexMod());
+
+		writer.putInt(MBServerStatics.STAT_INT_ID); // Intelligence ID
+		writer.putInt(freshChar ? 0 : playerCharacter.getIntMod());
+
+		// Handle Info
+		playerCharacter.title._serializeFirstName(writer, playerCharacter.firstName);
+		playerCharacter.title._serializeLastName(writer, playerCharacter.lastName, hideAsciiLastName, playerCharacter.asciiLastName);
+
+		// Unknown
+		writer.putInt(0);
+
+		writer.putString(ConfigManager.MB_WORLD_NAME.getValue());
+		writer.putInt(MBServerStatics.worldMapID);
+
+		writer.put((byte) 1); // End Datablock byte
+		writer.putInt(0); // Unsure, Pad?
+		writer.putInt(playerCharacter.getObjectType().ordinal());
+		writer.putInt(playerCharacter.getObjectUUID());
+
+		// Perhaps playerCharacter is loc and the next 3 are Facing dir?
+		writer.putFloat(1); // Unknown
+		writer.putFloat(playerCharacter.race.getRaceType().getScaleHeight()); // Unknown
+		writer.putFloat(1); // Unknown
+		
+		writer.putVector3f(playerCharacter.getLoc());
+		writer.putFloat(playerCharacter.faceDir.getRotation()); // Rotation, direction
+
+		// facing
+
+		// Running trains.
+
+		if (otherPlayer){
+			CharacterSkill runSkill = playerCharacter.skills.get("Running");
+			if (runSkill == null)
+				// Logger.log.log(
+				// LogEventType.WARNING,
+				// "Failed to find the 'Running Skill' when serializing PlayerCharacter '"
+				// + playerCharacter.getCombinedName() + "'");
+				// TODO put int=0 for now.
+				writer.putInt(0);
+			else
+				writer.putInt(runSkill.getNumTrains());
+		}else
+			writer.putInt(0);
+
+
+		ArrayList<Item> equipped = playerCharacter.charItemManager.getEquippedList();
+		
+		writer.putInt(equipped.size());
+		for (Item item: equipped){
+			Item._serializeForClientMsg(item, writer);
+		}
+		writer.putInt(playerCharacter.getRank());
+
+		writer.putInt(playerCharacter.getLevel());
+		if (loginData)
+			writer.putInt(5);
+		else
+			writer.putInt(playerCharacter.getIsSittingAsInt()); // 5
+		writer.putInt(playerCharacter.getIsWalkingAsInt()); // 1
+		writer.putInt(playerCharacter.getIsCombatAsInt()); // 1
+		writer.putInt(playerCharacter.getIsFlightAsInt()); // 2 or 3
+
+		writer.putInt(playerCharacter.getIsLfGroupAsInt()); // 1
+
+		// if (loginData)
+		// writer.putInt(0);
+		// else
+		writer.putInt(playerCharacter.getHeadlightsAsInt());
+
+
+		if (playerCharacter.getRegion() != null && !loginData){
+			Building building = Regions.GetBuildingForRegion(playerCharacter.getRegion());
+			
+			if (building == null){
+				writer.putInt(0);
+				writer.putInt(0);
+			}else{
+				writer.putInt(GameObjectType.Building.ordinal());
+				writer.putInt(building.getObjectUUID());
+			}
+		
+		}
+		else{
+			writer.putInt(0);
+			writer.putInt(0);
+		}
+
+
+		writer.put((byte)0);
+		writer.put((byte)0);
+		writer.put((byte)0);
+		writer.putInt(0);
+		writer.put((byte)0);
+		writer.put((byte)0);
+		writer.put((byte)0);
+		
+//		writer.putInt(0);
+//		writer.putInt(0);
+		writer.putInt(0);
+		writer.putInt(0);
+		writer.putInt(0);
+		writer.putInt(0);
+		
+		if (!playerCharacter.isAlive() && otherPlayer) {
+			writer.putInt(0);
+			writer.putInt(0);
+		}
+		
+
+		
+
+		//TODO FIGURE OUT THE REAL SEARLIZATION FOR NEXT 2 SHORTS?
+		writer.putInt(playerCharacter.skinColor); // Skin Color
+		writer.putFloat(20);
+		writer.put((byte)0); //Unknown
+		
+		//unknown object
+		writer.putInt(0);
+		writer.putInt(0);
+		
+		//unknown type
+		writer.putInt(0);
+		//0x4080 should be the next short here, instead it wraps 0's down their in for loops.. seriously.. who wrote playerCharacter shit.
+		// playerCharacter aint right!
+		// ByteBufferUtils.putString(writer, playerCharacter.guild.getName());
+		// writer.putInt(playerCharacter.getGuild().getUUID());
+		// ByteBufferUtils.putString(writer, playerCharacter.guild.getNation().getName());
+		// writer.putInt(playerCharacter.getGuild().getNation().getUUID());
+		Guild.serializeForClientMsg(playerCharacter.getGuild(),writer, playerCharacter, false);
+
+		//Send Tokens for race/class/promotion (disciplines?)
+		if (playerCharacter.promotionClass != null)
+			writer.putInt(3);
+		else
+			writer.putInt(2);
+		writer.putInt(playerCharacter.race.getToken());
+		writer.putInt(playerCharacter.baseClass.getToken());
+		if (playerCharacter.promotionClass != null)
+			writer.putInt(playerCharacter.promotionClass.getToken());
+		//		writer.putInt(2); // Unknown Counter
+		//		writer.putInt(0x04C1BE88); // Unknown
+		//		writer.putInt(0x0F651512); // Unknown
+
+		writer.putFloat(playerCharacter.altitude); // altitude?
+		writer.putFloat(playerCharacter.altitude); // altitude?
+		writer.put((byte) 0); // End Datablock byte
+
+		writer.putFloat(playerCharacter.healthMax);
+		writer.putFloat(playerCharacter.health.get());
+
+		writer.put((byte) 0); // End Datablock byte
+		//size
+
+
+		if (loginData){
+			writer.putInt(0);
+		}else{
+			int	indexPosition = writer.position();
+			writer.putInt(0); //placeholder for item cnt
+			int total = 0;
+			//	Logger.info("",""+ playerCharacter.getEffects().size());
+			for (Effect eff : playerCharacter.getEffects().values()) {
+				if (eff.getPower() == null && otherPlayer)
+					continue;
+				if ( !eff.serializeForLoad(writer))
+					continue;
+				++total;
+
+			}
+
+			writer.putIntAt(total, indexPosition);
+		}
+
+		if (otherPlayer) {
+			writer.put((byte) 0); // End Datablock Byte
+			return;
+
+		}
+
+		//made up for sendalleffects
+		//writer.putInt(0); // Pad
+		//writer.put((byte) 0); // End Datablock byte
+		writer.putInt(playerCharacter.getUnusedStatPoints());
+		writer.putInt(playerCharacter.getLevel());
+		writer.putInt(playerCharacter.getExp() + playerCharacter.overFlowEXP);
+		writer.putFloat(playerCharacter.manaMax);
+		writer.putFloat(playerCharacter.mana.get());
+		writer.putFloat(playerCharacter.staminaMax);
+		writer.putFloat(playerCharacter.stamina.get());
+		writer.putInt(playerCharacter.getAtrHandOne());
+		writer.putInt(playerCharacter.getAtrHandTwo());
+		writer.putInt(playerCharacter.getDefenseRating());
+
+		if (MBServerStatics.POWERS_DEBUG) //debug mode, grant lots of trains
+			writer.putInt(1000);
+		else
+			writer.putInt(playerCharacter.trainsAvailable.get());
+
+		/*
+		 * Skills
+		 */
+		if (loginData)
+			writer.putInt(0); // Skip skills
+		else {
+			writer.putInt(playerCharacter.skills.size());
+			Iterator<String> it = playerCharacter.skills.keySet().iterator();
+			while (it.hasNext()) {
+				String name = it.next();
+				CharacterSkill.serializeForClientMsg(playerCharacter.skills.get(name),writer);
+			}
+		}
+
+		/*
+		 * Powers
+		 */
+		if (loginData)
+			writer.putInt(0); // Skip Powers
+		else
+			if (MBServerStatics.POWERS_DEBUG) //debug mode, grant all powers
+				PowersManager.testPowers(writer);
+			else {
+				writer.putInt(playerCharacter.powers.size());
+				for (CharacterPower sp : playerCharacter.powers.values()) {
+					CharacterPower.serializeForClientMsg(sp,writer);
+				}
+			}
+
+		/*
+		 * Inventory
+		 */
+		if (loginData) {
+			writer.putInt(0); // Skip Inventory
+			writer.putInt(playerCharacter.getInventoryCapacity()); // Inventory Capacity
+
+		} else {
+			ArrayList<Item> inv = playerCharacter.charItemManager.getInventory(true);
+			Item.putList(writer, inv, false, playerCharacter.getObjectUUID());
+			writer.putInt(playerCharacter.getInventoryCapacityRemaining());
+		}
+
+		/*
+		 * Bank
+		 */
+		if (loginData) {
+			writer.putInt(0); // Skip Bank
+			writer.putInt(AbstractCharacter.getBankCapacity()); // Bank Capacity
+
+		} else {
+			ArrayList<Item> bank = playerCharacter.charItemManager.getBank();
+
+			Item.putList(writer, bank, false, playerCharacter.getObjectUUID());
+			writer.putInt(playerCharacter.getBankCapacityRemaining());
+		}
+		//load player friends.
+		if (loginData)
+			writer.putInt(0);
+			else{
+				HashSet<Integer> friendMap = PlayerFriends.PlayerFriendsMap.get(playerCharacter.getObjectUUID());
+				if (friendMap == null)
+					writer.putInt(0);
+				else{
+					writer.putInt(friendMap.size());
+					for (int friendID : friendMap){
+						PlayerCharacter friend = PlayerCharacter.getFromCache(friendID);
+						//shouldn't get here, but if null serialize blank friend.
+						if (friend == null){
+							writer.putInt(0);
+							writer.putInt(0);
+							writer.putInt(0);
+							writer.putInt(0);
+							writer.putInt(0);
+						}else{
+							writer.putInt(friend.getObjectType().ordinal());
+							writer.putInt(friend.getObjectUUID());
+							writer.putString(friend.getName());
+							boolean online = SessionManager.getPlayerCharacterByID(friend.getObjectUUID()) != null ? true : false;
+							writer.putInt(online ? 0 : 1);
+							writer.putInt(friend.friendStatus.ordinal());
+						}
+					
+					}
+				}
+			}
+		
+		
+		writer.putInt(0);
+		writer.putInt(0);
+		writer.putInt(0);
+		writer.putInt(0);
+		writer.putInt(0);
+		writer.putInt(0);
+		writer.putInt(0);
+		writer.putInt(0);
+		writer.putInt(0);
+		writer.putInt(0);
+		writer.putInt(0);
+		writer.putInt(0);
+		writer.putInt(0);
+
+		writer.putShort((short) 0);
+		writer.put((byte) 0);
+		// playerCharacter is for send self in enter world (full character)
+		if (!loginData && !freshChar) {
+			int size = playerCharacter.getRecycleTimers().size();
+			writer.putInt(size);
+			if (size > 0)
+				for (int token : playerCharacter.getRecycleTimers().keySet()) {
+
+					JobContainer frtj = playerCharacter.getRecycleTimers().get(token);
+					long timeLeft = frtj.timeOfExection() - System.currentTimeMillis();
+					writer.putInt(token);
+					writer.putInt((int) timeLeft / 1000);
+				}
+			DateTime enterWorld = new DateTime(playerCharacter.timestamps.get("EnterWorld"));
+			writer.putDateTime(enterWorld);
+
+			writer.putInt(0x49EF1E98); //DUnno what playerCharacter is.
+			writer.putFloat(DateTime.now().hourOfDay().get()); //daylight in float.
+			writer.putFloat(6); //interval of light to change per game hour //float
+			//writer.putInt(1637194901); //playerCharacter is actually an opcode taht is in recordings, no clue what it is, dumped it and it changes nothing
+		} else {
+            writer.put((byte) 0); //added to compensate the cooldown check.
+
+            //add server up or down
+            int serverUp = LoginServer.worldServerRunning ? 1 : 0;
+
+            if (playerCharacter.account == null)
+                serverUp = 0;
+
+            if ((playerCharacter.account.status.equals(AccountStatus.ADMIN) == false) &&
+                    (playerCharacter.account.status.equals(MBServerStatics.worldAccessLevel) == false))
+                serverUp = 0;
+
+            writer.putInt(serverUp);
+            writer.putInt(0); // effects, not sure used by players
+            writer.put((byte) 0); // End Player Datablock
+        }
+
+	}
+
+
+
+	public static PlayerCharacter generatePCFromCommitNewCharacterMsg(Account a, CommitNewCharacterMsg msg, ClientConnection clientConnection)
+			 {
+
+		String firstName = msg.getFirstName().trim();
+		String lastName = msg.getLastName().trim();
+
+		if (firstName.length() < 3){
+			LoginServerMsgHandler.sendInvalidNameMsg(firstName, lastName,  MBServerStatics.INVALIDNAME_FIRSTNAME_MUST_BE_LONGER,
+					clientConnection);
+			return null;
+		}
+			
+		// Ensure names are below required length
+		if (firstName.length() > 15 || lastName.length() > 15){
+			LoginServerMsgHandler.sendInvalidNameMsg(firstName, lastName, MBServerStatics.INVALIDNAME_FIRSTANDLAST_MUST_BE_SHORTER,
+					clientConnection);
+			return null;
+		}
+
+		// Check if firstname is valid
+		if (MiscUtils.checkIfFirstNameInvalid(firstName)){
+			LoginServerMsgHandler.sendInvalidNameMsg(firstName, lastName,  MBServerStatics.INVALIDNAME_PLEASE_CHOOSE_ANOTHER_FIRSTNAME,
+					clientConnection);
+			return null;
+		}
+			
+		// Check if last name is valid
+		if (MiscUtils.checkIfLastNameInvalid(lastName)){
+			LoginServerMsgHandler.sendInvalidNameMsg(firstName, lastName, MBServerStatics.INVALIDNAME_LASTNAME_UNAVAILABLE,
+					clientConnection);
+			return null;
+		}
+			
+		// Verify Race
+		int raceID = msg.getRace();
+
+		Race race = Race.getRace(raceID);
+
+		if (race == null) {
+			Logger.info("Invalid RaceID: " + raceID);
+			return null;
+		}
+
+		// Verify BaseClass Object.
+		int baseClassID = msg.getBaseClass();
+		BaseClass baseClass = DbManager.BaseClassQueries.GET_BASE_CLASS(baseClassID);
+
+		if (baseClass == null) {
+			Logger.info("Invalid BaseClasID: " + baseClassID);
+			return null;
+		}
+
+		// Verify Race/baseClass combo.
+		boolean valid = false;
+
+		for (BaseClass bc : race.getValidBaseClasses()) {
+
+			if (bc.getObjectUUID() == baseClassID) {
+				valid = true;
+				break;
+			}
+		}
+
+		if (!valid) {
+			Logger.info("Invalid BaseClass/Race Combo");
+			return null;
+		}
+
+		// Verify HairStyle/BeardStyle/SkinColor/HairColor/BeardColor
+		int hairStyleID = msg.getHairStyle();
+		int beardStyleID = msg.getBeardStyle();
+		int skinColorID = msg.getSkinColor();
+		int hairColorID = msg.getHairColor();
+		int beardColorID = msg.getBeardColor();
+
+		if (!race.isValidHairStyle(hairStyleID)) {
+			Logger.info("Invalid HairStyleID: " + hairStyleID + " for race: " + race.getName());
+			return null;
+		}
+
+		if (!race.isValidSkinColor(skinColorID)) {
+			Logger.info("Invalid skinColorID: " + skinColorID + " for race: " + race.getName());
+			return null;
+				 }
+
+		 if (!race.isValidHairColor(hairColorID)) {
+			Logger.info("Invalid hairColorID: " + hairColorID + " for race: " + race.getName());
+			return null;
+				 }
+
+		 if (!race.isValidBeardColor(beardColorID)) {
+			Logger.info("Invalid beardColorID: " + beardColorID + " for race: " + race.getName());
+			return null;
+				 }
+
+		// Get stat modifiers
+		int strMod = msg.getStrengthMod();
+		int dexMod = msg.getDexterityMod();
+		int conMod = msg.getConstitutionMod();
+		int intMod = msg.getIntelligenceMod();
+		int spiMod = msg.getSpiritMod();
+		
+
+		if (intMod < -5 || dexMod < -5 || conMod < -5 || strMod <-5 || spiMod < -5) {
+			Logger.error("NEGATIVE STAT CHEAT ATTEMPTED! ACCOUNT: " +a.getUname() + "(" + a.getObjectUUID() +  ") IP ADDRESS: " +clientConnection.getClientIpAddress());
+			return null;
+		}
+
+		// calculate current stats:
+		short strCur = (short) (race.getStrStart() + baseClass.getStrMod() + strMod);
+		short dexCur = (short) (race.getDexStart() + baseClass.getDexMod() + dexMod);
+		short conCur = (short) (race.getConStart() + baseClass.getConMod() + conMod);
+		short intCur = (short) (race.getIntStart() + baseClass.getIntMod() + intMod);
+		short spiCur = (short) (race.getSpiStart() + baseClass.getSpiMod() + spiMod);
+
+		// calculate max stats:
+		short strMax = race.getStrMax();
+		short dexMax = race.getDexMax();
+		short conMax = race.getConMax();
+		short intMax = race.getIntMax();
+		short spiMax = race.getSpiMax();
+
+		// Verify not too many runes applied
+		int numRunes = msg.getNumRunes();
+
+		if (numRunes > 16) {
+			Logger.info("Too many Runes applied");
+			return null;
+		}
+
+		// Get Runes
+		// ArrayList<RuneBase> characterRunesUsed = new ArrayList<RuneBase>();
+		// ArrayList<Byte> subtypesUsed = new ArrayList<Byte>();
+		int remainingPoints = race.getStartingPoints() - strMod - dexMod - conMod - intMod - spiMod;
+
+		int[] characterRunes = msg.getRunes();
+
+		HashSet<Byte> usedRunesSubType = new HashSet<>();
+		HashSet<RuneBase> usedRunes = new HashSet<>();
+
+		// So that all the penalties can be added at the end.
+		ConcurrentHashMap<String, Integer> penalties = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW);
+
+		penalties.put("StrCur", 0);
+		penalties.put("StrMax", 0);
+		penalties.put("DexCur", 0);
+		penalties.put("DexMax", 0);
+		penalties.put("ConCur", 0);
+		penalties.put("ConMax", 0);
+		penalties.put("IntCur", 0);
+		penalties.put("IntMax", 0);
+		penalties.put("SpiCur", 0);
+		penalties.put("SpiMax", 0);
+
+		PriorityQueue<Map.Entry<Integer, RuneBase>> orderedRunes = new PriorityQueue<>(14,
+				new Comparator<Map.Entry<Integer, RuneBase>>() {
+
+			@Override
+			public int compare(Entry<Integer, RuneBase> o1, Entry<Integer, RuneBase> o2) {
+				return o1.getKey() - o2.getKey();
+			}
+		});
+
+		// Figure out which Runes we are adding.
+		for (int i : characterRunes) {
+			// Zero skip
+			if (i == 0)
+				continue;
+
+			// Skip the Race and BaseClass runes... already dealt with.
+			if (i == raceID || i == baseClassID)
+				continue;
+
+			RuneBase runeBase = RuneBase.getRuneBase(i);
+
+			// Null check
+			if (runeBase == null) {
+				Logger.info("GOM returned NULL RuneBase");
+				return null;
+			}
+
+			// Validate Rune against Race
+			if (!race.isAllowedRune(runeBase)) {
+				Logger.info("Trait Not valid for Race");
+				return null;
+			}
+
+			// Validate BaseClass against Race
+			if (!baseClass.isAllowedRune(runeBase)) {
+				Logger.info("Trait Not valid for BaseClass");
+				return null;
+			}
+
+			int previous_size = usedRunes.size();
+			int previous_subtype = usedRunesSubType.size();
+
+			usedRunes.add(runeBase);
+			usedRunesSubType.add(runeBase.getSubtype());
+
+			// Duplicate Rune check
+			if (usedRunes.size() <= previous_size) {
+				Logger.info("Duplicate RuneBase");
+				return null;
+			}
+
+			// Duplicate Subtype check
+			if (runeBase.getSubtype() != 0 && usedRunesSubType.size() <= previous_subtype) {
+				Logger.info("Duplicate RuneBase Subtype");
+				return null;
+			}
+
+			int maxValue = 0;
+
+			// Every attempt is made to load MIN_NEEDED_ATTRIBUTES first.
+
+			if (runeBase.getAttrs() != null)
+				for (RuneBaseAttribute rba : runeBase.getAttrs()) {
+					if (rba.getAttributeID() == MBServerStatics.RUNE_STR_MIN_NEEDED_ATTRIBUTE_ID
+							|| rba.getAttributeID() == MBServerStatics.RUNE_DEX_MIN_NEEDED_ATTRIBUTE_ID
+							|| rba.getAttributeID() == MBServerStatics.RUNE_CON_MIN_NEEDED_ATTRIBUTE_ID
+							|| rba.getAttributeID() == MBServerStatics.RUNE_INT_MIN_NEEDED_ATTRIBUTE_ID
+							|| rba.getAttributeID() == MBServerStatics.RUNE_SPI_MIN_NEEDED_ATTRIBUTE_ID) {
+						maxValue = rba.getModValue();
+						if (runeBase.getName().equals("Giant's Blood"))
+							maxValue = 45; // Take care of the Giant's Blood special
+						// case.
+						break;
+					}
+				}
+
+			orderedRunes.add(new AbstractMap.SimpleEntry<>(maxValue, runeBase));
+		}
+
+		while (orderedRunes.size() > 0) {
+			RuneBase rb = orderedRunes.remove().getValue();
+			ArrayList<RuneBaseAttribute> attrs = rb.getAttrs();
+
+			if (attrs != null)
+				for (RuneBaseAttribute abr : attrs) {
+
+					int attrID = abr.getAttributeID();
+					int value = abr.getModValue();
+
+					switch (attrID) {
+					case MBServerStatics.RUNE_COST_ATTRIBUTE_ID:
+
+						Logger.info( "Bought " + rb.getName() + " for " + value + " points. "
+								+ (remainingPoints - value) + " left.");
+
+						if ((remainingPoints - value) >= 0) {
+							remainingPoints -= value;
+							continue;
+						}
+						Logger.info("Not enough points left");
+						return null;
+					case MBServerStatics.RUNE_STR_MIN_NEEDED_ATTRIBUTE_ID:
+
+						if (strCur >= value)
+							continue;
+
+						Logger.info("STR fails to meet Rune Minimum --> " + rb.getName());
+						return null;
+					case MBServerStatics.RUNE_DEX_MIN_NEEDED_ATTRIBUTE_ID:
+
+						if (dexCur >= value)
+							continue;
+
+						Logger.info("DEX fails to meet Rune Minimum --> " + rb.getName());
+						return null;
+					case MBServerStatics.RUNE_CON_MIN_NEEDED_ATTRIBUTE_ID:
+
+						if (conCur >= value)
+							continue;
+
+						Logger.info("CON fails to meet Rune Minimum --> " + rb.getName());
+						return null;
+					case MBServerStatics.RUNE_INT_MIN_NEEDED_ATTRIBUTE_ID:
+
+						if (intCur >= value)
+							continue;
+
+						Logger.info("INT fails to meet Rune Minimum --> " + rb.getName());
+						return null;
+					case MBServerStatics.RUNE_SPI_MIN_NEEDED_ATTRIBUTE_ID:
+
+						if (spiCur >= value)
+							continue;
+
+						Logger.info("SPI fails to meet Rune Minimum --> " + rb.getName());
+						return null;
+					case MBServerStatics.RUNE_STR_ATTRIBUTE_ID:
+
+						if (value < 0)
+							penalties.put("StrCur", (penalties.get("StrCur") + value));
+						else
+							strCur += value;
+						continue;
+
+					case MBServerStatics.RUNE_DEX_ATTRIBUTE_ID:
+						if (value < 0)
+							penalties.put("DexCur", (penalties.get("DexCur") + value));
+						else
+							dexCur += value;
+						continue;
+					case MBServerStatics.RUNE_CON_ATTRIBUTE_ID:
+						if (value < 0)
+							penalties.put("ConCur", (penalties.get("ConCur") + value));
+						else
+							conCur += value;
+						continue;
+					case MBServerStatics.RUNE_INT_ATTRIBUTE_ID:
+						if (value < 0)
+							penalties.put("IntCur", (penalties.get("IntCur") + value));
+						else
+							intCur += value;
+						continue;
+					case MBServerStatics.RUNE_SPI_ATTRIBUTE_ID:
+						if (value < 0)
+							penalties.put("SpiCur", (penalties.get("SpiCur") + value));
+						else
+							spiCur += value;
+						continue;
+					case MBServerStatics.RUNE_STR_MAX_ATTRIBUTE_ID:
+						if (value < 0)
+							penalties.put("StrMax", (penalties.get("StrMax") + value));
+						else
+							strMax += value;
+						continue;
+					case MBServerStatics.RUNE_DEX_MAX_ATTRIBUTE_ID:
+						if (value < 0)
+							penalties.put("DexMax", (penalties.get("DexMax") + value));
+						else
+							dexMax += value;
+						continue;
+					case MBServerStatics.RUNE_CON_MAX_ATTRIBUTE_ID:
+						if (value < 0)
+							penalties.put("ConMax", (penalties.get("ConMax") + value));
+						else
+							conMax += value;
+						continue;
+					case MBServerStatics.RUNE_INT_MAX_ATTRIBUTE_ID:
+						if (value < 0)
+							penalties.put("IntMax", (penalties.get("IntMax") + value));
+						else
+							intMax += value;
+						continue;
+					case MBServerStatics.RUNE_SPI_MAX_ATTRIBUTE_ID:
+						if (value < 0)
+							penalties.put("SpiMax", (penalties.get("SpiMax") + value));
+						else
+							spiMax += value;
+						continue;
+
+					default:
+						Logger.info("Unknown ATTRIBUTE_ID while checking RuneBaseAttributes: " + attrID);
+						return null;
+					}
+				}
+		}
+
+		// Add in all of the penalties.
+		strCur += penalties.get("StrCur");
+		strMax += penalties.get("StrMax");
+		dexCur += penalties.get("DexCur");
+		dexMax += penalties.get("DexMax");
+		conCur += penalties.get("ConCur");
+		conMax += penalties.get("ConMax");
+		intCur += penalties.get("IntCur");
+		intMax += penalties.get("IntMax");
+		spiCur += penalties.get("SpiCur");
+		spiMax += penalties.get("SpiMax");
+
+		int kitID = msg.getKit();
+
+		// get the correctKit
+		int raceClassID = Kit.GetKitIDByRaceClass(raceID, baseClassID);
+		ArrayList<Kit> allKits = Kit.RaceClassIDMap.get(raceClassID);
+
+		Kit kit = null;
+
+		for (Kit k : allKits) {
+			if (k.getKitNumber() == kitID) {
+				kit = k;
+				break;
+			}
+		}
+
+		if (kit == null) {
+			Logger.info("Unable to find matching kitID: " + kitID);
+			return null;
+		}
+
+		byte runningTrains = 0;
+		PlayerCharacter playerCharacter;
+
+		//Synchronized block to allow exclusive access when confirming
+		//uniqueness of FirstName and subsequently saving the new record
+		//to the database with that FirstName
+		synchronized (FirstNameLock) {
+			// Test if FirstName already exists.
+			// This must be the very last check before calling the
+			// DB to create the character record
+			if (DbManager.PlayerCharacterQueries.IS_CHARACTER_NAME_UNIQUE(firstName) == false){
+					LoginServerMsgHandler.sendInvalidNameMsg(firstName, lastName, MBServerStatics.INVALIDNAME_FIRSTNAME_UNAVAILABLE,
+							clientConnection);
+				return null;
+			}
+			
+			// Make PC
+			PlayerCharacter pcWithoutID = new PlayerCharacter( firstName, lastName, (short) strMod, (short) dexMod, (short) conMod,
+					(short) intMod, (short) spiMod, Guild.getErrantGuild(), runningTrains, a, race, baseClass, (byte) skinColorID, (byte) hairColorID,
+					(byte) beardColorID, (byte) beardStyleID, (byte) hairStyleID);
+
+			try {
+				playerCharacter = DbManager.PlayerCharacterQueries.ADD_PLAYER_CHARACTER(pcWithoutID);
+			} catch (Exception e) {
+				Logger.error("generatePCFromCommitNewCharacterMsg", "An error occurred while saving new PlayerCharacter to DB", e);
+				return null;
+			}
+
+			if (playerCharacter == null) {
+				Logger.info("GOM Failed to create PlayerCharacter");
+				return null;
+			}
+
+		} // END synchronized(FirstNameLock)
+
+		// Add creation runes
+		for (RuneBase rb : usedRunes) {
+			CharacterRune runeWithoutID = new CharacterRune(rb, playerCharacter.getObjectUUID());
+			CharacterRune characterRune;
+			try {
+				characterRune = DbManager.CharacterRuneQueries.ADD_CHARACTER_RUNE(runeWithoutID);
+			} catch (Exception e) {
+				characterRune = null;
+			}
+
+			if (characterRune == null) {
+				playerCharacter.deactivateCharacter();
+				Logger.info("GOM Failed to create CharacterRune");
+				return null;
+			}
+
+			playerCharacter.addRune(characterRune);
+		}
+
+		if (hairStyleID != 0) {
+			// Create Hair
+			Item tempHair = new Item( ItemBase.getItemBase(hairStyleID), playerCharacter.getObjectUUID(), OwnerType.PlayerCharacter,
+					(byte) 0, (byte) 0, (short) 1, (short) 1, false, false, ItemContainerType.EQUIPPED,
+					(byte) MBServerStatics.SLOT_HAIRSTYLE, new ArrayList<>(),"");
+
+			Item hair;
+
+			try {
+				hair = DbManager.ItemQueries.ADD_ITEM(tempHair);
+			} catch (Exception e) {
+				hair = null;
+			}
+
+			if (hair == null) {
+				playerCharacter.deactivateCharacter();
+				Logger.info("GameObjectManager failed to create Hair:" + hairStyleID + " in Slot:"
+						+ MBServerStatics.SLOT_HAIRSTYLE);
+				return null;
+			}
+		}
+
+		if (beardStyleID != 0) {
+			// Create Beard
+			Item tempBeard = new Item( ItemBase.getItemBase(beardStyleID), playerCharacter.getObjectUUID(), OwnerType.PlayerCharacter,
+					(byte) 0, (byte) 0, (short) 1, (short) 1, false, false,ItemContainerType.EQUIPPED,
+					(byte) MBServerStatics.SLOT_BEARDSTYLE, new ArrayList<>(),"");
+			Item beard;
+			try {
+				beard = DbManager.ItemQueries.ADD_ITEM(tempBeard);
+			} catch (Exception e) {
+				beard = null;
+			}
+
+			if (beard == null) {
+				playerCharacter.deactivateCharacter();
+				Logger.info("GameObjectManager failed to create Beard:" + beardStyleID + " in Slot:"
+						+ MBServerStatics.SLOT_BEARDSTYLE);
+				return null;
+			}
+		}
+		// Create items from Kit and equip on character.
+		try {
+			kit.equipPCwithKit(playerCharacter);
+		} catch (Exception e) {
+			Logger.info("Unable to find KIT ID for Race: " + raceID + "||" + "Class:" + baseClassID );
+			playerCharacter.deactivateCharacter();
+		return null;
+		}
+
+		// Get any new skills that belong to the player
+		playerCharacter.calculateSkills();
+
+		a.setLastCharacter(playerCharacter.getObjectUUID());
+		playerCharacter.charItemManager.load();
+
+		playerCharacter.activateCharacter();
+
+		return playerCharacter;
+	}
+
+	public String getCombinedName() {
+		return this.getName();
+	}
+
+	public long getLastGuildToInvite() {
+		return this.lastGuildToInvite;
+	}
+
+	public void setLastGuildToInvite(int value) {
+		this.lastGuildToInvite = value;
+	}
+
+	public boolean getFollow() {
+		return this.follow;
+	}
+
+	public boolean toggleFollow() {
+		this.follow = !this.follow;
+		return this.follow;
+	}
+
+	public void setFollow(boolean value) {
+		this.follow = value;
+	}
+
+	public int getLastGroupToInvite() {
+		return this.lastGroupToInvite;
+	}
+
+	public void setLastGroupToInvite(int value) {
+		this.lastGroupToInvite = value;
+	}
+
+	@Override
+	public float getAltitude() {
+		if (this.altitude < 0)
+			this.altitude = 0;
+		
+		//player has reached desired altitude, return normal altitude.
+		if (this.getTakeOffTime() == 0)
+			return this.altitude;
+		
+		//sanity check  if desired altitude is the same as current altitude. return desired altitude.
+		if (this.altitude == this.getDesiredAltitude()){
+			return this.getDesiredAltitude();
+		}
+		
+		//calculate how much the player has moved up
+		float amountMoved = (System.currentTimeMillis() - this.getTakeOffTime()) * MBServerStatics.FLY_RATE; //FUCK DIVIDING
+			
+		//Player is moving up
+		if (this.getDesiredAltitude() > this.altitude){
+			
+			//if amount moved passed desiredAltitude, return the desired altitude.
+			if (this.altitude + amountMoved >= this.getDesiredAltitude())
+				return this.getDesiredAltitude();
+			
+			return this.altitude + amountMoved;				
+			//Player is moving down
+		}else{
+			//if amount moved passed desiredAltitude, return the desired altitude.
+			if (this.altitude - amountMoved <= this.getDesiredAltitude())
+				return this.getDesiredAltitude();
+			return this.altitude - amountMoved;
+		}
+		
+		
+		
+	}
+
+	public void setAltitude(float value) {
+			this.altitude = value;
+	}
+
+	public HashSet<AbstractWorldObject> getLoadedObjects() {
+		return this.loadedObjects;
+	}
+
+	public HashSet<AbstractWorldObject> getLoadedStaticObjects() {
+		return this.loadedStaticObjects;
+	}
+
+	public void setLoadedStaticObjects(HashSet<AbstractWorldObject> value) {
+		this.loadedStaticObjects = value;
+	}
+
+	public void setTeleportMode(boolean teleportMode) {
+		this.teleportMode = teleportMode;
+	}
+
+	public boolean isTeleportMode() {
+		return teleportMode;
+	}
+
+	// public ConcurrentHashMap<Integer, FinishRecycleTimeJob>
+	// getRecycleTimers() {
+	// return this.recycleTimers;
+	// }
+	// public UsePowerJob getLastPower() {
+	// return this.lastPower;
+	// }
+	// public void setLastPower(UsePowerJob value) {
+	// this.lastPower = value;
+	// }
+	// public void clearLastPower() {
+	// this.lastPower = null;
+	// }
+	public long chatFloodTime(int chatOpcode, long chatTimeMilli, int qtyToSave) {
+		if (qtyToSave < 1)
+			return 0L; // disabled
+		LinkedList<Long> times = null;
+		long oldestTime;
+		synchronized (chatChanFloodList) {
+			if (!chatChanFloodList.containsKey(chatOpcode)) {
+				times = new LinkedList<>();
+				for (int i = 0; i < qtyToSave; i++) {
+					times.add(0L);
+				}
+				chatChanFloodList.put(chatOpcode, times);
+			} else
+				times = chatChanFloodList.get(chatOpcode);
+			oldestTime = times.getLast();
+			times.removeLast();
+			times.addFirst(chatTimeMilli);
+		}
+		return oldestTime;
+	}
+
+	public void addIgnoredPlayer(Account ac, String name) {
+		if (ac == null)
+			return;
+		int acID = ac.getObjectUUID();
+		if (acID < 1)
+			return;
+		if (ignoredPlayerIDs == null)
+			return;
+		if (acID == getObjectUUID())
+			return; // yourself
+
+		ignoredPlayerIDs.put(acID, name);
+	}
+
+	public static boolean isIgnoreListFull() {
+		return false; //Why were we setting a limit on ignores? -
+		//return (ignoredPlayerIDs.size() >= MBServerStatics.IGNORE_LIST_MAX);
+	}
+
+	public void removeIgnoredPlayer(Account ac) {
+		if (ac == null)
+			return;
+		int acID = ac.getObjectUUID();
+		if (acID < 1)
+			return;
+		if (ignoredPlayerIDs == null)
+			return;
+		if (acID == getObjectUUID())
+			return; // yourself
+
+		ignoredPlayerIDs.remove(acID);
+	}
+
+	public boolean isIgnoringPlayer(PlayerCharacter pc) {
+
+        if (pc == null)
+            return false;
+
+        if (pc.account == null)
+            return false;
+
+        return isIgnoringPlayer(pc.account);
+    }
+
+	public boolean isIgnoringPlayer(Account ac) {
+		if (ac == null)
+			return false;
+		int acID = ac.getObjectUUID();
+		if (acID < 1)
+			return false;
+		return ignoredPlayerIDs.containsKey(acID);
+	}
+
+	public static boolean isIgnorable() {
+		return true;
+		//		// if (account == null) return false;
+		//		if (account.getAccessLevel() > 0) {
+		//			return false;
+		//		}
+		//		return true;
+	}
+
+	public String[] getIgnoredPlayerNames() {
+		int size = ignoredPlayerIDs.size();
+		String[] ary = new String[size];
+		for (int i = 0; i < size; i++) {
+			//			ary[i] = PlayerCharacter.getFirstName(ignoredPlayerIDs.get(i));
+			ary[i] = ignoredPlayerIDs.get(i);
+		}
+		return ary;
+	}
+
+	public int getStrMod() {
+		return this.strMod.get();
+	}
+
+	public int getDexMod() {
+		return this.dexMod.get();
+	}
+
+	public int getConMod() {
+		return this.conMod.get();
+	}
+
+	public int getIntMod() {
+		return this.intMod.get();
+	}
+
+	public int getSpiMod() {
+		return this.spiMod.get();
+	}
+
+	public boolean isMale() {
+		if (this.race == null)
+			return true;
+		return (this.race.getRaceType().getCharacterSex().equals(CharacterSex.MALE));
+	}
+	
+
+	public boolean canSee(PlayerCharacter tar) {
+
+		if (tar == null)
+			return false;
+
+		if (this.equals(tar))
+			return true;
+
+		return this.getSeeInvis() >= tar.hidden && !tar.safemodeInvis();
+	}
+
+	/**
+	 * @ Initialize player upon creation
+	 */
+	public static void initializePlayer(PlayerCharacter player) {
+
+		if (player.initialized)
+			return;
+		//	Logger.info("", " Initializing " + player.getCombinedName());
+		player.skills = DbManager.CharacterSkillQueries.GET_SKILLS_FOR_CHARACTER(player);
+		player.powers = player.initializePowers();
+		
+		
+		if (ConfigManager.serverType.equals(ServerType.WORLDSERVER))
+		player.setLoc(player.bindLoc);
+		player.endLoc = Vector3fImmutable.ZERO;
+
+		//get level based on experience
+		player.level = (short) Experience.getLevel(player.exp);
+
+		player.setHealth(999999f);
+		player.mana.set(999999f);
+		player.stamina.set(999999f);
+		player.bonuses = new PlayerBonuses(player);
+		PlayerBonuses.InitializeBonuses(player);
+		player.resists = new Resists(player);
+		player.charItemManager.load();
+
+		if (ConfigManager.serverType.equals(ServerType.WORLDSERVER)) {
+
+			//CharacterSkill.updateAllBaseAmounts(this);
+			CharacterPower.grantTrains(player);
+
+			// calculate skills. Make sure none are missing.
+			AbstractCharacter.runBonusesOnLoad(player);
+			
+			PlayerCharacter.InitializeSkillsOnLoad(player);
+
+			//apply all bonuses
+			player.recalculatePlayerStats(true);
+			player.trainsAvailable.set(CharacterSkill.getTrainsAvailable(player));
+
+			if (player.trainsAvailable.get() < 0)
+				player.recalculateTrains();
+
+			//this.resists.calculateResists(this);
+			player.newChar = true;
+
+			//check current guild valid for player
+			player.checkGuildStatus();
+
+			player.setHealth(player.getHealthMax());
+			player.mana.set(player.manaMax);
+			player.stamina.set(player.staminaMax);
+		} else
+			player.setBindLoc(Vector3fImmutable.ZERO);
+
+		player.initialized = true;
+
+		String lastAscii = player.lastName.replaceAll("[^\\p{ASCII}]", "");
+		player.asciiLastName = lastAscii.equals(player.lastName);
+	}
+
+	public void recalculatePlayerStats(boolean initialized) {
+
+		//calculate base stats
+		calculateBaseStats();
+
+		//calculate base skills
+		CharacterSkill.updateAllBaseAmounts(this);
+		calculateModifiedStats();
+
+		//calculate modified skills
+		CharacterSkill.updateAllModifiedAmounts(this);
+		this.updateScaleHeight();
+
+		//calculate modified stats
+
+
+		//calculate ATR, damage and defense
+		calculateAtrDefenseDamage();
+
+		//calculate movement bonus
+		calculateSpeedMod();
+
+		// recalculate Max Health/Mana/Stamina
+		calculateMaxHealthManaStamina();
+
+		// recalculate Resists
+		Resists.calculateResists(this);
+
+	}
+
+	public static void recalculatePlayerStatsOnLoad(PlayerCharacter pc) {
+
+		//calculate base stats
+		pc.calculateBaseStats();
+
+		//calculate base skills
+		CharacterSkill.updateAllBaseAmounts(pc);
+		pc.calculateModifiedStats();
+
+		//calculate modified skills
+		CharacterSkill.updateAllModifiedAmounts(pc);
+
+
+		//calculate modified stats
+
+
+		//calculate ATR, damage and defense
+		pc.calculateAtrDefenseDamage();
+
+		//calculate movement bonus
+		pc.calculateSpeedMod();
+
+		// recalculate Max Health/Mana/Stamina
+		pc.calculateMaxHealthManaStamina();
+
+		// recalculate Resists
+		Resists.calculateResists(pc);
+
+	}
+
+	/**
+	 * @ Recalculate player after promoting or gaining a level
+	 */
+	public void recalculate() {
+		this.applyBonuses();
+		this.trainsAvailable.set(CharacterSkill.getTrainsAvailable(this));
+		if (this.trainsAvailable.get() < 0)
+			recalculateTrains();
+		//this.resists.calculateResists(this);
+
+		// calculate skills and powers. Make sure none are missing.
+		this.calculateSkills();
+
+		// calculate powers again. See if any new powers unlocked
+		this.calculateSkills();
+	}
+
+	//This is run to auto-fix any overage on skill training.
+	private void recalculateTrains() {
+		int trainsAvailable = CharacterSkill.getTrainsAvailable(this);
+		if (trainsAvailable < 0) {
+
+			//refine powers first, run twice to catch any prereqs
+			ConcurrentHashMap<Integer, CharacterPower> powers = this.getPowers();
+			for (int i = 0; i < 2; i++) {
+				for (CharacterPower p : powers.values()) {
+					if (trainsAvailable >= 0)
+						return;
+					while (p.getTrains() > 0 && p.refine(this)) {
+						trainsAvailable++;
+						if (trainsAvailable >= 0)
+							return;
+					}
+				}
+			}
+
+			//refine skills
+			ConcurrentHashMap<String, CharacterSkill> skills = this.getSkills();
+			for (CharacterSkill s : skills.values()) {
+				if (trainsAvailable >= 0)
+					return;
+				while (s.getNumTrains() > 0 && s.refine(this)) {
+					if (CharacterSkill.getTrainsAvailable(this) >= 0)
+						return;
+				}
+			}
+		}
+	}
+
+	/**
+	 * @ Calculates Base Stats Call this when modifying stats or adding/removing
+	 * runes
+	 */
+	public void calculateBaseStats() {
+		if (this.race == null || this.baseClass == null)
+			// Logger.getInstance().log( LogEventType.ERROR,
+			// "PlayerCharacter.updateBaseStats: Missing race or baseclass for Player "
+			// + this.getUUID());
+			return;
+
+		// get base stats and total available
+		int strMin = this.race.getStrStart() + this.baseClass.getStrMod() - 5;
+		int dexMin = this.race.getDexStart() + this.baseClass.getDexMod() - 5;
+		int conMin = this.race.getConStart() + this.baseClass.getConMod() - 5;
+		int intMin = this.race.getIntStart() + this.baseClass.getIntMod() - 5;
+		int spiMin = this.race.getSpiStart() + this.baseClass.getSpiMod() - 5;
+		int str = this.race.getStrStart() + this.baseClass.getStrMod() + this.strMod.get();
+		int dex = this.race.getDexStart() + this.baseClass.getDexMod() + this.dexMod.get();
+		int con = this.race.getConStart() + this.baseClass.getConMod() + this.conMod.get();
+		int intt = this.race.getIntStart() + this.baseClass.getIntMod() + this.intMod.get();
+		int spi = this.race.getSpiStart() + this.baseClass.getSpiMod() + this.spiMod.get();
+		int strMax = this.race.getStrMax();
+		int dexMax = this.race.getDexMax();
+		int conMax = this.race.getConMax();
+		int intMax = this.race.getIntMax();
+		int spiMax = this.race.getSpiMax();
+		int available = this.race.getStartingPoints() - this.strMod.get() - this.dexMod.get() - this.conMod.get() - this.intMod.get() - this.spiMod.get();
+		if (level < 20)
+			available += (level - 1) * 5;
+		else if (level < 30)
+			available += 90 + (level - 19) * 4;
+		else if (level < 40)
+			available += 130 + (level - 29) * 3;
+		else if (level < 50)
+			available += 160 + (level - 39) * 2;
+		else
+			available += 180 + (level - 49);
+
+		// modify for any runes applied.
+		for (CharacterRune rune : this.runes) {
+			if (rune.getRuneBase() == null)
+				// Logger.getInstance().log( LogEventType.ERROR,
+				// "PlayerCharacter.updateBaseStats: Missing runebase for rune "
+				// + rune.getUUID());
+				continue;
+			ArrayList<RuneBaseAttribute> attrs = rune.getRuneBase().getAttrs();
+			if (attrs == null)
+				// Logger.getInstance().log( LogEventType.ERROR,
+				// "PlayerCharacter.updateBaseStats: Missing attributes for runebase "
+				// + rune.getRuneBase().getUUID());
+				continue;
+			for (RuneBaseAttribute abr : attrs) {
+				int attrID = abr.getAttributeID();
+				int value = abr.getModValue();
+				switch (attrID) {
+				case MBServerStatics.RUNE_COST_ATTRIBUTE_ID:
+					available -= value;
+					break;
+				case MBServerStatics.RUNE_STR_ATTRIBUTE_ID:
+					str += value;
+					strMin += value;
+					break;
+				case MBServerStatics.RUNE_DEX_ATTRIBUTE_ID:
+					dex += value;
+					dexMin += value;
+					break;
+				case MBServerStatics.RUNE_CON_ATTRIBUTE_ID:
+					con += value;
+					conMin += value;
+					break;
+				case MBServerStatics.RUNE_INT_ATTRIBUTE_ID:
+					intt += value;
+					intMin += value;
+					break;
+				case MBServerStatics.RUNE_SPI_ATTRIBUTE_ID:
+					spi += value;
+					spiMin += value;
+					break;
+				case MBServerStatics.RUNE_STR_MAX_ATTRIBUTE_ID:
+					strMax += value;
+					break;
+				case MBServerStatics.RUNE_DEX_MAX_ATTRIBUTE_ID:
+					dexMax += value;
+					break;
+				case MBServerStatics.RUNE_CON_MAX_ATTRIBUTE_ID:
+					conMax += value;
+					break;
+				case MBServerStatics.RUNE_INT_MAX_ATTRIBUTE_ID:
+					intMax += value;
+					break;
+				case MBServerStatics.RUNE_SPI_MAX_ATTRIBUTE_ID:
+					spiMax += value;
+					break;
+				default:
+				}
+			}
+
+			//Set titles based on rune..
+			switch (rune.getRuneBaseID()) {
+			default:
+				break;
+
+			case 2901:	//CSR 1
+				this.title = CharacterTitle.CSR_1;
+				break;
+			case 2902:	//CSR 1
+				this.title = CharacterTitle.CSR_2;
+				break;
+			case 2903:	//CSR 1
+				this.title = CharacterTitle.CSR_3;
+				break;
+			case 2904:	//CSR 1
+				this.title = CharacterTitle.CSR_4;
+				break;
+
+			case 2910: //Wolfpack Developer
+				this.title = CharacterTitle.DEVELOPER;
+				break;
+			case 2911: //QA Test Rune
+				this.title = CharacterTitle.QA;
+				break;
+			}
+		}
+
+		//hack check. Make sure available does not go below 0.
+		//subtract from each stat until available is 0 or greater.
+		if (available < 0) {
+			while (this.spiMod.get() > 0 && available < 0) {
+				this.spiMod.decrementAndGet();
+				spi--;
+				available++;
+			}
+			while (this.conMod.get() > 0 && available < 0) {
+				this.conMod.decrementAndGet();
+				con--;
+				available++;
+			}
+			while (this.strMod.get() > 0 && available < 0) {
+				this.strMod.decrementAndGet();
+				str--;
+				available++;
+			}
+			while (this.dexMod.get() > 0 && available < 0) {
+				this.dexMod.decrementAndGet();
+				dex--;
+				available++;
+			}
+			while (this.intMod.get() > 0 && available < 0) {
+				this.intMod.decrementAndGet();
+				intt--;
+				available++;
+			}
+
+			//update database
+			this.addDatabaseJob("Stats", MBServerStatics.THIRTY_SECONDS);
+		}
+
+		this.statStrBase = (short) str;
+		this.statDexBase = (short) dex;
+		this.statConBase = (short) con;
+		this.statIntBase = (short) intt;
+		this.statSpiBase = (short) spi;
+		this.statStrMax = (short) (strMax);
+		this.statDexMax = (short) (dexMax);
+		this.statConMax = (short) (conMax);
+		this.statIntMax = (short) (intMax);
+		this.statSpiMax = (short) (spiMax);
+		this.statStrMin = (short) strMin;
+		this.statDexMin = (short) dexMin;
+		this.statConMin = (short) conMin;
+		this.statIntMin = (short) intMin;
+		this.statSpiMin = (short) spiMin;
+		this.unusedStatPoints = (short) available;
+		this.trainedStatPoints = 0;
+
+		// Testing, allow characters to have more stats then normal for formula checking
+		if (this.statStrBase > this.statStrMax)
+			this.statStrMax = this.statStrBase;
+		if (this.statDexBase > this.statDexMax)
+			this.statDexMax = this.statDexBase;
+		if (this.statConBase > this.statConMax)
+			this.statConMax = this.statConBase;
+		if (this.statIntBase > this.statIntMax)
+			this.statIntMax = this.statIntBase;
+		if (this.statSpiBase > this.statSpiMax)
+			this.statSpiMax = this.statSpiBase;
+
+		// Modified stats must be recalculated when base stats are
+		//calculateModifiedStats();
+		//update hide and seeInvis levels
+		if (this.bonuses != null) {
+			this.hidden = (int)bonuses.getFloat(ModType.Invisible, SourceType.None);
+			this.seeInvis = (int) bonuses.getFloat(ModType.SeeInvisible, SourceType.None);
+		} else {
+			this.hidden = (byte) 0;
+			this.seeInvis = (byte) 0;
+		}
+
+		//check is player is a CSR
+		this.isCSR = this.containsCSRRune();
+	}
+
+	private boolean containsCSRRune() {
+
+		if (this.race != null && this.race.getRaceType().equals(RaceType.CSRMALE))
+			return true;
+
+		if (this.baseClass != null && this.baseClass.getObjectUUID() > 2900 && this.baseClass.getObjectUUID() < 2905)
+			return true;
+
+		if (this.promotionClass != null && this.promotionClass.getObjectUUID() > 2900 && this.promotionClass.getObjectUUID() < 2905)
+			return true;
+
+		if (this.runes == null)
+			return false;
+
+		for (CharacterRune rune : this.runes) {
+
+			if (rune == null || rune.getRuneBase() == null)
+				continue;
+
+			RuneBase rb = rune.getRuneBase();
+
+			if (rb.getObjectUUID() > 2900 && rb.getObjectUUID() < 2905)
+				return true;
+			if (rb.getObjectUUID() == 2910)
+				return true;
+
+		}
+		return false;
+	}
+
+	public boolean isCSR() {
+		return this.isCSR;
+	}
+
+	public void setAsciiLastName(boolean value) {
+		this.asciiLastName = value;
+	}
+
+	public boolean _asciiLastName() {
+		return this.asciiLastName;
+	}
+
+	public static boolean hideNonAscii() {
+
+		return false;
+	}
+
+	/**
+	 * @ Calculates Modified Stats Call this when changing equipment or
+	 * add/removing effect. skips base stat modification.
+	 */
+	public void calculateModifiedStats() {
+		float strVal = this.statStrBase;
+		float dexVal = this.statDexBase;
+		float conVal = this.statConBase;
+		float intVal = this.statIntBase;
+		float spiVal = this.statSpiBase;
+
+		this.dexPenalty = getDexPenalty();
+
+		// TODO modify for equipment
+		if (this.bonuses != null) {
+			// modify for effects
+			strVal += Math.round(this.bonuses.getFloat(ModType.Attr, SourceType.Strength));
+			dexVal += Math.round(this.bonuses.getFloat(ModType.Attr, SourceType.Dexterity));
+			conVal += Math.round(this.bonuses.getFloat(ModType.Attr, SourceType.Constitution));
+			intVal += Math.round(this.bonuses.getFloat(ModType.Attr, SourceType.Intelligence));
+			spiVal += Math.round(this.bonuses.getFloat(ModType.Attr, SourceType.Spirit));
+			
+
+			// apply dex penalty for armor
+			dexVal *= this.dexPenalty;
+
+			// modify percent amounts. DO THIS LAST!
+			strVal *= (1 +Math.round(this.bonuses.getFloatPercentAll(ModType.Attr, SourceType.Strength)));
+			dexVal *= (1 +Math.round(this.bonuses.getFloatPercentAll(ModType.Attr, SourceType.Dexterity)));
+			conVal *= (1 + Math.round(this.bonuses.getFloatPercentAll(ModType.Attr, SourceType.Constitution)));
+			intVal *= (1+Math.round(this.bonuses.getFloatPercentAll(ModType.Attr, SourceType.Intelligence)));
+			spiVal *= (1+Math.round(this.bonuses.getFloatPercentAll(ModType.Attr, SourceType.Spirit)));
+		} else
+			// apply dex penalty for armor
+			dexVal *= this.dexPenalty;
+
+		// Set current stats
+		this.statStrCurrent = (strVal < 1) ? (short) 1 : (short) strVal;
+		this.statDexCurrent = (dexVal < 1) ? (short) 1 : (short) dexVal;
+		this.statConCurrent = (conVal < 1) ? (short) 1 : (short) conVal;
+		this.statIntCurrent = (intVal < 1) ? (short) 1 : (short) intVal;
+		this.statSpiCurrent = (spiVal < 1) ? (short) 1 : (short) spiVal;
+
+		// recalculate skills
+		//CharacterSkill.updateAllBaseAmounts(this);
+		// recalculate Max Health/Mana/Stamina
+		//calculateMaxHealthManaStamina();
+		// recalculate Resists
+		//this.resists.calculateResists(this);
+	}
+
+	public float getDexPenalty() {
+
+		if (this.charItemManager == null || this.charItemManager.getEquipped() == null) {
+			Logger.error( "Player " + this.getObjectUUID() + " missing equipment");
+			return 1f;
+		}
+
+		ConcurrentHashMap<Integer, Item> equipped = this.charItemManager.getEquipped();
+		float dexPenalty = 0f;
+		dexPenalty += getDexPenalty(equipped.get(MBServerStatics.SLOT_HELMET));
+		dexPenalty += getDexPenalty(equipped.get(MBServerStatics.SLOT_CHEST));
+		dexPenalty += getDexPenalty(equipped.get(MBServerStatics.SLOT_ARMS));
+		dexPenalty += getDexPenalty(equipped.get(MBServerStatics.SLOT_GLOVES));
+		dexPenalty += getDexPenalty(equipped.get(MBServerStatics.SLOT_LEGGINGS));
+		dexPenalty += getDexPenalty(equipped.get(MBServerStatics.SLOT_FEET));
+		return (1 - (dexPenalty / 100));
+	}
+
+	public static float getDexPenalty(Item armor) {
+		if (armor == null)
+			return 0f;
+		ItemBase ab = armor.getItemBase();
+		if (ab == null)
+			return 0f;
+		return ab.getDexPenalty();
+	}
+
+	public int getStrForClient() {
+		return this.statStrCurrent - this.race.getStrStart() - this.baseClass.getStrMod();
+	}
+
+	public int getDexForClient() {
+		return this.statDexCurrent - this.race.getDexStart() - this.baseClass.getDexMod();
+	}
+
+	public int getConForClient() {
+		return this.statConCurrent - this.race.getConStart() - this.baseClass.getConMod();
+	}
+
+	public int getIntForClient() {
+		return this.statIntCurrent - this.race.getIntStart() - this.baseClass.getIntMod();
+	}
+
+	public int getSpiForClient() {
+		return this.statSpiCurrent - this.race.getSpiStart() - this.baseClass.getSpiMod();
+	}
+
+	public int getTrainsAvailable() {
+		return this.trainsAvailable.get();
+	}
+
+	public void modifyTrainsAvailable(int amount) {
+		boolean worked = false;
+		while (!worked) {
+			int old = this.trainsAvailable.get();
+			int newVal = old + amount;
+			//			if (newVal < 0)
+			//				newVal = 0;
+			worked = this.trainsAvailable.compareAndSet(old, newVal);
+		}
+	}
+
+	// Reset any data that should not persist from a previous session
+	public void resetDataAtLogin() {
+		loadedObjects.clear();
+		loadedStaticObjects.clear();
+		lastStaticLoc = Vector3fImmutable.ZERO;
+		setLastTarget(GameObjectType.unknown, 0);
+		this.follow = false;
+	}
+
+	/**
+	 * @ Calculates Atr (both hands) Defense, and Damage for pc
+	 */
+	public void calculateAtrDefenseDamage() {
+		if (this.charItemManager == null || this.charItemManager.getEquipped() == null || this.skills == null) {
+			Logger.error("Player " + this.getObjectUUID() + " missing skills or equipment");
+			defaultAtrAndDamage(true);
+			defaultAtrAndDamage(false);
+			this.defenseRating = 0;
+			return;
+		}
+		ConcurrentHashMap<Integer, Item> equipped = this.charItemManager.getEquipped();
+
+		//		// Reset passives
+		//		if (this.bonuses != null) {
+		//			this.bonuses.setBool("Block", false);
+		//			this.bonuses.setBool("Parry", false);
+		//			if (this.baseClass != null && this.baseClass.getUUID() == 2502)
+		//				this.bonuses.setBool("Dodge", true);
+		//			else
+		//				this.bonuses.setBool("Dodge", false);
+		//		}
+		// calculate atr and damage for each hand
+		calculateAtrDamageForWeapon(equipped.get(MBServerStatics.SLOT_MAINHAND), true, equipped.get(MBServerStatics.SLOT_OFFHAND));
+		calculateAtrDamageForWeapon(equipped.get(MBServerStatics.SLOT_OFFHAND), false, equipped.get(MBServerStatics.SLOT_MAINHAND));
+
+		// No Defense while in DeathShroud
+		if (this.effects != null && this.effects.containsKey("DeathShroud"))
+			this.defenseRating = (short) 0;
+		else {
+			// calculate defense for equipment
+			float defense = this.statDexCurrent * 2;
+			defense += getShieldDefense(equipped.get(MBServerStatics.SLOT_OFFHAND));
+			defense += getArmorDefense(equipped.get(MBServerStatics.SLOT_HELMET));
+			defense += getArmorDefense(equipped.get(MBServerStatics.SLOT_CHEST));
+			defense += getArmorDefense(equipped.get(MBServerStatics.SLOT_ARMS));
+			defense += getArmorDefense(equipped.get(MBServerStatics.SLOT_GLOVES));
+			defense += getArmorDefense(equipped.get(MBServerStatics.SLOT_LEGGINGS));
+			defense += getArmorDefense(equipped.get(MBServerStatics.SLOT_FEET));
+			defense += getWeaponDefense(equipped);
+
+			if (this.bonuses != null) {
+				// add any bonuses
+				defense += (short) this.bonuses.getFloat(ModType.DCV, SourceType.None);
+
+				// Finally multiply any percent modifiers. DO THIS LAST!
+				float pos_Bonus = this.bonuses.getFloatPercentPositive(ModType.DCV, SourceType.None);
+				defense = (short) (defense * (1 + pos_Bonus));
+
+				//Lucky rune applies next
+				//applied runes will be calculated and added to the normal bonuses. no need for this garbage anymore
+				//defense = (short) (defense * (1 + ((float) this.bonuses.getShort("rune.Defense") / 100)));
+
+				//and negative percent modifiers
+				//already done...
+				float neg_Bonus = this.bonuses.getFloatPercentNegative(ModType.DCV, SourceType.None);
+				defense = (short) (defense *(1 + neg_Bonus));
+
+			} else
+				// TODO add error log here
+				Logger.error( "Error: missing bonuses");
+
+			defense = (defense < 1) ? 1 : defense;
+			this.defenseRating = (short) (defense + 0.5f);
+		}
+	}
+
+	/**
+	 * @ Calculates Atr, and Damage for each weapon
+	 */
+	private void calculateAtrDamageForWeapon(Item weapon, boolean mainHand, Item otherHand) {
+
+		// make sure weapon exists
+		boolean noWeapon = false;
+		ItemBase wb = null;
+		if (weapon == null)
+			noWeapon = true;
+		else {
+			ItemBase ib = weapon.getItemBase();
+			if (ib == null)
+				noWeapon = true;
+			else
+				if (!ib.getType().equals(ItemType.WEAPON)) {
+					defaultAtrAndDamage(mainHand);
+					return;
+				} else
+					wb = ib;
+		}
+		float skillPercentage, masteryPercentage;
+		float mastDam;
+		float min, max;
+		float speed = 20f;
+		boolean strBased = false;
+
+		ItemBase wbMain = (weapon != null) ? weapon.getItemBase() : null;
+		ItemBase wbOff = (otherHand != null) ? otherHand.getItemBase() : null;
+
+		// get skill percentages and min and max damage for weapons
+		if (noWeapon) {
+			if (mainHand) {
+				Item off = this.charItemManager.getEquipped().get(MBServerStatics.SLOT_OFFHAND);
+				if (off != null && off.getItemBase() != null && off.getItemBase().getType().equals(ItemType.WEAPON))
+					this.rangeHandOne = 10 * (1 + (this.statStrBase / 600)); // Set
+				// to
+				// no
+				// weapon
+				// range
+				else
+					this.rangeHandOne = -1; // set to do not attack
+			} else
+				this.rangeHandTwo = -1; // set to do not attack
+
+			skillPercentage = getModifiedAmount(this.skills.get("Unarmed Combat"));
+			masteryPercentage = getModifiedAmount(this.skills.get("Unarmed Combat Mastery"));
+			if (masteryPercentage == 0f)
+				mastDam = CharacterSkill.getQuickMastery(this, "Unarmed Combat Mastery");
+			else
+				mastDam = masteryPercentage;
+			// TODO Correct these
+			min = 1;
+			max = 3;
+		} else {
+			if (mainHand)
+				this.rangeHandOne = weapon.getItemBase().getRange() * (1 + (this.statStrBase / 600));
+			else
+				this.rangeHandTwo = weapon.getItemBase().getRange() * (1 + (this.statStrBase / 600));
+
+			if (this.bonuses != null){
+				float range_bonus = 1 + this.bonuses.getFloatPercentAll(ModType.WeaponRange, SourceType.None);
+	
+				if (mainHand)
+					this.rangeHandOne *= range_bonus;
+				else
+					this.rangeHandTwo *= range_bonus;
+
+			}
+			skillPercentage = getModifiedAmount(this.skills.get(wb.getSkillRequired()));
+			masteryPercentage = getModifiedAmount(this.skills.get(wb.getMastery()));
+			if (masteryPercentage == 0f)
+				mastDam = 0f;
+			//				mastDam = CharacterSkill.getQuickMastery(this, wb.getMastery());
+			else
+				mastDam = masteryPercentage;
+			min = (float) wb.getMinDamage();
+			max = (float) wb.getMaxDamage();
+			strBased = wb.isStrBased();
+
+			//
+			// Add parry bonus for weapon and allow parry if needed
+				
+			//					// Only Fighters and Thieves can Parry
+			//					if ((this.baseClass != null && this.baseClass.getUUID() == 2500)
+			//							|| (this.promotionClass != null && this.promotionClass.getUUID() == 2520)) {
+			//						if (wbMain == null || wbMain.getRange() < MBServerStatics.RANGED_WEAPON_RANGE)
+			//							if (wbOff == null || wbOff.getRange() < MBServerStatics.RANGED_WEAPON_RANGE)
+			//								this.bonuses.setBool("Parry", true);
+			//					}
+			//				}
+		}
+
+		if (this.effects != null && this.effects.containsKey("DeathShroud"))
+			// No Atr in deathshroud.
+			if (mainHand)
+				this.atrHandOne = (short) 0;
+			else
+				this.atrHandTwo = (short) 0;
+		else {
+			// calculate atr
+			float atr = 0;
+			atr += (int) skillPercentage * 4f; //<-round down skill% -
+			atr += (int) masteryPercentage * 3f;
+			if (this.statStrCurrent > this.statDexCurrent)
+				atr += statStrCurrent / 2;
+			else
+				atr += statDexCurrent / 2;
+
+			// add in any bonuses to atr
+			if (this.bonuses != null) {
+				// Add any base bonuses
+				atr += this.bonuses.getFloat(ModType.OCV, SourceType.None);
+
+				// Finally use any multipliers. DO THIS LAST!
+				float pos_Bonus =  (1 + this.bonuses.getFloatPercentPositive(ModType.OCV, SourceType.None));
+				atr *= pos_Bonus; 
+
+				// next precise
+				//runes will have their own bonuses.
+			//	atr *= (1 + ((float) this.bonuses.getShort("rune.Attack") / 100));
+
+				//and negative percent modifiers
+				float neg_Bonus = this.bonuses.getFloatPercentNegative(ModType.OCV, SourceType.None);
+				
+				atr *= (1 +  neg_Bonus);
+			}
+
+			atr = (atr < 1) ? 1 : atr;
+
+			// set atr
+			if (mainHand)
+				this.atrHandOne = (short) (atr + 0.5f);
+			else
+				this.atrHandTwo = (short) (atr + 0.5f);
+		}
+
+		//calculate speed
+		if (wb != null)
+			speed = wb.getSpeed();
+		else
+			speed = 20f; //unarmed attack speed
+			if (weapon != null)
+			speed *= (1 + weapon.getBonusPercent(ModType.WeaponSpeed, SourceType.None));
+			speed *=  (1 + this.bonuses.getFloatPercentAll(ModType.AttackDelay, SourceType.None));
+		if (speed < 10)
+			speed = 10;
+
+		//add min/max damage bonuses for weapon
+		if (weapon != null) {
+			// Add any base bonuses
+			
+			min +=  weapon.getBonus(ModType.MinDamage, SourceType.None);
+			max +=  weapon.getBonus(ModType.MaxDamage, SourceType.None);
+			
+			min +=  weapon.getBonus(ModType.MeleeDamageModifier, SourceType.None);
+			max +=  weapon.getBonus(ModType.MeleeDamageModifier, SourceType.None);
+			// Finally use any multipliers. DO THIS LAST!
+			
+			float percentMinDamage = 1;
+			float percentMaxDamage = 1;
+			
+			percentMinDamage +=  weapon.getBonusPercent(ModType.MinDamage, SourceType.None);
+			percentMinDamage +=  weapon.getBonusPercent(ModType.MeleeDamageModifier, SourceType.None);
+			
+			percentMaxDamage += weapon.getBonusPercent(ModType.MaxDamage, SourceType.None);
+			percentMaxDamage += weapon.getBonusPercent(ModType.MeleeDamageModifier, SourceType.None);
+			
+			
+			
+			min *= percentMinDamage;
+			max *= percentMaxDamage;
+		}
+
+		//if duel wielding, cut damage by 30%
+		if (otherHand != null) {
+			ItemBase ibo = otherHand.getItemBase();
+			if (ibo != null && ibo.getType().equals(ItemType.WEAPON)) {
+				min *= 0.7f;
+				max *= 0.7f;
+			}
+		}
+
+		// calculate damage
+		float minDamage;
+		float maxDamage;
+		float pri = (strBased) ? (float) this.statStrCurrent : (float) this.statDexCurrent;
+		float sec = (strBased) ? (float) this.statDexCurrent : (float) this.statStrCurrent;
+		minDamage = (float) (min * ((0.0315f * Math.pow(pri, 0.75f)) + (0.042f * Math.pow(sec, 0.75f)) + (0.01f * ((int) skillPercentage + (int) mastDam))));
+		maxDamage = (float) (max * ((0.0785f * Math.pow(pri, 0.75f)) + (0.016f * Math.pow(sec, 0.75f)) + (0.0075f * ((int) skillPercentage + (int) mastDam))));
+		minDamage = (float) ((int) (minDamage + 0.5f)); //round to nearest decimal
+		maxDamage = (float) ((int) (maxDamage + 0.5f)); //round to nearest decimal
+
+		// Half damage if in death shroud
+		if (this.effects != null && this.effects.containsKey("DeathShroud")) {
+			minDamage *= 0.5f;
+			maxDamage *= 0.5f;
+		}
+
+		// add in any bonuses to damage
+		if (this.bonuses != null) {
+			// Add any base bonuses
+			minDamage += this.bonuses.getFloat(ModType.MinDamage, SourceType.None);
+			maxDamage += this.bonuses.getFloat(ModType.MaxDamage, SourceType.None);
+			
+			minDamage += this.bonuses.getFloat(ModType.MeleeDamageModifier, SourceType.None);
+			maxDamage += this.bonuses.getFloat(ModType.MeleeDamageModifier, SourceType.None);
+			// Finally use any multipliers. DO THIS LAST!
+			
+			float percentMinDamage = 1;
+			float percentMaxDamage = 1;
+			
+			percentMinDamage +=  this.bonuses.getFloatPercentAll(ModType.MinDamage, SourceType.None);
+			percentMinDamage +=  this.bonuses.getFloatPercentAll(ModType.MeleeDamageModifier, SourceType.None);
+			
+			percentMaxDamage += this.bonuses.getFloatPercentAll(ModType.MaxDamage, SourceType.None);
+			percentMaxDamage += this.bonuses.getFloatPercentAll(ModType.MeleeDamageModifier, SourceType.None);
+			
+			minDamage *= percentMinDamage;
+			maxDamage *= percentMaxDamage;
+			
+		}
+
+		// set damages
+		if (mainHand) {
+			this.minDamageHandOne =  (int) minDamage;
+			this.maxDamageHandOne =  (int) maxDamage;
+			this.speedHandOne = speed;
+		} else {
+			this.minDamageHandTwo =  (int) minDamage;
+			this.maxDamageHandTwo =  (int) maxDamage;
+			this.speedHandTwo = speed;
+		}
+	}
+
+	/**
+	 * @ Calculates Defense for shield
+	 */
+	private float getShieldDefense(Item shield) {
+		if (shield == null)
+			return 0;
+		ItemBase ab = shield.getItemBase();
+		if (ab == null || !ab.isShield())
+			return 0;
+		CharacterSkill blockSkill = this.skills.get("Block");
+		float skillMod;
+		if (blockSkill == null) {
+			skillMod = 0;
+		} else
+			skillMod = blockSkill.getModifiedAmount();
+
+		float def = ab.getDefense();
+		//apply item defense bonuses
+		if (shield != null){
+			def += shield.getBonus(ModType.DR, SourceType.None);
+			def *= (1 + shield.getBonusPercent(ModType.DR, SourceType.None));
+
+		}
+		
+		// float val = ((float)ab.getDefense()) * (1 + (skillMod / 100));
+		return (def * (1 + ((int) skillMod / 100f)));
+	}
+
+	public void setPassives() {
+		if (this.bonuses != null) {
+			ConcurrentHashMap<Integer, Item> equipped = this.charItemManager.getEquipped();
+			Item off = equipped.get(MBServerStatics.SLOT_OFFHAND);
+			Item main = equipped.get(MBServerStatics.SLOT_MAINHAND);
+			ItemBase wbMain = null;
+			ItemBase wbOff = null;
+			if (main != null)
+				wbMain = main.getItemBase();
+			if (off != null)
+				wbOff = off.getItemBase();
+
+			//set block if block found
+			this.bonuses.setBool(ModType.Block,SourceType.None, false);
+			if (this.baseClass != null && (this.baseClass.getObjectUUID() == 2500 || this.baseClass.getObjectUUID() == 2501))
+				if (off != null && off.getItemBase() != null && off.getItemBase().isShield())
+					this.bonuses.setBool(ModType.Block,SourceType.None, true);
+
+			//set dodge if rogue
+			if (this.baseClass != null && this.baseClass.getObjectUUID() == 2502)
+				this.bonuses.setBool(ModType.Dodge,SourceType.None, true);
+			else
+				this.bonuses.setBool(ModType.Dodge,SourceType.None, false);
+
+			//set parry if fighter or thief and no invalid weapon found
+			this.bonuses.setBool(ModType.Parry,SourceType.None, false);
+			if ((this.baseClass != null && this.baseClass.getObjectUUID() == 2500)
+					|| (this.promotionClass != null && this.promotionClass.getObjectUUID() == 2520))
+				if (wbMain == null || wbMain.getRange() < MBServerStatics.RANGED_WEAPON_RANGE)
+					if (wbOff == null || wbOff.getRange() < MBServerStatics.RANGED_WEAPON_RANGE)
+						this.bonuses.setBool(ModType.Parry,SourceType.None, true);
+
+		}
+
+	}
+
+	/**
+	 * @ Calculates Defense for armor
+	 */
+	private float getArmorDefense(Item armor) {
+
+		if (armor == null)
+			return 0;
+
+		ItemBase ib = armor.getItemBase();
+
+		if (ib == null)
+			return 0;
+
+		if (!ib.getType().equals(ItemType.ARMOR))
+			return 0;
+		if (ib.getSkillRequired().isEmpty())
+			return ib.getDefense();
+		CharacterSkill armorSkill = this.skills.get(ib.getSkillRequired());
+		if (armorSkill == null) {
+			Logger.error( "Player " + this.getObjectUUID()
+			+ " has armor equipped without the nescessary skill to equip it");
+			return ib.getDefense();
+		}
+
+		float def = ib.getDefense();
+		//apply item defense bonuses
+		if (armor != null){
+			def += armor.getBonus(ModType.DR, SourceType.None);
+			def *= (1 + armor.getBonusPercent(ModType.DR, SourceType.None));
+		}
+	
+
+		return (def * (1 + ((int) armorSkill.getModifiedAmount() / 50f)));
+	}
+
+	/**
+	 * @ Calculates Defense for weapon
+	 */
+	private float getWeaponDefense(ConcurrentHashMap<Integer, Item> equipped) {
+		Item weapon = equipped.get(MBServerStatics.SLOT_MAINHAND);
+		ItemBase wb = null;
+		CharacterSkill skill, mastery;
+		float val = 0;
+		boolean unarmed = false;
+		if (weapon == null) {
+			weapon = equipped.get(MBServerStatics.SLOT_OFFHAND);
+			if (weapon == null || weapon.getItemBase().isShield())
+				unarmed = true;
+			else
+				wb = weapon.getItemBase();
+		} else
+			wb = weapon.getItemBase();
+		if (wb == null)
+			unarmed = true;
+		if (unarmed) {
+			skill = this.skills.get("Unarmed Combat");
+			mastery = this.skills.get("Unarmed Combat Mastery");
+		} else {
+			skill = this.skills.get(wb.getSkillRequired());
+			mastery = this.skills.get(wb.getMastery());
+		}
+		if (skill != null)
+			val += (int) skill.getModifiedAmount() / 2f;
+		if (mastery != null)
+			val += (int) mastery.getModifiedAmount() / 2f;
+		return val;
+	}
+
+	private static float getModifiedAmount(CharacterSkill skill) {
+		if (skill == null)
+			return 0f;
+		return skill.getModifiedAmount();
+	}
+
+	//Call this function to recalculate granted skills and powers for player
+	public synchronized void calculateSkills() {
+		//tell the player to applyBonuses because something has changed
+
+		runSkillCalc();
+
+		//start running the skill/power calculations
+	}
+
+	//Don't call this function directly. linked from pc.calculateSkills()
+	//through SkillCalcJob. Designed to only run from one worker thread
+	public void runSkillCalc() {
+		try {
+
+			//see if any new skills or powers granted
+			CharacterSkill.calculateSkills(this);
+			// calculate granted Trains in powers.
+			CharacterPower.grantTrains(this);
+			//see if any new powers unlocked from previous check
+			CharacterPower.calculatePowers(this);
+
+		} catch (Exception e) {
+		}
+
+	}
+
+	public static void InitializeSkillsOnLoad(PlayerCharacter pc) {
+		try {
+			{
+
+				//see if any new skills or powers granted
+				CharacterSkill.calculateSkills(pc);
+
+				// calculate granted Trains in powers.
+				CharacterPower.grantTrains(pc);
+
+				//see if any new powers unlocked from previous check
+				CharacterPower.calculatePowers(pc);
+			}
+		} catch (Exception e) {
+			Logger.error( e.getMessage());
+		}
+
+	}
+
+	//calculate item bonuses here
+	public void calculateItemBonuses() {
+		if (this.charItemManager == null || this.bonuses == null)
+			return;
+		ConcurrentHashMap<Integer, Item> equipped = this.charItemManager.getEquipped();
+		for (Item item : equipped.values()) {
+			ItemBase ib = item.getItemBase();
+			if (ib == null)
+				continue;
+			//TODO add effect bonuses in here for equipped items
+		}
+	}
+
+	/**
+	 * @ Defaults ATR, Defense and Damage for player
+	 */
+	private void defaultAtrAndDamage(boolean mainHand) {
+		if (mainHand) {
+			this.atrHandOne = 0;
+			this.minDamageHandOne = 0;
+			this.maxDamageHandOne = 0;
+			this.rangeHandOne = -1;
+			this.speedHandOne = 20;
+		} else {
+			this.atrHandTwo = 0;
+			this.minDamageHandTwo = 0;
+			this.maxDamageHandTwo = 0;
+			this.rangeHandTwo = -1;
+			this.speedHandTwo = 20;
+		}
+	}
+
+	public void calculateMaxHealthManaStamina() {
+		float h = 1f;
+		float m = 0f;
+		float s = 0f;
+		float baseHealth = 15f;
+		float baseMana = 5f;
+		float baseStamina = 1f;
+		float promoHealth = 0f;
+		float promoMana = 0f;
+		float promoStamina = 0f;
+		float raceHealth = 0f;
+		float raceMana = 0f;
+		float raceStamina = 0f;
+		float toughness = 0f;
+		float athletics = 0f;
+
+		//get baseclass modifiers
+		if (this.baseClass != null) {
+			baseHealth = this.baseClass.getHealthMod();
+			baseMana = this.baseClass.getManaMod();
+			baseStamina = this.baseClass.getStaminaMod();
+		} else {
+			//TODO log error here
+		}
+
+		//get promotion modifiers
+		if (this.promotionClass != null) {
+			promoHealth = this.promotionClass.getHealthMod();
+			promoMana = this.promotionClass.getManaMod();
+			promoStamina = this.promotionClass.getStaminaMod();
+		}
+
+		// next get racial modifer
+		if (this.race != null) {
+			raceHealth += this.race.getHealthBonus();
+			raceMana += this.race.getManaBonus();
+			raceStamina += this.race.getStaminaBonus();
+		} else {
+			//TODO log error here
+		}
+
+		//Get level modifers
+		float f = 0;
+		float g = 0;
+		if (this.level < 10 || this.promotionClass == null)
+			f = this.level;
+		else if (this.level < 20) {
+			f = this.level;
+			g = this.level - 9;
+		} else if (level < 30) {
+			f = (float) (19 + (this.level - 19) * 0.8);
+			g = (float) (10 + (this.level - 19) * 0.8);
+		} else if (level < 40) {
+			f = (float) (27 + (this.level - 29) * 0.6);
+			g = (float) (18 + (this.level - 29) * 0.6);
+		} else if (level < 50) {
+			f = (float) (33 + (this.level - 39) * 0.4);
+			g = (float) (24 + (this.level - 39) * 0.4);
+		} else if (level < 60) {
+			f = (float) (37 + (this.level - 49) * 0.2);
+			g = (float) (28 + (this.level - 49) * 0.2);
+		} else {
+			f = (float) (39 + (this.level - 59) * 0.1);
+			g = (float) (30 + (this.level - 59) * 0.1);
+		}
+
+		//get toughness and athletics amount
+		if (this.skills != null) {
+			if (this.skills.containsKey("Toughness"))
+				toughness = this.skills.get("Toughness").getModifiedAmount();
+			if (this.skills.containsKey("Athletics"))
+				athletics = this.skills.get("Athletics").getModifiedAmount();
+		}
+
+		h = (((f * baseHealth) + (g * promoHealth)) * (0.3f + (0.005f * this.statConCurrent)) + (this.statConCurrent + raceHealth)) * (1 + (int) toughness / 400f);
+		m = ((f * baseMana) + (g * promoMana)) * (0.3f + (0.005f * this.statSpiCurrent)) + (this.statSpiCurrent + raceMana);
+		s = (((f * baseStamina) + (g * promoStamina)) * (0.3f + (0.005f * this.statConCurrent)) + (this.statConCurrent + raceStamina)) * (1 + (int) athletics / 300f);
+
+	//	s = f * (baseStamina + 1.75f) * .5f + this.statConCurrent + raceStamina;
+		// Apply any bonuses from runes and effects
+		if (this.bonuses != null) {
+			
+
+			//apply effects
+			h += this.bonuses.getFloat(ModType.HealthFull, SourceType.None);
+			m += this.bonuses.getFloat(ModType.ManaFull, SourceType.None);
+			s += this.bonuses.getFloat(ModType.StaminaFull, SourceType.None);
+
+			h *= (1 +this.bonuses.getFloatPercentAll(ModType.HealthFull, SourceType.None)) ;
+			m *= (1+this.bonuses.getFloatPercentAll(ModType.ManaFull, SourceType.None));
+			s *= (1+this.bonuses.getFloatPercentAll(ModType.StaminaFull, SourceType.None));
+		
+		}
+
+		// Set max health, mana and stamina
+		if (h > 0)
+			this.healthMax = h;
+		else
+			this.healthMax = 1;
+		if (m > -1)
+			this.manaMax = m;
+		else
+			this.manaMax = 0;
+		if (s > -1)
+			this.staminaMax = s;
+		else
+			this.staminaMax = 0;
+
+		// Update health, mana and stamina if needed
+		if (this.getCurrentHitpoints() > this.healthMax)
+			this.setHealth(this.healthMax);
+		if (this.mana.get() > this.manaMax)
+			this.mana.set(this.manaMax);
+		if (this.stamina.get() > this.staminaMax)
+			this.stamina.set(staminaMax);
+	}
+
+	@Override
+	public float getPassiveChance(String type, int attackerLevel, boolean fromCombat) {
+		if (this.skills == null || this.bonuses == null)
+			return 0f;
+		
+		ModType modType = ModType.GetModType(type);
+
+		// must be allowed to use this passive
+		if (!this.bonuses.getBool(modType, SourceType.None))
+			return 0f;
+
+		// must not be stunned
+		if (this.bonuses.getBool(ModType.Stunned, SourceType.None))
+			return 0f;
+
+		// Get base skill amount
+		CharacterSkill sk = this.skills.get(type);
+		float amount;
+		if (sk == null)
+			amount = CharacterSkill.getQuickMastery(this, type);
+		else
+			amount = sk.getModifiedAmount();
+
+		// Add bonuses
+		amount += this.bonuses.getFloat(modType, SourceType.None);
+
+		// Add item bonuses and return
+		if (type.equals(ModType.Dodge) && !fromCombat)
+			return ((amount / 4) - attackerLevel + this.getLevel()) / 4;
+		else
+			return (amount - attackerLevel + this.getLevel()) / 4;
+	}
+
+	public float getPassiveChance1(ModType modType, SourceType sourceType, int attackerLevel, boolean fromCombat) {
+		if (this.skills == null || this.bonuses == null)
+			return 0f;
+
+		// must be allowed to use this passive
+		if (!this.bonuses.getBool(modType, sourceType))
+			return 0f;
+
+		// must not be stunned
+		if (this.bonuses.getBool(ModType.Stunned, SourceType.None))
+			return 0f;
+
+		// Get base skill amount
+		CharacterSkill sk = this.skills.get(sourceType.name());
+		float amount;
+		if (sk == null)
+			amount = CharacterSkill.getQuickMastery(this, modType.name());
+		else
+			amount = sk.getModifiedAmount();
+
+		// Add bonuses
+		amount += this.bonuses.getFloat(modType, sourceType);
+
+		// Add item bonuses and return
+		if (sourceType.equals(SourceType.Dodge) && !fromCombat)
+			return ((amount / 4) - attackerLevel + this.getLevel()) / 4;
+		else
+			return (amount - attackerLevel + this.getLevel()) / 4;
+	}
+
+	public float getRegenModifier(ModType type) {
+		float regen = 1f;
+
+		if (this.bonuses != null)
+			// get regen bonus from effects
+			regen = this.bonuses.getRegen(type);
+		return regen;
+	}
+
+	@Override
+	public boolean canBeLooted() {
+		return !this.isAlive();
+	}
+
+	@Override
+	public void setLevel(short targetLevel) {
+
+		short tmpLevel;
+
+		tmpLevel = targetLevel;
+
+		tmpLevel = (short) Math.min(tmpLevel, 75);
+
+		while (this.level < tmpLevel) {
+			grantXP(Experience.getBaseExperience(tmpLevel) - this.exp);
+		}
+
+	}
+	
+	public void ResetLevel(short targetLevel) {
+		
+		if (targetLevel > 13){
+			ChatManager.chatSystemError(this, "Please choose a level between 1 and 13.");
+			return;
+		}
+		this.promotionClass = null;
+		if (targetLevel > 10){
+		this.level = 10;
+		this.exp = Experience.getBaseExperience(11);
+		int maxEXP = Experience.getBaseExperience(targetLevel); //target level exp;
+		this.overFlowEXP = maxEXP - this.exp;
+		}else{
+			this.level = targetLevel;
+			this.exp = Experience.getBaseExperience(level);
+			this.overFlowEXP = 0;
+		}
+		
+		
+		for (CharacterSkill skill: this.getSkills().values()){
+			skill.reset(this, true);
+		}
+		
+		for (CharacterPower power : this.getPowers().values()){
+			power.reset(this);
+		}
+		
+		this.recalculatePlayerStats(initialized);
+		this.recalculate();
+		
+		ChatManager.chatSystemInfo(this, "Character reset to " + targetLevel+ ". All training points have been refunded. Relog to update changes on client.");
+
+	}
+
+	@Override
+	public void removeFromCache() {
+		Logger.info("Removing " + this.getName() + " from Object Cache.");
+
+		for (Item e : this.charItemManager.getEquipped().values()) {
+			e.removeFromCache();
+		}
+
+		for (Item i : this.charItemManager.getInventory(true)) {
+			i.removeFromCache();
+		}
+
+		for (Item b : this.charItemManager.getBank()) {
+			b.removeFromCache();
+		}
+
+		if (this.account.getLastCharIDUsed() == this.getObjectUUID())
+			for (Item v : this.charItemManager.getVault()) {
+				v.removeFromCache();
+			}
+
+		for (CharacterSkill cs : this.getSkills().values()) {
+			cs.removeFromCache();
+		}
+
+		for (CharacterPower ps : this.getPowers().values()) {
+			ps.removeFromCache();
+		}
+
+		for (CharacterRune cr : this.runes) {
+			cr.removeFromCache();
+		}
+
+		super.removeFromCache();
+	}
+
+	public static String getFirstName(int tableId) {
+
+		PlayerCharacter player;
+
+		if (tableId == 0)
+			return "";
+
+		player = (PlayerCharacter) DbManager.getObject(GameObjectType.PlayerCharacter, tableId);
+
+		return player.getFirstName();
+	}
+
+	public static PlayerCharacter getFromCache(int id) {
+		return (PlayerCharacter) DbManager.getFromCache(GameObjectType.PlayerCharacter, id);
+	}
+	
+	public static PlayerCharacter getByFirstName(String name) {
+		
+		PlayerCharacter returnPlayer = null;
+		for (AbstractGameObject ago : DbManager.getList(GameObjectType.PlayerCharacter)){
+			PlayerCharacter cachePlayer = (PlayerCharacter)ago;
+			if (!name.equalsIgnoreCase(cachePlayer.getFirstName()))
+				continue;
+			if (cachePlayer.isDeleted())
+				continue;
+			returnPlayer = cachePlayer;
+			break;
+		}
+		
+		return returnPlayer;
+	}
+
+	public static PlayerCharacter getPlayerCharacter(int uuid) {
+
+		PlayerCharacter outPlayer;
+
+		outPlayer = DbManager.PlayerCharacterQueries.GET_PLAYER_CHARACTER(uuid);
+
+		if (outPlayer != null)
+			return outPlayer;
+
+		return (PlayerCharacter) DbManager.getFromCache(GameObjectType.PlayerCharacter, uuid);
+	}
+
+	public void storeIgnoreListDB() {
+
+	}
+
+	public void updateSkillsAndPowersToDatabase() {
+		if (this.skills != null)
+			for (CharacterSkill skill : this.skills.values()) {
+				DbManager.CharacterSkillQueries.UPDATE_TRAINS(skill);
+				if (this.powers != null)
+					for (CharacterPower power : this.powers.values()) {
+						DbManager.CharacterPowerQueries.UPDATE_TRAINS(power);
+					}
+			}
+	}
+
+	@Override
+	public void updateDatabase() {
+	}
+
+	@Override
+	public void runAfterLoad() {
+
+		// Create player bounds object
+
+		//		if ((MBServer.getApp() instanceof engine.server.world.WorldServer))
+		//			DbManager.GuildQueries.LOAD_GUILD_HISTORY_FOR_PLAYER(this);
+
+		Bounds playerBounds = Bounds.borrow();
+		playerBounds.setBounds(this.getLoc());
+		this.setBounds(playerBounds);
+	}
+
+	@Override
+	protected ConcurrentHashMap<Integer, CharacterPower> initializePowers() {
+		return DbManager.CharacterPowerQueries.GET_POWERS_FOR_CHARACTER(this);
+	}
+
+	@Override
+	public final void setFirstName(final String name) {
+		super.setFirstName(name);
+	}
+
+	@Override
+	public void setLastName(final String name) {
+		super.setLastName(name);
+	}
+
+	@Override
+	public short getLevel() {
+		return this.getPCLevel();
+	}
+
+	@Override
+	public boolean asciiLastName() {
+		return this._asciiLastName();
+	}
+
+	@Override
+	public void setGuild(Guild value) {
+		
+		if (value == null)
+			value = Guild.getErrantGuild();
+		
+		int guildID = 0;
+		
+		if (!value.isErrant())
+			guildID = value.getObjectUUID();
+		DbManager.PlayerCharacterQueries.UPDATE_GUILD(this, guildID);
+		super.setGuild(value);
+
+		// Player changed guild so let's invalidate the login server
+		// cache to reflect this event.
+
+		//Update player bind location;
+		
+		Building cityTol = null;
+		
+		if (value.getOwnedCity() != null)
+		 cityTol = value.getOwnedCity().getTOL();
+		
+		this.setBindBuildingID(cityTol != null ? cityTol.getObjectUUID() : 0);
+			//update binds, checks for nation tol if guild tol == null;
+		 PlayerCharacter.getUpdatedBindBuilding(this);
+		
+		
+		DbManager.AccountQueries.INVALIDATE_LOGIN_CACHE(this.getObjectUUID(), "character");
+	}
+
+	public long getSummoner(int summoner) {
+		synchronized (this.summoners) {
+			if (!this.summoners.containsKey(summoner))
+				return 0;
+			return this.summoners.get(summoner);
+		}
+	}
+
+	public void addSummoner(int summoner, long time) {
+		synchronized (this.summoners) {
+			this.summoners.put(summoner, time);
+		}
+	}
+
+	public void removeSummoner(int summoner) {
+		synchronized (this.summoners) {
+			if (this.summoners.containsKey(summoner))
+				this.summoners.remove(summoner);
+		}
+	}
+
+	public boolean commandSiegeMinion(Mob toCommand) {
+		if (!toCommand.isSiege())
+			return false;
+		if (toCommand.isPet() || !toCommand.isAlive())
+			return false;
+		
+		if (toCommand.getGuild().getNation() != this.getGuild().getNation())
+			return false;
+
+		if (this.pet != null) {
+			Mob currentPet = this.pet;
+			if (!currentPet.isSiege()) {
+
+				currentPet.setCombatTarget(null);
+				currentPet.setState(STATE.Disabled);
+
+				if (currentPet.getParentZone() != null)
+
+					currentPet.getParentZone().zoneMobSet.remove(currentPet);
+
+				try {
+					currentPet.clearEffects();
+				}catch(Exception e){
+					Logger.error( e.getMessage());
+				}
+				currentPet.getPlayerAgroMap().clear();
+				WorldGrid.RemoveWorldObject(currentPet);
+				DbManager.removeFromCache(currentPet);
+
+			} else
+				if (currentPet.isSiege()) {
+					currentPet.setMob();
+					currentPet.setOwner(null);
+					currentPet.setCombatTarget(null);
+					if (currentPet.isAlive())
+						WorldGrid.updateObject(currentPet);
+				}
+		}
+
+		toCommand.setPet(this, false);
+		this.setPet(toCommand);
+		toCommand.setCombatTarget(null);
+		PetMsg petMsg = new PetMsg(6, toCommand);
+		Dispatch dispatch = Dispatch.borrow(this, petMsg);
+		DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.PRIMARY);
+
+		if (toCommand.isAlive())
+			WorldGrid.updateObject(toCommand);
+		return true;
+	}
+
+	public boolean isNoTeleScreen() {
+		return noTeleScreen;
+	}
+
+	public void setNoTeleScreen(boolean noTeleScreen) {
+		this.noTeleScreen = noTeleScreen;
+	}
+
+	private double getDeltaTime() {
+
+		return (System.currentTimeMillis() - lastUpdateTime) * .001f;
+	}
+	
+	private double getStamDeltaTime() {
+
+		return (System.currentTimeMillis() - lastStamUpdateTime) * .001f;
+	}
+
+	public boolean isFlying() {
+
+		return this.getAltitude() > 0;
+
+	}
+
+	public boolean isSwimming() {
+
+		// If char is flying they aren't quite swimming
+		try {
+			if (this.isFlying())
+				return false;
+
+			Zone zone = ZoneManager.findSmallestZone(this.getLoc());
+
+			if (zone.getSeaLevel() != 0) {
+
+				float localAltitude = this.getLoc().y + this.centerHeight;
+				if (localAltitude < zone.getSeaLevel())
+					return true;
+			} else {
+				if (this.getLoc().y + this.centerHeight < 0)
+					return true;
+			}
+		} catch (Exception e){
+			Logger.info(this.getName() + e);
+		}
+
+		return false;
+	}
+	
+	public boolean isSwimming(Vector3fImmutable currentLoc) {
+
+		// If char is flying they aren't quite swimming
+		try{
+
+			float localAltitude = HeightMap.getWorldHeight(currentLoc);
+
+			Zone zone = ZoneManager.findSmallestZone(currentLoc);
+
+			if (zone.getSeaLevel() != 0){
+
+				if (localAltitude < zone.getSeaLevel())
+					return true;
+			}else{
+				if (localAltitude < 0)
+					return true;
+			}
+		}catch(Exception e){
+			Logger.info(this.getName() + e);
+		}
+
+		return false;
+	}
+
+	// Method is called by Server Heartbeat simulation tick.
+	// Stat regen and transform updates should go in here.
+
+	@Override
+	public void update() {
+
+		if (this.updateLock.writeLock().tryLock()){
+			try{
+				
+				if (!this.isAlive())
+					return;
+				
+					updateLocation();
+					updateMovementState();
+					updateRegen();
+
+				if (this.getStamina() < 10){
+					if (this.getAltitude() > 0 || this.getDesiredAltitude() > 0){
+						PlayerCharacter.GroundPlayer(this);
+						updateRegen();
+					}
+				}
+
+					RealmMap.updateRealm(this);
+					updateBlessingMessage();
+			
+				this.safeZone = this.isInSafeZone();
+
+			}catch(Exception e){
+				Logger.error(e);
+			}finally{
+				this.updateLock.writeLock().unlock();
+			}
+		}
+
+	}
+	@Override
+	public void updateFlight() {
+		
+		if (this.getAltitude() == 0 && this.getTakeOffTime() == 0)
+			return;
+		
+		if (this.getTakeOffTime() == 0)
+			return;
+		
+		if (this.getAltitude() == this.getDesiredAltitude()){
+			if (this.getDesiredAltitude() == 0)
+				this.syncClient();
+			//landing in a building, mark altitude to 0 as player is no longer flying.
+			if (this.landingRegion != null){
+				this.altitude = 0;
+				this.region = this.landingRegion;
+				this.loc = this.loc.setY(this.landingRegion.lerpY(this));
+			}
+			else
+			this.altitude = this.getDesiredAltitude();
+			
+			this.loc = this.loc.setY(HeightMap.getWorldHeight(this) + this.getAltitude());
+
+			this.setTakeOffTime(0);
+			MovementManager.finishChangeAltitude(this, this.getDesiredAltitude());
+			
+			return;
+		}
+		
+		this.loc = this.loc.setY(HeightMap.getWorldHeight(this) + this.getAltitude());
+	}
+
+	public boolean hasBoon(){
+		for (Effect eff : this.getEffects().values()){
+			if (eff.getPowerToken() == -587743986 || eff.getPowerToken() == -1660519801 || eff.getPowerToken() == -1854683250)
+				return true;
+		}
+		return false;
+	}
+
+	public void updateBlessingMessage(){
+
+		if (this.getTimeStamp("RealmClaim") > System.currentTimeMillis())
+			return;
+
+		int count = 0;
+
+		for (Effect eff : this.getEffects().values()){
+			if (eff.getPowerToken() == -587743986 || eff.getPowerToken() == -1660519801 || eff.getPowerToken() == -1854683250)
+				count++;
+		}
+
+		if (count > 0){
+			this.timestamps.put("RealmClaim", DateTime.now().plusMinutes(3).getMillis());
+			for (PlayerCharacter toSend : SessionManager.getAllActivePlayerCharacters()){
+				ChatManager.chatSystemInfo(toSend, this.getCombinedName() + " is seeking to claim a realm and already has " + count + " blessngs!");
+			}
+		}
+	}
+	@Override
+	public void updateLocation(){
+
+		
+		if (!this.isMoving())
+			return;
+
+		if (!this.isActive)
+			return;
+		
+		Vector3fImmutable newLoc = this.getMovementLoc();
+		
+		if (this.isAlive() == false || this.getBonuses().getBool(ModType.Stunned, SourceType.None) || this.getBonuses().getBool(ModType.CannotMove, SourceType.None)) {
+			//Target is stunned or rooted. Don't move
+			this.stopMovement(newLoc);
+			this.region = AbstractWorldObject.GetRegionByWorldObject(this);
+			return;
+		}
+		if (newLoc.equals(this.getEndLoc())){
+			this.stopMovement(newLoc);
+			this.region = AbstractWorldObject.GetRegionByWorldObject(this);
+			if (this.getDebug(1))
+				ChatManager.chatSystemInfo( this,
+						"Arrived at End location. " + this.getEndLoc());
+			return;
+			//Next upda
+		}
+		
+		setLoc(newLoc);
+		this.region = AbstractWorldObject.GetRegionByWorldObject(this);
+
+		if (this.getDebug(1))
+			ChatManager.chatSystemInfo(this,
+					"Distance to target " + this.getEndLoc().distance2D(this.getLoc()) + " speed " + this.getSpeed());
+
+		if (this.getStamina() < 10)
+			MovementManager.sendOOS(this);
+
+		//	if (MBServerStatics.MOVEMENT_SYNC_DEBUG || this.getDebug(1))
+		//                Logger.info("MovementManager", "Updating movement current loc:" + this.getLoc().getX() + " " + this.getLoc().getZ()
+		//                        + " end loc: " + this.getEndLoc().getX() + " " + this.getEndLoc().getZ() + " distance " + this.getEndLoc().distance2D(this.getLoc()));
+
+	}
+	@Override
+	public void updateMovementState() {
+		
+		
+		if (this.enteredWorld) {
+			if (!this.lastSwimming) {
+				boolean enterWater = PlayerCharacter.enterWater(this);
+				
+				if (enterWater){
+					this.lastSwimming = enterWater;
+					MovementManager.sendRWSSMsg(this);
+					
+				}
+			} else {
+				if (PlayerCharacter.LeaveWater(this)){
+					this.lastSwimming = false;
+					if (!this.isMoving())
+					MovementManager.sendRWSSMsg(this);
+				}
+					
+			}
+			
+			boolean breathe = PlayerCharacter.CanBreathe(this);
+			
+			if (breathe != this.canBreathe){
+				this.canBreathe = breathe;
+			//	ChatManager.chatSystemInfo(this, "Breathe : " + this.canBreathe);
+				this.syncClient();
+			}
+		}
+
+		//char is flying
+		if (this.isFlying() == true) {
+			this.movementState = MovementState.FLYING;
+			return;
+		}
+		// Char is not moving.  Set sitting or idle
+		if (!this.isMoving()) {
+
+			if (this.sit == true)
+				this.movementState = MovementState.SITTING;
+			else
+				this.movementState = MovementState.IDLE;
+
+			return;
+		} else {
+			this.movementState = MovementState.RUNNING;
+		}
+
+		// Char is swimming // we now are saving lastSwimstate boolean, use this instead of calling getSwimming again.
+		if (this.lastSwimming == true) {
+			this.movementState = MovementState.SWIMMING;
+			return;
+		}
+
+		// Char is moving, yet not swimming or flying he must be running
+		this.movementState = MovementState.RUNNING;
+
+	}
+	@Override
+	public void updateRegen() {
+		
+		float healthRegen = 0f;
+		float manaRegen = 0f;
+		float stamRegen = 0f;
+		
+		boolean updateClient = false;
+
+		// Early exit if char is dead or disconnected
+		if ((this.isAlive() == false)
+				|| (this.isActive() == false) || this.getLoc().x == 0 && this.getLoc().z == 0)
+			return;
+
+		// Calculate Regen amount from last simulation tick
+		switch (this.movementState) {
+
+		case IDLE:
+
+			healthRegen = ((this.healthMax * MBServerStatics.HEALTH_REGEN_IDLE) + MBServerStatics.HEALTH_REGEN_IDLE_STATIC) * (getRegenModifier(ModType.HealthRecoverRate));
+			
+			if (this.isCasting() || this.isItemCasting())
+				healthRegen *= .75f;
+			// Characters regen mana when in only walk mode and idle
+			if (this.walkMode)
+				manaRegen = ((this.manaMax  * MBServerStatics.MANA_REGEN_IDLE)  *  getRegenModifier(ModType.ManaRecoverRate));
+			else if (!this.isCasting() && !this.isItemCasting())
+				manaRegen = ((this.manaMax  * MBServerStatics.MANA_REGEN_IDLE)  *  getRegenModifier(ModType.ManaRecoverRate));
+			else
+				manaRegen = 0;
+
+			 if (!PlayerCharacter.CanBreathe(this))
+				stamRegen = MBServerStatics.STAMINA_REGEN_SWIM;
+			else if ((!this.isCasting() && !this.isItemCasting()) || this.lastMovementState.equals(MovementState.FLYING))
+				stamRegen = MBServerStatics.STAMINA_REGEN_IDLE * getRegenModifier(ModType.StaminaRecoverRate);
+			else
+				stamRegen =0 ;
+			break;
+		case SITTING:
+			healthRegen = ((this.healthMax * MBServerStatics.HEALTH_REGEN_SIT) + MBServerStatics.HEALTH_REGEN_SIT_STATIC) * getRegenModifier(ModType.HealthRecoverRate);
+			manaRegen = (this.manaMax * MBServerStatics.MANA_REGEN_SIT)  *  ( getRegenModifier(ModType.ManaRecoverRate));
+			stamRegen = MBServerStatics.STAMINA_REGEN_SIT * getRegenModifier(ModType.StaminaRecoverRate);
+			break;
+		case RUNNING:
+			if (this.walkMode == true) {
+				healthRegen = ((this.healthMax * MBServerStatics.HEALTH_REGEN_WALK) + MBServerStatics.HEALTH_REGEN_IDLE_STATIC) * getRegenModifier(ModType.HealthRecoverRate);
+				manaRegen = this.manaMax * MBServerStatics.MANA_REGEN_WALK *  getRegenModifier(ModType.ManaRecoverRate);
+				stamRegen = MBServerStatics.STAMINA_REGEN_WALK;
+			} else {
+				healthRegen =0;
+				manaRegen = 0;
+				
+				if (this.combat == true)
+					stamRegen = MBServerStatics.STAMINA_REGEN_RUN_COMBAT;
+				else
+					stamRegen = MBServerStatics.STAMINA_REGEN_RUN_NONCOMBAT;
+			}
+			break;
+		case FLYING:
+			
+			float seventyFive = this.staminaMax * .75f;
+			float fifty = this.staminaMax *.5f;
+			float twentyFive = this.staminaMax *.25f;
+
+			if (this.getDesiredAltitude() == 0 && this.getAltitude() <= 10){
+				if (this.isCombat())
+					stamRegen = 0;
+				else
+					stamRegen = MBServerStatics.STAMINA_REGEN_IDLE * getRegenModifier(ModType.StaminaRecoverRate);
+			}
+				else if (!this.useFlyMoveRegen()){
+					
+					healthRegen = ((this.healthMax * MBServerStatics.HEALTH_REGEN_IDLE) + MBServerStatics.HEALTH_REGEN_IDLE_STATIC) * ( getRegenModifier(ModType.HealthRecoverRate));
+					
+					if (this.isCasting() || this.isItemCasting())
+						healthRegen *= .75f;
+					// Characters regen mana when in only walk mode and idle
+					if (this.walkMode)
+						manaRegen = (this.manaMax * MBServerStatics.MANA_REGEN_IDLE + (this.getSpiMod() * .015f))* ( getRegenModifier(ModType.ManaRecoverRate));
+					else if (!this.isCasting() && !this.isItemCasting())
+						manaRegen = (this.manaMax * MBServerStatics.MANA_REGEN_IDLE + (this.getSpiMod() * .015f)) * (  getRegenModifier(ModType.ManaRecoverRate));
+					else
+						manaRegen = 0;
+					
+					if (!this.isItemCasting() && !this.isCasting() || this.getTakeOffTime() != 0)
+						stamRegen = MBServerStatics.STAMINA_REGEN_FLY_IDLE;		
+					else
+						stamRegen = -1f;
+				}
+				else
+			if (this.walkMode == true) {
+				healthRegen = ((this.healthMax * MBServerStatics.HEALTH_REGEN_WALK) + MBServerStatics.HEALTH_REGEN_IDLE_STATIC) * getRegenModifier(ModType.HealthRecoverRate);
+				manaRegen = ((this.manaMax * MBServerStatics.MANA_REGEN_WALK)+ (this.getSpiMod() * .015f))  * (getRegenModifier(ModType.ManaRecoverRate));
+				stamRegen = MBServerStatics.STAMINA_REGEN_FLY_WALK;
+			} else {
+				healthRegen = 0;
+				manaRegen = 0;
+				if (this.isCombat())
+					stamRegen = MBServerStatics.STAMINA_REGEN_FLY_RUN_COMBAT;
+				else
+				stamRegen = MBServerStatics.STAMINA_REGEN_FLY_RUN;
+			}
+
+			float oldStamina = this.stamina.get();
+			
+			if (FastMath.between(oldStamina, 0, twentyFive) && !this.wasTripped25){
+				updateClient = true;
+				this.wasTripped25 = true;
+				this.wasTripped50 = false;
+				this.wasTripped75 = false;
+			}else if (FastMath.between(oldStamina, twentyFive, fifty) && !this.wasTripped50){
+				updateClient = true;
+				this.wasTripped25 = false;
+				this.wasTripped50 = true;
+				this.wasTripped75 = false;
+			}else if (FastMath.between(oldStamina, fifty, seventyFive) && !this.wasTripped75){
+				updateClient = true;
+				this.wasTripped25 = false;
+				this.wasTripped50 = false;
+				this.wasTripped75 = true;
+			}
+			break;
+		case SWIMMING:
+			if (this.walkMode == true) {
+				healthRegen = ((this.healthMax * MBServerStatics.HEALTH_REGEN_WALK) + MBServerStatics.HEALTH_REGEN_IDLE_STATIC) *  getRegenModifier(ModType.HealthRecoverRate);
+				manaRegen = ((this.manaMax * MBServerStatics.MANA_REGEN_WALK)+ (this.getSpiMod() * .015f)) * ( getRegenModifier(ModType.ManaRecoverRate));
+				stamRegen = MBServerStatics.STAMINA_REGEN_SWIM;
+			} else {
+				healthRegen = 0;
+				manaRegen = 0;
+				stamRegen = MBServerStatics.STAMINA_REGEN_SWIM;
+				
+				if (this.combat == true)
+					stamRegen += MBServerStatics.STAMINA_REGEN_RUN_COMBAT;
+				else
+					stamRegen += MBServerStatics.STAMINA_REGEN_RUN_NONCOMBAT;
+			}
+			break;
+		}
+
+		// Are we drowning?
+		if ((this.getStamina() <= 0)
+				&& (PlayerCharacter.CanBreathe(this) == false))
+			healthRegen = (this.healthMax * -.03f);
+
+		// Multiple regen values by current deltaTime
+		//     Logger.info("", healthRegen + "");
+		healthRegen *= getDeltaTime();
+		manaRegen *= getDeltaTime();
+		stamRegen *= getStamDeltaTime();
+
+		boolean workedHealth = false;
+		boolean workedMana = false;
+		boolean workedStamina = false;
+
+		float old, mod;
+		while(!workedHealth || !workedMana || !workedStamina) {
+			if (!this.isAlive() || !this.isActive())
+				return;
+			if (!workedHealth) {
+				old = this.health.get();
+				mod = old + healthRegen;
+				if (mod > this.healthMax)
+					mod = healthMax;
+				else if (mod <= 0) {
+					if (this.isAlive.compareAndSet(true, false))
+						killCharacter("Water");
+					return;
+				}
+				workedHealth = this.health.compareAndSet(old, mod);
+			}
+			if (!workedStamina) {
+				old = this.stamina.get();
+				mod = old + stamRegen;
+				if (mod > this.staminaMax)
+					mod = staminaMax;
+				else if (mod < 0)
+					mod = 0;
+				workedStamina = this.stamina.compareAndSet(old, mod);
+			}
+			if (!workedMana) {
+				old = this.mana.get();
+				mod = old + manaRegen;
+				if (mod > this.manaMax)
+					mod = manaMax;
+				else if (mod < 0)
+					mod = 0;
+				workedMana = this.mana.compareAndSet(old, mod);
+			}
+		}
+
+		if (updateClient)
+			this.syncClient();
+
+		// Reset this char's frame time.
+		this.lastUpdateTime = System.currentTimeMillis();
+		this.lastStamUpdateTime = System.currentTimeMillis();
+
+	}
+	
+	public synchronized void updateStamRegen(long time) {
+
+		boolean disable = true;
+		
+		if (disable)
+			return;
+	
+		float stamRegen = 0f;
+
+		// Early exit if char is dead or disconnected
+		if ((this.isAlive() == false)
+				|| (this.isActive() == false) || this.getLoc().x == 0 && this.getLoc().z == 0)
+			return;
+
+		// Calculate Regen amount from last simulation tick
+		switch (this.movementState) {
+
+		case IDLE:
+			 if (!PlayerCharacter.CanBreathe(this))
+				stamRegen = MBServerStatics.STAMINA_REGEN_SWIM;
+			else if ((!this.isCasting() && !this.isItemCasting()) || this.lastMovementState.equals(MovementState.FLYING))
+				stamRegen = MBServerStatics.STAMINA_REGEN_IDLE * getRegenModifier(ModType.StaminaRecoverRate);
+			else
+				stamRegen =0 ;
+			break;
+		case SITTING:
+			stamRegen = MBServerStatics.STAMINA_REGEN_SIT * getRegenModifier(ModType.StaminaRecoverRate);
+			break;
+		case RUNNING:
+			if (this.walkMode == true) {
+							stamRegen = MBServerStatics.STAMINA_REGEN_WALK;
+			} else {
+				if (this.combat == true)
+					stamRegen = MBServerStatics.STAMINA_REGEN_RUN_COMBAT;
+				else
+					stamRegen = MBServerStatics.STAMINA_REGEN_RUN_NONCOMBAT;
+			}
+			break;
+		case FLYING:
+			
+			if (this.getDesiredAltitude() == 0 && this.getAltitude() <= 10){
+				if (this.isCombat())
+					stamRegen = 0;
+				else
+					stamRegen = MBServerStatics.STAMINA_REGEN_IDLE * getRegenModifier(ModType.StaminaRecoverRate);
+			}
+				else 	if (!this.isMoving()){
+					
+					
+					if (!this.isItemCasting() && !this.isCasting() || this.getTakeOffTime() != 0)
+						stamRegen = MBServerStatics.STAMINA_REGEN_FLY_IDLE;		
+					else
+						stamRegen = -1f;
+					
+				}
+				else
+				
+			if (this.walkMode == true) {
+				
+				stamRegen = MBServerStatics.STAMINA_REGEN_FLY_WALK;
+			} else {
+				if (this.isCombat())
+					stamRegen = MBServerStatics.STAMINA_REGEN_FLY_RUN_COMBAT;
+				else
+				stamRegen = MBServerStatics.STAMINA_REGEN_FLY_RUN;
+			}
+			break;
+		case SWIMMING:
+			if (this.walkMode == true) {
+				stamRegen = MBServerStatics.STAMINA_REGEN_SWIM;
+			} else {
+				stamRegen = MBServerStatics.STAMINA_REGEN_SWIM;
+			}
+			break;
+		}
+
+	
+	
+
+		// Multiple regen values by current deltaTime
+		//     Logger.info("", healthRegen + "");
+		
+		stamRegen *= (time * .001f);
+
+
+		
+		boolean workedStamina = false;
+
+
+		float old, mod;
+		while( !workedStamina) {
+			if (!this.isAlive() || !this.isActive())
+				return;
+			
+			if (!workedStamina) {
+				old = this.stamina.get();
+				mod = old + stamRegen;
+				if (mod > this.staminaMax)
+					mod = staminaMax;
+				else if (mod < 0)
+					mod = 0;
+				workedStamina = this.stamina.compareAndSet(old, mod);
+			}
+		
+		}
+		
+	}
+
+	public void syncClient() {
+
+		ModifyHealthMsg modifyHealthMsg = new ModifyHealthMsg(null, this, 0, 1, 1, -1984683793, "", 0, 652920987);
+		//mhm.setOmitFromChat(0);
+		Dispatch dispatch = Dispatch.borrow(this, modifyHealthMsg);
+		DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.PRIMARY);
+
+	}
+
+	public MovementState getMovementState() {
+		return movementState;
+	}
+
+	public boolean isHasAnniversery() {
+		return hasAnniversery;
+	}
+
+	public void setHasAnniversery(boolean hasAnniversery) {
+		DbManager.PlayerCharacterQueries.SET_ANNIVERSERY(this, hasAnniversery);
+		this.hasAnniversery = hasAnniversery;
+	}
+
+	public int getSpamCount() {
+		return spamCount;
+	}
+
+	public void setSpamCount(int spamCount) {
+		this.spamCount = spamCount;
+	}
+
+
+	public String getHash() {
+		return hash;
+	}
+
+	public void setHash() {
+
+		this.hash = DataWarehouse.hasher.encrypt(this.getObjectUUID());
+
+		// Write hash to player character table
+
+		DataWarehouse.writeHash(DataRecordType.CHARACTER, this.getObjectUUID());
+	}
+
+	public AtomicInteger getGuildStatus() {
+		return guildStatus;
+	}
+
+	public static int GetPlayerRealmTitle(PlayerCharacter player){
+	
+		if (player.getGuild().isErrant())
+			return 0;
+		if (!player.getGuild().isGuildLeader(player.getObjectUUID()))
+			return 0;
+		if (player.getGuild().getOwnedCity() == null)
+			return 10;
+		if (player.getGuild().getOwnedCity().getRealm() == null)
+			return 10;
+		if (player.getGuild().getOwnedCity().getRealm().getRulingCity() == null)
+			return 10;
+
+		if (player.getGuild().getOwnedCity().getRealm().getRulingCity().getObjectUUID() != player.getGuild().getOwnedCity().getObjectUUID())
+			return 10;
+		int realmTitle = 1;
+		if (player.getGuild().getSubGuildList() == null || player.getGuild().getSubGuildList().isEmpty())
+			return 11;
+		for (Guild subGuild: player.getGuild().getSubGuildList()){
+			if (subGuild.getOwnedCity() == null)
+				continue;
+			if (subGuild.getOwnedCity().getRealm() == null)
+				continue;
+			if (subGuild.getOwnedCity().getRealm().getRulingCity() == null)
+				continue;
+			if (subGuild.getOwnedCity().getRealm().getRulingCity().getObjectUUID() != subGuild.getOwnedCity().getObjectUUID())
+				continue;
+			realmTitle++;
+		}
+
+		if (realmTitle < 3)
+			return 11;
+		else if (realmTitle < 5)
+			return 12;
+		else
+			return 13;
+	}
+	public static void UpdateClientPlayerRank(PlayerCharacter pc){
+		if (pc == null)
+			return;
+		boolean disable = true;
+		
+		if (disable)
+			return;
+		UpdateCharOrMobMessage ucm = new UpdateCharOrMobMessage(pc,2,pc.getRank());
+		DispatchMessage.sendToAllInRange(pc, ucm);
+	}
+
+	public void setLastRealmID(int lastRealmID) {
+		this.lastRealmID = lastRealmID;
+	}
+
+	public int getLastRealmID() {
+		return lastRealmID;
+	}
+
+	public int getSubRaceID() {
+		return subRaceID;
+	}
+
+	public void setSubRaceID(int subRaceID) {
+		this.subRaceID = subRaceID;
+	}
+
+	public ArrayList<GuildHistory> getGuildHistory() {
+		return guildHistory;
+	}
+
+	public void setGuildHistory(ArrayList<GuildHistory> guildHistory) {
+		this.guildHistory = guildHistory;
+	}
+
+	public void moveTo(Vector3fImmutable endLoc){
+		this.setInBuilding(-1);
+		this.setInFloorID(-1);
+		MoveToPointMsg moveToMsg = new MoveToPointMsg();
+		moveToMsg.setStartCoord(this.getLoc());
+		moveToMsg.setEndCoord(endLoc);
+		moveToMsg.setInBuilding(-1);
+		moveToMsg.setUnknown01(-1);
+		moveToMsg.setSourceType(GameObjectType.PlayerCharacter.ordinal());
+		moveToMsg.setSourceID(this.getObjectUUID());
+
+		Dispatch dispatch = Dispatch.borrow(this, moveToMsg);
+		DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.PRIMARY);
+
+		try {
+			MovementManager.movement(moveToMsg, this);
+		} catch (MsgSendException e) {
+			// TODO Auto-generated catch block
+			Logger.error("Player.MoveTo", this.getName() + " tripped error " + e.getMessage());
+		}
+
+	}
+
+	public void updateScaleHeight(){
+		
+		float strengthScale = 0;
+		float unknownScale1 = 0;
+		float unknownScale2 = 0;
+		float unknownScale3 = 0;
+		
+		float scaleHeight = 0;
+
+		if ((int) this.statStrBase > 40)
+			strengthScale = ((int) this.statStrBase - 40)* 0.0024999999f; //Y scale ?
+
+		unknownScale1 = (float) (((int) this.statStrBase * 0.0024999999f + strengthScale + 0.89999998) * race.getRaceType().getScaleHeight());
+		strengthScale = (int) this.statStrBase * 0.0037499999f + strengthScale + 0.85000002f; //strengthScale is different for x and z
+
+		unknownScale2 = strengthScale * race.getRaceType().getScaleHeight(); //x scale?
+		unknownScale3 = strengthScale * race.getRaceType().getScaleHeight(); //z Scale?
+		
+	
+
+		scaleHeight = (1.5f + unknownScale1);
+		
+	
+		
+		this.characterHeight = scaleHeight;
+		
+		this.centerHeight = scaleHeight;
+
+	}
+
+	public int getOverFlowEXP() {
+		return overFlowEXP;
+	}
+
+	public void setOverFlowEXP(int overFlowEXP) {
+		this.overFlowEXP = overFlowEXP;
+	}
+	
+	public static void GroundPlayer(PlayerCharacter groundee){
+		if (groundee.getDesiredAltitude() == 0 && groundee.getAltitude() == 0)
+			return;
+		groundee.setAltitude(groundee.getAltitude());
+		groundee.setDesiredAltitude(0);
+		groundee.setTakeOffTime(System.currentTimeMillis());
+		
+		ChangeAltitudeMsg msg = ChangeAltitudeMsg.GroundPlayerMsg(groundee);
+		// force a landing
+		DispatchMessage.dispatchMsgToInterestArea(groundee, msg, DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, true, false);
+	
+	}
+
+	public MovementState getLastMovementState() {
+		return lastMovementState;
+	}
+
+	public void setLastMovementState(MovementState lastMovementState) {
+		this.lastMovementState = lastMovementState;
+	}
+	@Override
+	public final void setIsCasting(final boolean isCasting) {
+		if (this.isCasting != isCasting)
+			this.update();
+		this.isCasting = isCasting;
+	}
+	@Override
+	public void setItemCasting(boolean itemCasting) {
+		if (this.itemCasting != itemCasting)
+			this.dynamicUpdate(UpdateType.REGEN);
+		this.itemCasting = itemCasting;
+	}
+	
+	public void resetRegenUpdateTime(){
+		this.lastUpdateTime = System.currentTimeMillis();
+		this.lastStamUpdateTime = System.currentTimeMillis();
+	}
+
+	public float getCharacterHeight() {
+		return characterHeight;
+	}
+
+	public void setCharacterHeight(float characterHeight) {
+		this.characterHeight = characterHeight;
+	}
+
+	public void setCenterHeight(float centerHeight) {
+		this.centerHeight = centerHeight;
+	}
+	
+	public static boolean CanBreathe(PlayerCharacter breather){
+		try{
+			if (breather.isFlying())
+				return true;
+			Zone zone = ZoneManager.findSmallestZone(breather.getLoc());
+
+			if (zone.getSeaLevel() != 0){
+
+				float localAltitude = breather.getLoc().y;
+
+
+				if (localAltitude  + breather.characterHeight  < zone.getSeaLevel() -2)
+					return false;
+				
+				if (breather.isMoving()){
+					if (localAltitude + breather.characterHeight <  zone.getSeaLevel())
+						return false;
+				}
+			}else{
+				if (breather.getLoc().y + breather.characterHeight < -2)
+					return false;
+				
+				if (breather.isMoving()){
+					if (breather.getLoc().y + breather.characterHeight < 0)
+						return false;
+				}
+			}
+			
+			
+		}catch(Exception e){
+			Logger.info(breather.getName() + e);
+		}
+
+	
+		return true;
+	}
+	
+	public static boolean enterWater(PlayerCharacter enterer){
+		
+		try{
+			if (enterer.isFlying())
+				return false;
+
+			
+
+			Zone zone = ZoneManager.findSmallestZone(enterer.getLoc());
+
+			if (zone.getSeaLevel() != 0){
+
+				float localAltitude = enterer.getLoc().y + enterer.characterHeight ;
+
+
+				if (localAltitude < zone.getSeaLevel())
+					return true;
+			}else{
+				if (enterer.getLoc().y + enterer.characterHeight  < 0)
+					return true;
+			}
+		}catch(Exception e){
+			Logger.info(enterer.getName() + e);
+		}
+
+		return false;
+		
+	}
+	
+	public static boolean LeaveWater(PlayerCharacter leaver){
+		
+		try{
+			
+
+			Zone zone = ZoneManager.findSmallestZone(leaver.getLoc());
+			
+			float leaveWater = leaver.centerHeight;
+			
+			if (leaver.isMoving())
+				leaveWater = 1f;
+			
+
+			if (zone.getSeaLevel() != 0){
+
+				float localAltitude = leaver.getLoc().y;
+
+
+				if (localAltitude + leaveWater < zone.getSeaLevel())
+					return false;
+			}else{
+				if (leaver.getLoc().y + leaveWater < 0)
+					return false;
+			}
+		}catch(Exception e){
+			Logger.info(leaver.getName() + e);
+		}
+
+		return true;
+	}
+
+	public boolean isEnteredWorld() {
+		return enteredWorld;
+	}
+
+	public void setEnteredWorld(boolean enteredWorld) {
+		this.enteredWorld = enteredWorld;
+	}
+
+	public long getChannelMute() {
+		return channelMute;
+	}
+
+	public void setChannelMute(long channelMute) {
+		this.channelMute = channelMute;
+	}
+
+	public boolean isLastSwimming() {
+		return lastSwimming;
+	}
+
+	public boolean isTeleporting() {
+		return isTeleporting;
+	}
+
+	public void setTeleporting(boolean isTeleporting) {
+		this.isTeleporting = isTeleporting;
+	}
+	
+	@Override
+	public final void teleport(final Vector3fImmutable targetLoc) {
+		locationLock.writeLock().lock();
+		try{
+			MovementManager.translocate(this, targetLoc,null);
+		}catch(Exception e){
+			Logger.error(e);
+		}finally{
+			locationLock.writeLock().unlock();
+		}
+	}
+
+	public ReadWriteLock getTeleportLock() {
+		return teleportLock;
+	}
+	
+	public static boolean CanBindToBuilding(PlayerCharacter player, int buildingID){
+		if (buildingID == 0)
+			return false;
+		
+		Building bindBuilding = BuildingManager.getBuildingFromCache(buildingID);
+		
+		if (bindBuilding == null)
+			return false;
+		
+		if(!BuildingManager.playerCanManage(player, bindBuilding))
+		return false;
+		
+		return true;
+	}
+	
+	public float getBargain(){
+		float bargain = 0;
+		
+		
+		CharacterSkill bargainSkill = this.getSkills().get(engine.Enum.CharacterSkills.Bargaining.name());
+	
+		if (bargainSkill != null)
+			bargain = bargainSkill.getModifiedAmountBeforeMods();
+		
+		if (bargain > 100)
+			bargain = 100;
+		
+		bargain *= .01f;
+		
+		return bargain;
+	}
+}
diff --git a/src/engine/objects/PlayerFriends.java b/src/engine/objects/PlayerFriends.java
new file mode 100644
index 00000000..2d2cdf08
--- /dev/null
+++ b/src/engine/objects/PlayerFriends.java
@@ -0,0 +1,122 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.objects;
+
+import engine.Enum.DispatchChannel;
+import engine.gameManager.DbManager;
+import engine.gameManager.SessionManager;
+import engine.net.Dispatch;
+import engine.net.DispatchMessage;
+import engine.net.client.msg.UpdateFriendStatusMessage;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.HashMap;
+import java.util.HashSet;
+
+public class PlayerFriends  {
+
+	public int playerUID;
+	public int friendUID;
+	
+	public static HashMap <Integer,HashSet<Integer>> PlayerFriendsMap = new HashMap<>();
+
+	/**
+	 * ResultSet Constructor
+	 */
+
+	public PlayerFriends(ResultSet rs) throws SQLException {
+		this.playerUID = rs.getInt("playerUID");
+		this.friendUID = rs.getInt("friendUID");
+		
+		//cache player friends.
+		//hashset already created, just add to set.
+		if (PlayerFriendsMap.containsKey(playerUID)){
+			HashSet<Integer> playerFriendSet = PlayerFriendsMap.get(playerUID);
+			playerFriendSet.add(friendUID);
+			//hashset not yet created, create new set, and add to map.
+		}else{
+			HashSet<Integer> playerFriendSet = new HashSet<>();
+			playerFriendSet.add(friendUID);
+			PlayerFriendsMap.put(this.playerUID, playerFriendSet);
+		}
+		
+	}
+
+	public PlayerFriends(int playerUID, int friendUID) {
+		super();
+		this.playerUID = playerUID;
+		this.friendUID = friendUID;
+	}
+
+	public int getPlayerUID() {
+		return playerUID;
+	}
+	
+	public static void AddToFriends(int playerID, int friendID){
+		HashSet<Integer> friends = PlayerFriendsMap.get(playerID);
+		
+		if (friends != null){
+			//already in friends list, don't do anything.
+			if (friends.contains(friendID))
+				return;
+			
+			DbManager.PlayerCharacterQueries.ADD_FRIEND(playerID, friendID);
+			friends.add(friendID);
+		}else{
+			friends = new HashSet<>();
+			DbManager.PlayerCharacterQueries.ADD_FRIEND(playerID, friendID);
+			friends.add(friendID);
+			PlayerFriendsMap.put(playerID, friends);
+		}
+	}
+	
+	public static void RemoveFromFriends(int playerID, int friendID){
+		
+	if (!CanRemove(playerID, friendID))
+		return;
+	
+HashSet<Integer> friends = PlayerFriendsMap.get(playerID);
+		
+		if (friends != null){
+			DbManager.PlayerCharacterQueries.REMOVE_FRIEND(playerID, friendID);
+			friends.remove(friendID);
+		}
+	}
+	
+	public static boolean CanRemove(int playerID, int toRemove){
+		if (PlayerFriendsMap.get(playerID) == null)
+			return false;
+		
+		if (PlayerFriendsMap.get(playerID).isEmpty())
+			return false;
+		
+		if (!PlayerFriendsMap.get(playerID).contains(toRemove))
+			return false;
+		
+		return true;
+	}
+	
+	public static void SendFriendsStatus(PlayerCharacter player, boolean online ){
+		HashSet<Integer> friendsSet = PlayerFriends.PlayerFriendsMap.get(player.getObjectUUID());
+		if (friendsSet != null){
+			for(int friendID: friendsSet){
+				PlayerCharacter friend = SessionManager.getPlayerCharacterByID(friendID);
+				if (friend == null)
+					continue;
+				UpdateFriendStatusMessage outMsg = new UpdateFriendStatusMessage(player);
+				outMsg.online = online;
+				Dispatch dispatch = Dispatch.borrow(friend, outMsg);
+				DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
+			}
+		}
+
+	}
+}
diff --git a/src/engine/objects/Portal.java b/src/engine/objects/Portal.java
new file mode 100644
index 00000000..498e0921
--- /dev/null
+++ b/src/engine/objects/Portal.java
@@ -0,0 +1,169 @@
+package engine.objects;
+
+import engine.Enum.RunegateType;
+import engine.InterestManagement.WorldGrid;
+import engine.gameManager.ConfigManager;
+import engine.job.JobScheduler;
+import engine.jobs.CloseGateJob;
+import engine.math.Vector3fImmutable;
+import engine.server.MBServerStatics;
+import org.pmw.tinylog.Logger;
+
+import java.util.HashSet;
+
+/* A Runegate object holds an array of these
+ * portals.  This class controls their triggers
+ * and visual effects.
+ */
+
+public class Portal {
+
+	private boolean active;
+	private RunegateType sourceGateType;
+	private RunegateType portalType;
+	private RunegateType destinationGateType;
+	private final Vector3fImmutable portalLocation;
+	private long lastActive = 0;
+
+	public Portal(RunegateType gateType, RunegateType portalType, RunegateType destinationGate) {
+
+		Building gateBuilding;
+
+		this.active = false;
+		this.sourceGateType = gateType;
+		this.destinationGateType = destinationGate;
+		this.portalType = portalType;
+
+		gateBuilding = this.sourceGateType.getGateBuilding();
+
+		if (gateBuilding == null) {
+			Logger.error("Gate building " + this.sourceGateType.getGateUUID() + " for " + this.sourceGateType.name() + " missing");
+		}
+
+		this.portalLocation = gateBuilding.getLoc().add(new Vector3fImmutable(portalType.getOffset().x, 6, portalType.getOffset().y));
+	}
+
+	public boolean isActive() {
+
+		return this.active;
+
+	}
+
+	public void deactivate() {
+
+		Building sourceBuilding;
+
+		// Remove effect bit from the runegate building, which turns off this
+		// portal type's particle effect
+
+		sourceBuilding = this.sourceGateType.getGateBuilding();
+        sourceBuilding.removeEffectBit(portalType.getEffectFlag());
+		this.active = false;
+		sourceBuilding.updateEffects();
+	}
+
+	public void activate(boolean autoClose) {
+
+		Building sourceBuilding;
+
+
+		// Apply  effect bit to the runegate building, which turns on this
+		// portal type's particle effect
+
+		sourceBuilding = this.sourceGateType.getGateBuilding();
+        sourceBuilding.addEffectBit(portalType.getEffectFlag());
+		this.lastActive = System.currentTimeMillis();
+		this.active = true;
+
+		// Do not update effects at bootstrap as it
+		// tries to send a dispatch.
+
+		if (ConfigManager.worldServer.isRunning == true)
+			sourceBuilding.updateEffects();
+
+		if (autoClose == true) {
+            CloseGateJob cgj = new CloseGateJob(sourceBuilding, portalType);
+			JobScheduler.getInstance().scheduleJob(cgj, MBServerStatics.RUNEGATE_CLOSE_TIME);
+		}
+	}
+
+	public void collide() {
+
+		HashSet<AbstractWorldObject> playerList;
+
+		playerList = WorldGrid.getObjectsInRangePartial(this.portalLocation, 2, MBServerStatics.MASK_PLAYER);
+
+		if (playerList.isEmpty())
+			return;
+
+		for (AbstractWorldObject player : playerList) {
+
+			onEnter((PlayerCharacter) player);
+
+		}
+	}
+
+	public void onEnter(PlayerCharacter player) {
+
+		if (player.getTimeStamp("lastMoveGate") < this.lastActive)
+			return;
+		Building gateBuilding;
+
+        gateBuilding = destinationGateType.getGateBuilding();
+
+        if (gateBuilding != null){
+        	player.teleport(gateBuilding.getLoc());
+    		player.setSafeMode();
+        }
+		
+	}
+
+	/**
+	 * @return the sourceGateType
+	 */
+	public RunegateType getSourceGateType() {
+		return sourceGateType;
+	}
+
+	/**
+	 * @param sourceGateType the sourceGateType to set
+	 */
+	public void setSourceGateType(RunegateType sourceGateType) {
+		this.sourceGateType = sourceGateType;
+	}
+
+	/**
+	 * @return the portalType
+	 */
+	public RunegateType getPortalType() {
+		return portalType;
+	}
+
+	/**
+	 * @param portalType the portalType to set
+	 */
+	public void setPortalType(RunegateType portalType) {
+		this.portalType = portalType;
+	}
+
+	/**
+	 * @return the destinationGateType
+	 */
+	public RunegateType getDestinationGateType() {
+		return destinationGateType;
+	}
+
+	/**
+	 * @param destinationGateType the destinationGateType to set
+	 */
+	public void setDestinationGateType(RunegateType destinationGateType) {
+		this.destinationGateType = destinationGateType;
+	}
+
+	/**
+	 * @return the portalLocation
+	 */
+	public Vector3fImmutable getPortalLocation() {
+		return portalLocation;
+	}
+}
diff --git a/src/engine/objects/PowerGrant.java b/src/engine/objects/PowerGrant.java
new file mode 100644
index 00000000..fb5a8c31
--- /dev/null
+++ b/src/engine/objects/PowerGrant.java
@@ -0,0 +1,138 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.objects;
+
+import engine.server.MBServerStatics;
+import org.pmw.tinylog.Logger;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.concurrent.ConcurrentHashMap;
+
+
+
+
+public class PowerGrant extends AbstractGameObject {
+
+	private int token;
+	private ConcurrentHashMap<Integer, Short> runeGrants = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW);
+	private static ConcurrentHashMap<Integer, PowerGrant> grantedPowers = null;
+
+	/**
+	 * ResultSet Constructor
+	 */
+	public PowerGrant(ResultSet rs) throws SQLException {
+		super();
+		this.token = rs.getInt("powerToken");
+		runeGrants.put(rs.getInt("runeID"), rs.getShort("grantAmount"));
+	}
+
+	/*
+	 * Getters
+	 */
+
+	public ConcurrentHashMap<Integer, Short> getRuneGrants() {
+		return this.runeGrants;
+	}
+
+	public int getToken() {
+		return this.token;
+	}
+
+	private void addRuneGrant(int runeID, short amount) {
+		this.runeGrants.put(runeID, amount);
+	}
+
+
+	/*
+	 * Database
+	 */
+
+	public static Short getGrantedTrains(int token, PlayerCharacter pc) {
+		if (pc == null)
+			return (short) 0;
+
+		if (PowerGrant.grantedPowers == null)
+			fillGrantedPowers();
+
+		if (PowerGrant.grantedPowers.containsKey(token)) {
+			PowerGrant pg = PowerGrant.grantedPowers.get(token);
+            ConcurrentHashMap<Integer, Short> runeGrants = pg.runeGrants;
+			ArrayList<Integer> toks = new ArrayList<>();
+
+			//get race ID
+			Race race = pc.getRace();
+			if (race != null)
+				toks.add(race.getRaceRuneID());
+
+			//get baseClass ID
+			BaseClass bc = pc.getBaseClass();
+			if (bc != null)
+				toks.add(bc.getObjectUUID());
+
+			//get promoClass ID
+			PromotionClass pcc = pc.getPromotionClass();
+			if (pcc != null)
+				toks.add(pcc.getObjectUUID());
+
+			//get promotion and base class combined ID
+			if (bc != null && pcc != null)
+				toks.add( ((pcc.getObjectUUID() * 10) + bc.getObjectUUID()) );
+
+			//get any other rune IDs
+			ArrayList<CharacterRune> runes = pc.getRunes();
+			for (CharacterRune rune : runes)
+				toks.add(rune.getRuneBaseID());
+
+			//Add any power bonuses granted from runes up
+			short amount = (short) 0;
+			for (Integer tok : toks) {
+				if (runeGrants.containsKey(tok))
+					amount += runeGrants.get(tok);
+			}
+
+			return amount;
+		} else
+			return (short) 0;
+	}
+
+	public static void fillGrantedPowers() {
+		PowerGrant.grantedPowers = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW);
+		PreparedStatementShared ps = null;
+		try {
+			ps = prepareStatement("SELECT * FROM static_power_powergrant");
+			ResultSet rs = ps.executeQuery();
+			if (PowerGrant.grantedPowers.size() > 0) {
+				rs.close();
+				return;
+			}
+			while (rs.next()) {
+				int token = rs.getInt("powerToken");
+				PowerGrant pg = null;
+				if (PowerGrant.grantedPowers.containsKey(token)) {
+					pg = PowerGrant.grantedPowers.get(token);
+					pg.addRuneGrant(rs.getInt("runeID"), rs.getShort("grantAmount"));
+				} else {
+					pg = new PowerGrant(rs);
+					PowerGrant.grantedPowers.put(token, pg);
+				}
+			}
+			rs.close();
+		} catch (SQLException e) {
+			Logger.error( "SQL Error number: " + e.getErrorCode(), e);
+		} finally {
+			ps.release();
+		}
+	}
+
+	@Override
+	public void updateDatabase() {}
+}
diff --git a/src/engine/objects/PowerReq.java b/src/engine/objects/PowerReq.java
new file mode 100644
index 00000000..7c5548d3
--- /dev/null
+++ b/src/engine/objects/PowerReq.java
@@ -0,0 +1,194 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.objects;
+
+import engine.gameManager.PowersManager;
+import engine.powers.PowersBase;
+import engine.server.MBServerStatics;
+import org.pmw.tinylog.Logger;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.concurrent.ConcurrentHashMap;
+
+
+public class PowerReq extends AbstractGameObject implements Comparable<PowerReq> {
+
+	private PowersBase powersBase;
+	private int token;
+	private short level;
+	private ConcurrentHashMap<Integer, Byte> powerReqs;
+	private ConcurrentHashMap<Integer, Byte> skillReqs;
+
+	private static ConcurrentHashMap<Integer, ArrayList<PowerReq>> runePowers = fillRunePowers();
+	private static ArrayList<PowerReq> powersForAll = new ArrayList<>();
+
+	/**
+	 * No Table ID Constructor
+	 */
+	public PowerReq(PowersBase powersBase, short level, ConcurrentHashMap<Integer, Byte> powerReqs, ConcurrentHashMap<Integer, Byte> skillReqs) {
+		super();
+		this.powersBase = powersBase;
+		this.level = level;
+		this.powerReqs = powerReqs;
+		this.skillReqs = skillReqs;
+		if (this.powersBase != null)
+			this.token = this.powersBase.getToken();
+		else
+			this.token = 0;
+	}
+
+	/**
+	 * Normal Constructor
+	 */
+	public PowerReq(PowersBase powersBase, short level, ConcurrentHashMap<Integer, Byte> powerReqs, ConcurrentHashMap<Integer, Byte> skillReqs, int newUUID) {
+		super(newUUID);
+		this.powersBase = powersBase;
+		this.level = level;
+		this.powerReqs = powerReqs;
+		this.skillReqs = skillReqs;
+		if (this.powersBase != null)
+			this.token = this.powersBase.getToken();
+		else
+			this.token = 0;
+	}
+
+	/**
+	 * ResultSet Constructor
+	 */
+	public PowerReq(ResultSet rs) throws SQLException {
+		super(rs);
+		this.token = rs.getInt("powerToken");
+		this.powersBase = PowersManager.getPowerByToken(this.token);
+		this.level = rs.getShort("level");
+		int type = rs.getInt("type");
+		this.powerReqs = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW);
+		this.skillReqs = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW);
+		if (type == 1)
+			this.skillReqs.put(rs.getInt("requiredToken"), rs.getByte("requiredAmount"));
+		else if (type == 2)
+			this.powerReqs.put(rs.getInt("requiredToken"), rs.getByte("requiredAmount"));
+	}
+
+	/*
+	 * Getters
+	 */
+	public PowersBase getPowersBase() {
+		if (this.powersBase == null) {
+			this.powersBase = PowersManager.getPowerByToken(this.token);
+		}
+		return this.powersBase;
+	}
+
+	public short getLevel() {
+		return this.level;
+	}
+
+	public ConcurrentHashMap<Integer, Byte> getPowerReqs() {
+		return this.powerReqs;
+	}
+
+	public ConcurrentHashMap<Integer, Byte> getSkillReqs() {
+		return this.skillReqs;
+	}
+
+	public int getToken() {
+		return this.token;
+	}
+
+	private void addPower(int token, byte amount) {
+		this.powerReqs.put(token, amount);
+	}
+
+	private void addSkill(int token, byte amount) {
+		this.skillReqs.put(token, amount);
+	}
+
+
+	@Override
+	public int compareTo(PowerReq n) throws ClassCastException {
+		if (n.level == this.level)
+			return 0;
+		else if (this.level > n.level)
+			return 1;
+		else
+			return -1;
+	}
+
+
+	/*
+	 * Database
+	 */
+
+	public static ArrayList<PowerReq> getPowerReqsForRune(int id) {
+//		if (PowerReq.runePowers == null)
+//			fillRunePowers();
+		if (PowerReq.runePowers.containsKey(id))
+			return PowerReq.runePowers.get(id);
+		return new ArrayList<>();
+	}
+
+	public static ConcurrentHashMap<Integer, ArrayList<PowerReq>> fillRunePowers() {
+		PowerReq.runePowers = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW);
+		PreparedStatementShared ps = null;
+		try {
+			ps = prepareStatement("SELECT * FROM static_power_powerrequirement");
+			ResultSet rs = ps.executeQuery();
+			if (PowerReq.runePowers.size() > 0) {
+				rs.close();
+				return PowerReq.runePowers;
+			}
+			while (rs.next()) {
+				ArrayList<PowerReq> runePR = null;
+				int runeID = rs.getInt("runeID");
+				int token = rs.getInt("powerToken");
+				if (PowerReq.runePowers.containsKey(runeID))
+					runePR = PowerReq.runePowers.get(runeID);
+				else {
+					runePR = new ArrayList<>();
+					PowerReq.runePowers.put(runeID, runePR);
+				}
+				boolean found = false;
+				for (PowerReq pr : runePR) {
+                    if (pr.token == token) {
+						int type = rs.getInt("type");
+						if (type == 1)
+							pr.addSkill(rs.getInt("requiredToken"), rs.getByte("requiredAmount"));
+						else
+							pr.addPower(rs.getInt("requiredToken"), rs.getByte("requiredAmount"));
+						found = true;
+					}
+				}
+				if (!found) {
+					PowerReq pr = new PowerReq(rs);
+					runePR.add(pr);
+				}
+			}
+			rs.close();
+
+			//order the lists by level so prerequisites are met
+			for (ArrayList<PowerReq> runePR : PowerReq.runePowers.values()) {
+				Collections.sort(runePR);
+			}
+		} catch (SQLException e) {
+			Logger.error( "SQL Error number: " + e.getErrorCode(), e);
+		} finally {
+			ps.release();
+		}
+		return PowerReq.runePowers;
+	}
+	
+	@Override
+	public void updateDatabase() {
+
+	}
+}
\ No newline at end of file
diff --git a/src/engine/objects/PowersBaseAttribute.java b/src/engine/objects/PowersBaseAttribute.java
new file mode 100644
index 00000000..5a479a0b
--- /dev/null
+++ b/src/engine/objects/PowersBaseAttribute.java
@@ -0,0 +1,65 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+ package engine.objects;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+
+public class PowersBaseAttribute extends AbstractGameObject {
+
+	private final short attributeID;
+	private final short modValue;
+	private final short castTime;
+	private final short duration;
+	private final short recycleTime;
+
+	/**
+	 * ResultSet Constructor
+	 */
+	public PowersBaseAttribute(ResultSet rs) throws SQLException {
+		super(rs);
+
+		this.attributeID = rs.getShort("attributeID");
+		this.modValue = rs.getShort("modValue");
+		this.castTime = rs.getShort("castTime");
+		this.duration = rs.getShort("duration");
+		this.recycleTime = rs.getShort("recycleTime");
+	}
+
+	/*
+	 * Getters
+	 */
+	public short getAttributeID() {
+		return attributeID;
+	}
+
+	public short getModValue() {
+		return modValue;
+	}
+
+	public short getCastTime() {
+		return castTime;
+	}
+
+	public short getDuration() {
+		return duration;
+	}
+
+	public short getRecycleTime() {
+		return recycleTime;
+	}
+
+
+	@Override
+	public void updateDatabase() {
+		// TODO Create update logic.
+	}
+}
diff --git a/src/engine/objects/PreparedStatementShared.java b/src/engine/objects/PreparedStatementShared.java
new file mode 100644
index 00000000..436956fe
--- /dev/null
+++ b/src/engine/objects/PreparedStatementShared.java
@@ -0,0 +1,1264 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.objects;
+
+import engine.gameManager.DbManager;
+import engine.job.JobScheduler;
+import engine.jobs.BasicScheduledJob;
+import engine.server.MBServerStatics;
+import org.pmw.tinylog.Logger;
+
+import java.io.InputStream;
+import java.io.Reader;
+import java.math.BigDecimal;
+import java.net.URL;
+import java.sql.*;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * A thread-safe sharing implementation of {@link PreparedStatement}.
+ * <p>
+ * All of the methods from the PreparedStatement interface simply check to see
+ * that the PreparedStatement is active, and call the corresponding method on
+ * that PreparedStatement.
+ * 
+ * @author Burfo
+ * @see PreparedStatement
+ * 
+ **/
+public class PreparedStatementShared implements PreparedStatement  {
+	private static final ConcurrentHashMap<Integer, LinkedList<PreparedStatement>> statementList = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW);
+	private static final ArrayList<PreparedStatementShared> statementListDelegated = new ArrayList<>();
+	private static final String ExceptionMessage = "PreparedStatementShared object " + "was accessed after being released.";
+	private static boolean debuggingIsOn;
+	
+	private PreparedStatement ps = null;
+	private int sqlHash;
+	private String sql;
+	private long delegatedTime;
+	
+	//debugging variables
+	private StackTraceElement[] stackTrace;
+	private DebugParam[] variables;
+	private String filteredSql;
+
+	private class DebugParam {
+	    private Object debugObject;
+	    private boolean valueAssigned;
+
+	    public DebugParam(Object debugObject){
+	        this.debugObject = debugObject;
+	        valueAssigned = true;
+	    }
+
+	    public Object getDebugObject(){
+	        return debugObject;
+	    }
+
+	    public boolean isValueAssigned(){
+	        return valueAssigned;
+	    }
+        }
+        
+        @Override
+            public boolean isCloseOnCompletion() {
+		return true;
+	}
+        @Override
+            public void closeOnCompletion() {
+		Logger.warn( "Prepared Statement Closed");
+	}
+	
+	/**
+	 * Generates a new PreparedStatementShared based on the specified sql.
+	 * 
+	 * @param sql
+	 *            Query string to generate the PreparedStatement
+	 * @throws SQLException
+	 **/
+	public PreparedStatementShared(String sql) throws SQLException {
+		this.sqlHash = sql.hashCode();
+		this.sql = sql;
+		this.delegatedTime = System.currentTimeMillis();
+		this.ps = getFromPool(sql, sqlHash);
+		if (this.ps == null) {
+			this.ps = createNew(sql, sqlHash);
+		}
+		
+		
+		
+		if (debuggingIsOn) {
+			//see if there are any '?' in the statement that are not bind variables
+	        //and filter them out.
+	        boolean isString = false;
+	        char[] sqlString = this.sql.toCharArray();
+	        for (int i = 0; i < sqlString.length; i++){
+	            if (sqlString[i] == '\'')
+	                isString = !isString;
+	            //substitute the ? with an unprintable character if is in a string
+	            if (sqlString[i] == '?' && isString)
+	                sqlString[i] = '\u0007';
+	        }
+	        this.filteredSql = new String(sqlString);
+	        
+	        //find out how many variables are present in statement.
+	        int count = 0;
+	        int index = -1;
+	        while ((index = filteredSql.indexOf('?',index+1)) != -1){
+	            count++;
+	        }
+	        
+	        //create variables array with size equal to count of variables
+	        this.variables = new DebugParam[count];
+	        
+	        this.stackTrace = Thread.currentThread().getStackTrace();
+	        
+		} else {
+			this.stackTrace = null;
+			this.variables = null;
+			this.filteredSql = null;
+		}
+		
+		synchronized (statementListDelegated) {
+			statementListDelegated.add(this);
+		}
+
+	}
+
+	private static PreparedStatement getFromPool(String sql, int sqlHash) throws SQLException {
+		PreparedStatement ps = null;
+
+		if (statementList.containsKey(sqlHash)) {
+			LinkedList<PreparedStatement> list = statementList.get(sqlHash);
+			if (list == null) { // Shouldn't happen b/c no keys are ever removed
+				throw new AssertionError("list cannot be null.");
+			}
+			boolean success = false;
+			synchronized (list) {
+				do {
+					ps = list.pollFirst();
+					if (ps == null) {
+						break;
+					}
+					if (ps.isClosed()) { // should rarely happen
+						Logger.warn("A closed PreparedStatement was removed "
+								+ "from AbstractGameObject statementList. " + "SQL: " + sql);
+					} else {
+						success = true;
+					}
+				} while (!success);
+			}
+
+			if (ps != null) {
+				if (MBServerStatics.DB_DEBUGGING_ON_BY_DEFAULT) {
+					Logger.info("Found cached PreparedStatement for SQL hash: " + sqlHash
+							+ " SQL String: " + sql);
+				}
+			}
+		}
+		return ps;
+	}
+
+	private static PreparedStatement createNew(String sql, int sqlHash) throws SQLException {
+		statementList.putIfAbsent(sqlHash, new LinkedList<>());
+		return DbManager.prepareStatement(sql);
+	}
+
+	/**
+	 * Releases the use of a PreparedStatementShared that was generated by
+	 * {@link AbstractGameObject#prepareStatement}, making it available for use
+	 * by another query.
+	 * <p>
+	 * Do not utilize or modify the object after calling this method.
+	 * <p>
+	 * Example:
+	 * 
+	 * <pre>
+	 * &#064;code
+	 * PreparedStatementShared ps = prepareStatement(...);
+	 * ps.executeUpdate();
+	 * ps.release();
+	 * ps = null;}
+	 * </pre>
+	 **/
+	public void release() {
+		if (this.ps == null) {
+			return;
+		} // nothing to release
+		if (statementListDelegated.contains(this)) {
+			synchronized (statementListDelegated) {
+				statementListDelegated.remove(this);
+			}
+			try {
+				if (this.ps.isClosed()) {
+					return;
+				}
+				this.ps.clearParameters();
+				this.variables = null;
+			} catch (SQLException ignore) {
+			}
+
+			// add back to pool
+			LinkedList<PreparedStatement> list = statementList.get(this.sqlHash);
+			if (list == null) {
+				return;
+			}
+			synchronized (list) {
+				list.add(this.ps);
+			}
+		}
+		// clear values from this object so caller cannot use it after it has
+		// been released
+		this.ps = null;
+		this.sqlHash = 0;
+		this.sql = "";
+		this.delegatedTime = 0;
+		this.stackTrace = null;
+	}
+
+	/**
+	 * Determines if the object is in a usable state.
+	 * 
+	 * @return True if the object is in a useable state.
+	 **/
+	public boolean isUsable() {
+		if (ps == null) {
+			return false;
+		}
+		try {
+			if (ps.isClosed()) {
+				return false;
+			}
+		} catch (SQLException e) {
+			return false;
+		}
+		return true;
+	}
+
+	private String getTraceInfo() {
+		if (stackTrace == null) {
+			return "<no debug data>";
+		}
+
+		if (stackTrace.length > 3) {
+			return stackTrace[3].getClassName() + '.' + stackTrace[3].getMethodName();
+		} else if (stackTrace.length == 0) {
+			return "<unavailable>";
+		} else {
+			return stackTrace[stackTrace.length - 1].getClassName() + '.' + stackTrace[stackTrace.length - 1].getMethodName();
+		}
+	}
+
+	public static void submitPreparedStatementsCleaningJob() {
+		JobScheduler.getInstance().scheduleJob(new BasicScheduledJob("cleanUnreleasedStatements", PreparedStatementShared.class), 1000 * 60 * 2); // 2
+		// minutes
+	}
+
+	public static void cleanUnreleasedStatements() {
+		long now = System.currentTimeMillis();
+		long timeLimit = 120000; // 2 minutes
+
+		synchronized (statementListDelegated) {
+			Iterator<PreparedStatementShared> iterator = statementListDelegated.iterator();
+			while (iterator.hasNext()) {
+				PreparedStatementShared pss = iterator.next();
+				if ((pss.delegatedTime + timeLimit) >= now) {
+					continue;
+				}
+				iterator.remove();
+
+				Logger.warn("Forcefully released after being held for > 2 minutes." + " SQL STRING: \""
+						+ pss.sql + "\" METHOD: " + pss.getTraceInfo());
+			}
+		}
+
+		submitPreparedStatementsCleaningJob(); // resubmit
+	}
+
+        @Override
+	public boolean equals(Object obj) {
+		if (ps == null || obj == null) {
+			return false;
+		}
+
+		if (obj instanceof PreparedStatementShared) {
+			return this.ps.equals(((PreparedStatementShared) obj).ps);
+		}
+
+		if (obj instanceof PreparedStatement) {
+			return this.ps.equals(obj);
+		}
+
+		return false;
+	}
+	
+	@Override
+	public String toString(){
+        if (!debuggingIsOn || variables == null) {
+        	return "SQL: " + this.sql + " (enable DB debugging for more data)";
+        }
+		
+        String out;
+        
+        out = "SQL: [" + this.sql + "] ";
+        out += "VARIABLES[count=" + variables.length + "]: ";
+        
+        for (int i=0; i<variables.length; i++) {
+        	out+= "[" + (i+1) + "]: "; 
+        	DebugParam dp = variables[i];
+        	if (dp == null || !dp.isValueAssigned()) {
+        		out += "{MISSING} ";
+        		continue;
+        	}
+        	Object dpObj = dp.getDebugObject();
+        	out += dpObj.toString() + ' ';
+        }
+        return out;
+    }
+
+	public static void enableDebugging() {
+		debuggingIsOn = true;
+		Logger.info( "Database debugging has been enabled.");
+	}
+	
+	public static void disableDebugging() {
+		debuggingIsOn = false;
+		Logger.info( "Database debugging has been disabled.");
+	}
+	
+	@Override
+	public void addBatch() throws SQLException {
+		if (this.ps == null) {
+			throw new SQLException();
+		}
+		this.ps.addBatch();
+
+	}
+
+	private void saveObject(int parameterIndex, Object obj) throws SQLException {
+		if (!debuggingIsOn || this.variables == null) {
+			return;
+		}
+		
+		if (parameterIndex > variables.length){
+			throw new SQLException("Parameter index of " + parameterIndex + 
+					" exceeds actual parameter count of " + this.variables.length);
+		}
+		
+		this.variables[parameterIndex-1] = new DebugParam(obj);
+	}
+	
+	private void logExceptionAndRethrow(SQLException e) throws SQLException {
+		Logger.error("SQL operation failed: (" +
+				e.getMessage() + ") " + this.toString(), e);
+		throw e;
+	}
+	
+	@Override
+	public void clearParameters() throws SQLException {
+		if (this.ps == null) {
+			throw new SQLException(ExceptionMessage);
+		}
+
+		this.ps.clearParameters();
+		for (int i=0; i<this.variables.length; i++)	{
+			this.variables[i] = null;
+		}
+		
+	}
+
+	@Override
+	public boolean execute() throws SQLException {
+		if (this.ps == null) {
+			throw new SQLException(ExceptionMessage);
+		}
+		
+		if(debuggingIsOn || MBServerStatics.ENABLE_EXECUTION_TIME_WARNING) {
+			long startTime = System.currentTimeMillis();
+			boolean rs = false;
+			try {
+				rs = this.ps.execute();
+			} catch (SQLException e) {
+				logExceptionAndRethrow(e);
+			}
+			if((startTime + MBServerStatics.DB_EXECUTION_WARNING_TIME_MS) < System.currentTimeMillis())
+				Logger.warn("The following statement took " + (System.currentTimeMillis() - startTime)
+						+ " millis to execute: " + this.sql);
+			return rs;
+		}
+		
+		return this.ps.execute();
+	}
+
+	@Override
+	public ResultSet executeQuery() throws SQLException, SQLException {
+		if (this.ps == null) {
+			throw new SQLException(ExceptionMessage);
+		}
+		
+		if(debuggingIsOn || MBServerStatics.ENABLE_QUERY_TIME_WARNING) {
+			long startTime = System.currentTimeMillis();
+			ResultSet rs = null;
+			try {
+				rs = this.ps.executeQuery();
+			} catch (SQLException e) {
+				logExceptionAndRethrow(e);
+			}
+			if((startTime + MBServerStatics.DB_QUERY_WARNING_TIME_MS) < System.currentTimeMillis())
+				Logger.warn("The following query took " + (System.currentTimeMillis() - startTime)
+						+ " millis to execute: " + this.sql);
+			return rs;
+		}
+		
+		return this.ps.executeQuery();
+	}
+
+	@Override
+	public int executeUpdate() throws SQLException {
+		if (this.ps == null) {
+			throw new SQLException(ExceptionMessage);
+		}
+		
+		if(debuggingIsOn || MBServerStatics.ENABLE_UPDATE_TIME_WARNING) {
+			long startTime = System.currentTimeMillis();
+			int rs = 0;
+			try {
+				rs = this.ps.executeUpdate();
+			} catch (SQLException e) {
+				logExceptionAndRethrow(e);
+			}
+			if((startTime + MBServerStatics.DB_UPDATE_WARNING_TIME_MS) < System.currentTimeMillis())
+				Logger.warn("The following update took " + (System.currentTimeMillis() - startTime)
+						+ " millis to execute: " + this.sql);
+			return rs;
+		}
+		
+		return this.ps.executeUpdate();
+	}
+
+	@Override
+	public ResultSetMetaData getMetaData() throws SQLException {
+		if (this.ps == null) {
+			throw new SQLException(ExceptionMessage);
+		}
+		return this.ps.getMetaData();
+	}
+
+	@Override
+	public ParameterMetaData getParameterMetaData() throws SQLException {
+		if (this.ps == null) {
+			throw new SQLException(ExceptionMessage);
+		}
+		return this.ps.getParameterMetaData();
+	}
+
+	@Override
+	public void setArray(int parameterIndex, Array x) throws SQLException {
+		if (this.ps == null) {
+			throw new SQLException(ExceptionMessage);
+		}
+		this.saveObject(parameterIndex, x);
+		this.ps.setArray(parameterIndex, x);
+	}
+
+	@Override
+	public void setAsciiStream(int parameterIndex, InputStream x) throws SQLException {
+		if (this.ps == null) {
+			throw new SQLException(ExceptionMessage);
+		}
+		saveObject(parameterIndex, (x==null?"NULL":"<stream>"));
+		this.ps.setAsciiStream(parameterIndex, x);
+	}
+
+	@Override
+	public void setAsciiStream(int parameterIndex, InputStream x, int length) throws SQLException {
+		if (this.ps == null) {
+			throw new SQLException(ExceptionMessage);
+		}
+		saveObject(parameterIndex, (x==null?"NULL":"<stream length= " + length+ '>'));
+		this.ps.setAsciiStream(parameterIndex, x, length);
+	}
+
+	@Override
+	public void setAsciiStream(int parameterIndex, InputStream x, long length) throws SQLException {
+		if (this.ps == null) {
+			throw new SQLException(ExceptionMessage);
+		}
+		saveObject(parameterIndex, (x==null?"NULL":"<stream length= " + length+ '>'));
+		this.ps.setAsciiStream(parameterIndex, x, length);
+	}
+
+	@Override
+	public void setBigDecimal(int parameterIndex, BigDecimal x) throws SQLException {
+		if (this.ps == null) {
+			throw new SQLException(ExceptionMessage);
+		}
+		saveObject(parameterIndex, x);
+		this.ps.setBigDecimal(parameterIndex, x);
+	}
+
+	@Override
+	public void setBinaryStream(int parameterIndex, InputStream x) throws SQLException {
+		if (this.ps == null) {
+			throw new SQLException(ExceptionMessage);
+		}
+		saveObject(parameterIndex, (x==null?"NULL":"<stream>"));
+		this.ps.setBinaryStream(parameterIndex, x);
+	}
+
+	@Override
+	public void setBinaryStream(int parameterIndex, InputStream x, int length) throws SQLException {
+		if (this.ps == null) {
+			throw new SQLException(ExceptionMessage);
+		}
+		saveObject(parameterIndex, (x==null?"NULL":"<stream length= " + length+ '>'));
+		this.ps.setBinaryStream(parameterIndex, x, length);
+	}
+
+	@Override
+	public void setBinaryStream(int parameterIndex, InputStream x, long length) throws SQLException {
+		if (this.ps == null) {
+			throw new SQLException(ExceptionMessage);
+		}
+		saveObject(parameterIndex, (x==null?"NULL":"<stream length= " + length+ '>'));
+		this.ps.setBinaryStream(parameterIndex, x, length);
+	}
+
+	@Override
+	public void setBlob(int parameterIndex, Blob x) throws SQLException {
+		if (this.ps == null) {
+			throw new SQLException(ExceptionMessage);
+		}
+		saveObject(parameterIndex, x);
+		this.ps.setBlob(parameterIndex, x);
+	}
+
+	@Override
+	public void setBlob(int parameterIndex, InputStream x) throws SQLException {
+		if (this.ps == null) {
+			throw new SQLException(ExceptionMessage);
+		}
+		saveObject(parameterIndex, (x==null?"NULL":"<blob stream>"));
+		this.ps.setBlob(parameterIndex, x);
+	}
+
+	@Override
+	public void setBlob(int parameterIndex, InputStream x, long length) throws SQLException {
+		if (this.ps == null) {
+			throw new SQLException(ExceptionMessage);
+		}
+		saveObject(parameterIndex, (x==null?"NULL":"<blob stream length= " + length+ '>'));
+		this.ps.setBlob(parameterIndex, x, length);
+	}
+
+	@Override
+	public void setBoolean(int parameterIndex, boolean x) throws SQLException {
+		if (this.ps == null) {
+			throw new SQLException(ExceptionMessage);
+		}
+		saveObject(parameterIndex, new Boolean(x));
+		this.ps.setBoolean(parameterIndex, x);
+	}
+
+	@Override
+	public void setByte(int parameterIndex, byte x) throws SQLException {
+		if (this.ps == null) {
+			throw new SQLException(ExceptionMessage);
+		}
+		saveObject(parameterIndex, new Byte(x));
+		this.ps.setByte(parameterIndex, x);
+	}
+
+	@Override
+	public void setBytes(int parameterIndex, byte[] x) throws SQLException {
+		if (this.ps == null) {
+			throw new SQLException(ExceptionMessage);
+		}
+		saveObject(parameterIndex, (x==null?"NULL":"byte[] length="+x.length));
+		this.ps.setBytes(parameterIndex, x);
+	}
+
+	@Override
+	public void setCharacterStream(int parameterIndex, Reader reader) throws SQLException {
+		if (this.ps == null) {
+			throw new SQLException(ExceptionMessage);
+		}
+		saveObject(parameterIndex, (reader==null?"NULL":"<stream>"));
+		this.ps.setCharacterStream(parameterIndex, reader);
+	}
+
+	@Override
+	public void setCharacterStream(int parameterIndex, Reader reader, int length) throws SQLException {
+		if (this.ps == null) {
+			throw new SQLException(ExceptionMessage);
+		}
+		saveObject(parameterIndex, (reader==null?"NULL":"<stream length= " + length+ '>'));
+		this.ps.setCharacterStream(parameterIndex, reader, length);
+	}
+
+	@Override
+	public void setCharacterStream(int parameterIndex, Reader reader, long length) throws SQLException {
+		if (this.ps == null) {
+			throw new SQLException(ExceptionMessage);
+		}
+		saveObject(parameterIndex, (reader==null?"NULL":"<stream length= " + length+ '>'));
+		this.ps.setCharacterStream(parameterIndex, reader, length);
+	}
+
+	@Override
+	public void setClob(int parameterIndex, Clob x) throws SQLException {
+		if (this.ps == null) {
+			throw new SQLException(ExceptionMessage);
+		}
+		saveObject(parameterIndex, x);
+		this.ps.setClob(parameterIndex, x);
+	}
+
+	@Override
+	public void setClob(int parameterIndex, Reader reader) throws SQLException {
+		if (this.ps == null) {
+			throw new SQLException(ExceptionMessage);
+		}
+		saveObject(parameterIndex, (reader==null?"NULL":"<stream>"));
+		this.ps.setClob(parameterIndex, reader);
+	}
+
+	@Override
+	public void setClob(int parameterIndex, Reader reader, long length) throws SQLException {
+		if (this.ps == null) {
+			throw new SQLException(ExceptionMessage);
+		}
+		saveObject(parameterIndex, (reader==null?"NULL":"<stream length= " + length+ '>'));
+		this.ps.setClob(parameterIndex, reader, length);
+	}
+
+	@Override
+	public void setDate(int parameterIndex, Date x) throws SQLException {
+		if (this.ps == null) {
+			throw new SQLException(ExceptionMessage);
+		}
+		saveObject(parameterIndex,x);
+		this.ps.setDate(parameterIndex, x);
+	}
+
+	@Override
+	public void setDate(int parameterIndex, Date x, Calendar cal) throws SQLException {
+		if (this.ps == null) {
+			throw new SQLException(ExceptionMessage);
+		}
+		saveObject(parameterIndex,x);
+		this.ps.setDate(parameterIndex, x, cal);
+	}
+
+	@Override
+	public void setDouble(int parameterIndex, double x) throws SQLException {
+		if (this.ps == null) {
+			throw new SQLException(ExceptionMessage);
+		}
+		saveObject(parameterIndex,new Double(x));
+		this.ps.setDouble(parameterIndex, x);
+	}
+
+	@Override
+	public void setFloat(int parameterIndex, float x) throws SQLException {
+		if (this.ps == null) {
+			throw new SQLException(ExceptionMessage);
+		}
+		saveObject(parameterIndex,new Float(x));
+		this.ps.setFloat(parameterIndex, x);
+	}
+
+    /**
+     * Sets the designated parameter to the given Java <code>int</code> value.  
+     * The driver converts this
+     * to an SQL <code>INTEGER</code> value when it sends it to the database.
+     *
+     * @param parameterIndex the first parameter is 1, the second is 2, ...
+     * @param x the parameter value
+     * @param setZeroAsNull Converts an int value of 0 to an SQL NULL.
+     *        Should be set TRUE on INSERTS and UPDATES for record pointers
+     *        (e.g. GuildID) and FALSE for data elements (e.g. player's level).
+     * @exception SQLException if parameterIndex does not correspond to a parameter
+     * marker in the SQL statement; if a database access error occurs or 
+     * this method is called on a closed <code>PreparedStatement</code>
+     */
+	public void setInt(int parameterIndex, int x, boolean setZeroAsNull) throws SQLException {
+		if (this.ps == null) {
+			throw new SQLException(ExceptionMessage);
+		}
+		if (setZeroAsNull && x == 0) {
+			this.ps.setNull(parameterIndex, java.sql.Types.INTEGER);
+			saveObject(parameterIndex,"NULL");
+			return;
+		}
+		saveObject(parameterIndex,new Integer(x));
+		this.ps.setInt(parameterIndex, x);
+
+	}
+	
+	@Override
+	public void setInt(int parameterIndex, int x) throws SQLException {
+		setInt(parameterIndex, x, false);
+	}
+	
+
+	@Override
+	public void setLong(int parameterIndex, long x) throws SQLException {
+		if (this.ps == null) {
+			throw new SQLException(ExceptionMessage);
+		}
+		saveObject(parameterIndex,new Long(x));
+		this.ps.setLong(parameterIndex, x);
+	}
+
+	@Override
+	public void setNCharacterStream(int parameterIndex, Reader value) throws SQLException {
+		if (this.ps == null) {
+			throw new SQLException(ExceptionMessage);
+		}
+		saveObject(parameterIndex, (value==null?"NULL":"<stream>"));
+		this.ps.setNCharacterStream(parameterIndex, value);
+	}
+
+	@Override
+	public void setNCharacterStream(int parameterIndex, Reader value, long length) throws SQLException {
+		if (this.ps == null) {
+			throw new SQLException(ExceptionMessage);
+		}
+		saveObject(parameterIndex, (value==null?"NULL":"<stream length= " + length+ '>'));
+		this.ps.setNCharacterStream(parameterIndex, value, length);
+	}
+
+	@Override
+	public void setNClob(int parameterIndex, NClob value) throws SQLException {
+		if (this.ps == null) {
+			throw new SQLException(ExceptionMessage);
+		}
+		saveObject(parameterIndex, (value==null?"NULL":"<stream>"));
+		this.ps.setNClob(parameterIndex, value);
+	}
+
+	@Override
+	public void setNClob(int parameterIndex, Reader reader) throws SQLException {
+		if (this.ps == null) {
+			throw new SQLException(ExceptionMessage);
+		}
+		saveObject(parameterIndex, (reader==null?"NULL":"<stream>"));
+		this.ps.setNClob(parameterIndex, reader);
+	}
+
+	@Override
+	public void setNClob(int parameterIndex, Reader reader, long length) throws SQLException {
+		if (this.ps == null) {
+			throw new SQLException(ExceptionMessage);
+		}
+		saveObject(parameterIndex, (reader==null?"NULL":"<stream length= " + length+ '>'));
+		this.ps.setNClob(parameterIndex, reader, length);
+	}
+
+	@Override
+	public void setNString(int parameterIndex, String value) throws SQLException {
+		if (this.ps == null) {
+			throw new SQLException(ExceptionMessage);
+		}
+		saveObject(parameterIndex, value);
+		this.ps.setNString(parameterIndex, value);
+	}
+
+	@Override
+	public void setNull(int parameterIndex, int sqlType) throws SQLException {
+		if (this.ps == null) {
+			throw new SQLException(ExceptionMessage);
+		}
+		saveObject(parameterIndex, "NULL");
+		this.ps.setNull(parameterIndex, sqlType);
+	}
+
+	@Override
+	public void setNull(int parameterIndex, int sqlType, String typeName) throws SQLException {
+		if (this.ps == null) {
+			throw new SQLException(ExceptionMessage);
+		}
+		saveObject(parameterIndex, "NULL");
+		this.ps.setNull(parameterIndex, sqlType, typeName);
+	}
+
+	@Override
+	public void setObject(int parameterIndex, Object x) throws SQLException {
+		if (this.ps == null) {
+			throw new SQLException(ExceptionMessage);
+		}
+		saveObject(parameterIndex, (x==null?"NULL":x.getClass().getName()));
+		this.ps.setObject(parameterIndex, x);
+	}
+
+	@Override
+	public void setObject(int parameterIndex, Object x, int targetSqlType) throws SQLException {
+		if (this.ps == null) {
+			throw new SQLException(ExceptionMessage);
+		}
+		saveObject(parameterIndex, (x==null?"NULL":x.getClass().getName()));
+		this.ps.setObject(parameterIndex, x, targetSqlType);
+	}
+
+	@Override
+	public void setObject(int parameterIndex, Object x, int targetSqlType, int scaleOrLength) throws SQLException {
+		if (this.ps == null) {
+			throw new SQLException(ExceptionMessage);
+		}
+		saveObject(parameterIndex, (x==null?"NULL":x.getClass().getName()));
+		this.ps.setObject(parameterIndex, x, targetSqlType, scaleOrLength);
+	}
+
+	@Override
+	public void setRef(int parameterIndex, Ref x) throws SQLException {
+		if (this.ps == null) {
+			throw new SQLException(ExceptionMessage);
+		}
+		saveObject(parameterIndex, x);
+		this.ps.setRef(parameterIndex, x);
+	}
+
+	@Override
+	public void setRowId(int parameterIndex, RowId x) throws SQLException {
+		if (this.ps == null) {
+			throw new SQLException(ExceptionMessage);
+		}
+		saveObject(parameterIndex, x);
+		this.ps.setRowId(parameterIndex, x);
+	}
+
+	@Override
+	public void setSQLXML(int parameterIndex, SQLXML xmlObject) throws SQLException {
+		if (this.ps == null) {
+			throw new SQLException(ExceptionMessage);
+		}
+		saveObject(parameterIndex, xmlObject);
+		this.ps.setSQLXML(parameterIndex, xmlObject);
+	}
+
+	@Override
+	public void setShort(int parameterIndex, short x) throws SQLException {
+		if (this.ps == null) {
+			throw new SQLException(ExceptionMessage);
+		}
+		saveObject(parameterIndex, new Short(x));
+		this.ps.setShort(parameterIndex, x);
+	}
+
+	@Override
+	public void setString(int parameterIndex, String x) throws SQLException {
+		if (this.ps == null) {
+			throw new SQLException(ExceptionMessage);
+		}
+		saveObject(parameterIndex, x);
+		this.ps.setString(parameterIndex, x);
+	}
+
+	@Override
+	public void setTime(int parameterIndex, Time x) throws SQLException {
+		if (this.ps == null) {
+			throw new SQLException(ExceptionMessage);
+		}
+		saveObject(parameterIndex, x);
+		this.ps.setTime(parameterIndex, x);
+	}
+
+	@Override
+	public void setTime(int parameterIndex, Time x, Calendar cal) throws SQLException {
+		if (this.ps == null) {
+			throw new SQLException(ExceptionMessage);
+		}
+		saveObject(parameterIndex, x);
+		this.ps.setTime(parameterIndex, x, cal);
+	}
+
+	@Override
+	public void setTimestamp(int parameterIndex, Timestamp x) throws SQLException {
+		if (this.ps == null) {
+			throw new SQLException(ExceptionMessage);
+		}
+		saveObject(parameterIndex, x);
+		this.ps.setTimestamp(parameterIndex, x);
+	}
+
+	@Override
+	public void setTimestamp(int parameterIndex, Timestamp x, Calendar cal) throws SQLException {
+		if (this.ps == null) {
+			throw new SQLException(ExceptionMessage);
+		}
+		saveObject(parameterIndex, x);
+		this.ps.setTimestamp(parameterIndex, x, cal);
+	}
+
+	@Override
+	public void setURL(int parameterIndex, URL x) throws SQLException {
+		if (this.ps == null) {
+			throw new SQLException(ExceptionMessage);
+		}
+		saveObject(parameterIndex, x);
+		this.ps.setURL(parameterIndex, x);
+	}
+
+	@Override
+	@Deprecated
+	public void setUnicodeStream(int parameterIndex, InputStream x, int length) throws SQLException {
+		throw new UnsupportedOperationException("setUnicodeStream is unsupported");
+		/*
+		if (this.ps == null) {
+			throw new SQLException(ExceptionMessage);
+		}
+		this.ps.setUnicodeStream(parameterIndex, x, length);
+		*/
+	}
+
+	@Override
+	public void addBatch(String sql) throws SQLException {
+		if (this.ps == null) {
+			throw new SQLException(ExceptionMessage);
+		}
+		this.ps.addBatch(sql);
+	}
+
+	@Override
+	public void cancel() throws SQLException {
+		if (this.ps == null) {
+			throw new SQLException(ExceptionMessage);
+		}
+		this.ps.cancel();
+	}
+
+	@Override
+	public void clearBatch() throws SQLException {
+		if (this.ps == null) {
+			throw new SQLException(ExceptionMessage);
+		}
+		this.ps.clearBatch();
+	}
+
+	@Override
+	public void clearWarnings() throws SQLException {
+		if (this.ps == null) {
+			throw new SQLException(ExceptionMessage);
+		}
+		this.ps.clearWarnings();
+	}
+
+	/**
+	 * Redirected to the {@link #release} method.
+	 * 
+	 * @deprecated
+	 **/
+	@Override
+	public void close() throws SQLException {
+		this.release(); // redirect to release method
+	}
+
+	@Override
+	public boolean execute(String sql) throws SQLException {
+		if (this.ps == null) {
+			throw new SQLException(ExceptionMessage);
+		}
+		return this.ps.execute(sql);
+	}
+
+	@Override
+	public boolean execute(String sql, int autoGeneratedKeys) throws SQLException {
+		if (this.ps == null) {
+			throw new SQLException(ExceptionMessage);
+		}
+		return this.ps.execute(sql, autoGeneratedKeys);
+	}
+
+	@Override
+	public boolean execute(String sql, int[] columnIndexes) throws SQLException {
+		if (this.ps == null) {
+			throw new SQLException(ExceptionMessage);
+		}
+		return this.ps.execute(sql, columnIndexes);
+	}
+
+	@Override
+	public boolean execute(String sql, String[] columnNames) throws SQLException {
+		if (this.ps == null) {
+			throw new SQLException(ExceptionMessage);
+		}
+		return this.ps.execute(sql, columnNames);
+	}
+
+	@Override
+	public int[] executeBatch() throws SQLException {
+		if (this.ps == null) {
+			throw new SQLException(ExceptionMessage);
+		}
+		return this.ps.executeBatch();
+	}
+
+	@Override
+	public ResultSet executeQuery(String sql) throws SQLException {
+		if (this.ps == null) {
+			throw new SQLException(ExceptionMessage);
+		}
+		return this.ps.executeQuery(sql);
+	}
+
+	@Override
+	public int executeUpdate(String sql) throws SQLException {
+		if (this.ps == null) {
+			throw new SQLException(ExceptionMessage);
+		}
+		return this.ps.executeUpdate(sql);
+	}
+
+	@Override
+	public int executeUpdate(String sql, int autoGeneratedKeys) throws SQLException {
+		if (this.ps == null) {
+			throw new SQLException(ExceptionMessage);
+		}
+		return this.ps.executeUpdate(sql, autoGeneratedKeys);
+	}
+
+	@Override
+	public int executeUpdate(String sql, int[] columnIndexes) throws SQLException {
+		if (this.ps == null) {
+			throw new SQLException(ExceptionMessage);
+		}
+		return this.ps.executeUpdate(sql, columnIndexes);
+	}
+
+	@Override
+	public int executeUpdate(String sql, String[] columnNames) throws SQLException {
+		if (this.ps == null) {
+			throw new SQLException(ExceptionMessage);
+		}
+		return this.ps.executeUpdate(sql, columnNames);
+	}
+
+	@Override
+	public Connection getConnection() throws SQLException {
+		if (this.ps == null) {
+			throw new SQLException(ExceptionMessage);
+		}
+		return this.ps.getConnection();
+	}
+
+	@Override
+	public int getFetchDirection() throws SQLException {
+		if (this.ps == null) {
+			throw new SQLException(ExceptionMessage);
+		}
+		return this.ps.getFetchDirection();
+	}
+
+	@Override
+	public int getFetchSize() throws SQLException {
+		if (this.ps == null) {
+			throw new SQLException(ExceptionMessage);
+		}
+		return this.ps.getFetchSize();
+	}
+
+	@Override
+	public ResultSet getGeneratedKeys() throws SQLException {
+		if (this.ps == null) {
+			throw new SQLException(ExceptionMessage);
+		}
+		return this.ps.getGeneratedKeys();
+	}
+
+	@Override
+	public int getMaxFieldSize() throws SQLException {
+		if (this.ps == null) {
+			throw new SQLException(ExceptionMessage);
+		}
+		return this.ps.getMaxFieldSize();
+	}
+
+	@Override
+	public int getMaxRows() throws SQLException {
+		if (this.ps == null) {
+			throw new SQLException(ExceptionMessage);
+		}
+		return this.ps.getMaxRows();
+	}
+
+	@Override
+	public boolean getMoreResults() throws SQLException {
+		if (this.ps == null) {
+			throw new SQLException(ExceptionMessage);
+		}
+		return this.ps.getMoreResults();
+	}
+
+	@Override
+	public boolean getMoreResults(int current) throws SQLException {
+		if (this.ps == null) {
+			throw new SQLException(ExceptionMessage);
+		}
+		return this.ps.getMoreResults(current);
+	}
+
+	@Override
+	public int getQueryTimeout() throws SQLException {
+		if (this.ps == null) {
+			throw new SQLException(ExceptionMessage);
+		}
+		return this.ps.getQueryTimeout();
+	}
+
+	@Override
+	public ResultSet getResultSet() throws SQLException {
+		if (this.ps == null) {
+			throw new SQLException(ExceptionMessage);
+		}
+		return this.ps.getResultSet();
+	}
+
+	@Override
+	public int getResultSetConcurrency() throws SQLException {
+		if (this.ps == null) {
+			throw new SQLException(ExceptionMessage);
+		}
+		return this.ps.getResultSetConcurrency();
+	}
+
+	@Override
+	public int getResultSetHoldability() throws SQLException {
+		if (this.ps == null) {
+			throw new SQLException(ExceptionMessage);
+		}
+		return this.ps.getResultSetHoldability();
+	}
+
+	@Override
+	public int getResultSetType() throws SQLException {
+		if (this.ps == null) {
+			throw new SQLException(ExceptionMessage);
+		}
+		return this.ps.getResultSetType();
+	}
+
+	@Override
+	public int getUpdateCount() throws SQLException {
+		if (this.ps == null) {
+			throw new SQLException(ExceptionMessage);
+		}
+		return this.ps.getUpdateCount();
+	}
+
+	@Override
+	public SQLWarning getWarnings() throws SQLException {
+		if (this.ps == null) {
+			throw new SQLException(ExceptionMessage);
+		}
+		return this.ps.getWarnings();
+	}
+
+	@Override
+	public boolean isClosed() throws SQLException {
+		if (this.ps == null) {
+			throw new SQLException(ExceptionMessage);
+		}
+		return this.ps.isClosed();
+	}
+
+	@Override
+	public boolean isPoolable() throws SQLException {
+		if (this.ps == null) {
+			throw new SQLException(ExceptionMessage);
+		}
+		return this.ps.isPoolable();
+	}
+
+	@Override
+	public void setCursorName(String name) throws SQLException {
+		if (this.ps == null) {
+			throw new SQLException(ExceptionMessage);
+		}
+		this.ps.setCursorName(name);
+	}
+
+	@Override
+	public void setEscapeProcessing(boolean enable) throws SQLException {
+		if (this.ps == null) {
+			throw new SQLException(ExceptionMessage);
+		}
+		this.ps.setEscapeProcessing(enable);
+	}
+
+	@Override
+	public void setFetchDirection(int direction) throws SQLException {
+		if (this.ps == null) {
+			throw new SQLException(ExceptionMessage);
+		}
+		this.ps.setFetchDirection(direction);
+	}
+
+	@Override
+	public void setFetchSize(int rows) throws SQLException {
+		if (this.ps == null) {
+			throw new SQLException(ExceptionMessage);
+		}
+		this.ps.setFetchSize(rows);
+	}
+
+	@Override
+	public void setMaxFieldSize(int max) throws SQLException {
+		if (this.ps == null) {
+			throw new SQLException(ExceptionMessage);
+		}
+		this.ps.setMaxFieldSize(max);
+	}
+
+	@Override
+	public void setMaxRows(int max) throws SQLException {
+		if (this.ps == null) {
+			throw new SQLException(ExceptionMessage);
+		}
+		this.ps.setMaxRows(max);
+	}
+
+	@Override
+	public void setPoolable(boolean poolable) throws SQLException {
+		if (this.ps == null) {
+			throw new SQLException(ExceptionMessage);
+		}
+		this.ps.setPoolable(poolable);
+	}
+
+	@Override
+	public void setQueryTimeout(int seconds) throws SQLException {
+		if (this.ps == null) {
+			throw new SQLException(ExceptionMessage);
+		}
+		this.ps.setQueryTimeout(seconds);
+	}
+
+	@Override
+	public boolean isWrapperFor(Class<?> iface) throws SQLException {
+		if (this.ps == null) {
+			throw new SQLException(ExceptionMessage);
+		}
+		return this.ps.isWrapperFor(iface);
+	}
+
+	@Override
+	public <T> T unwrap(Class<T> iface) throws SQLException {
+		if (this.ps == null) {
+			throw new SQLException(ExceptionMessage);
+		}
+		return this.ps.unwrap(iface);
+	}
+
+}
diff --git a/src/engine/objects/ProducedItem.java b/src/engine/objects/ProducedItem.java
new file mode 100644
index 00000000..1284384d
--- /dev/null
+++ b/src/engine/objects/ProducedItem.java
@@ -0,0 +1,236 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.objects;
+
+import engine.Enum.DispatchChannel;
+import engine.gameManager.PowersManager;
+import engine.net.DispatchMessage;
+import engine.net.client.msg.ItemProductionMsg;
+import engine.powers.EffectsBase;
+import org.joda.time.DateTime;
+import org.pmw.tinylog.Logger;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.Date;
+
+public class ProducedItem  {
+
+	private int ID;
+	private int npcUID;
+	private int itemBaseID;
+	private DateTime dateToUpgrade;
+	private boolean isRandom;
+
+	private String prefix;
+	private String suffix;
+	private String name;
+	private int amount;
+	private int producedItemID = 0;
+	private boolean inForge;
+	private int value;
+	
+	private int playerID;
+
+
+
+	/**
+	 * ResultSet Constructor
+	 */
+
+	public ProducedItem(ResultSet rs) throws SQLException {
+		this.ID = rs.getInt("ID");
+		this.npcUID = rs.getInt("npcUID");
+        this.itemBaseID = rs.getInt("itemBaseID");
+
+        Date sqlDateTime = rs.getTimestamp("dateToUpgrade");
+
+		if (sqlDateTime != null)
+			this.dateToUpgrade = new DateTime(sqlDateTime);
+		else
+			dateToUpgrade = null;
+        this.isRandom = rs.getBoolean("isRandom");
+        this.prefix = rs.getString("prefix");
+        this.suffix = rs.getString("suffix");
+        this.name = rs.getString("name");
+		this.inForge = rs.getBoolean("inForge");
+		this.value = rs.getInt("value");
+		this.playerID = rs.getInt("playerID");
+	}
+
+	public ProducedItem(int ID,int npcUID, int itemBaseID, DateTime dateToUpgrade, boolean isRandom, String prefix, String suffix, String name, int playerID) {
+		super();
+		this.ID = ID;
+		this.npcUID = npcUID;
+        this.itemBaseID = itemBaseID;
+        this.dateToUpgrade = dateToUpgrade;
+		this.isRandom = isRandom;
+		this.prefix = prefix;
+		this.suffix = suffix;
+		this.name = name;
+		this.value = 0;
+		this.playerID = playerID;
+
+
+
+	}
+
+	public int getNpcUID() {
+		return npcUID;
+	}
+	public DateTime getDateToUpgrade() {
+		return dateToUpgrade;
+	}
+
+	public int getItemBaseID() {
+		return itemBaseID;
+	}
+
+	public void setItemBaseID(int itemBaseID) {
+		this.itemBaseID = itemBaseID;
+	}
+
+	public boolean isRandom() {
+		return isRandom;
+	}
+
+	public void setRandom(boolean isRandom) {
+		this.isRandom = isRandom;
+	}
+
+	public String getPrefix() {
+		return prefix;
+	}
+
+	public void setPrefix(String prefix) {
+		this.prefix = prefix;
+	}
+
+	public String getSuffix() {
+		return suffix;
+	}
+
+	public void setSuffix(String suffix) {
+		this.suffix = suffix;
+	}
+
+	public String getName() {
+		return name;
+	}
+
+	public void setName(String name) {
+		this.name = name;
+	}
+
+	public int getID() {
+		return ID;
+	}
+
+	public void setID(int iD) {
+		ID = iD;
+	}
+
+	public int getAmount() {
+		return amount;
+	}
+
+	public void setAmount(int amount) {
+		this.amount = amount;
+	}
+
+
+	public int getProducedItemID() {
+		return producedItemID;
+	}
+
+	public void setProducedItemID(int producedItemID) {
+		this.producedItemID = producedItemID;
+	}
+
+	public boolean isInForge() {
+		return inForge;
+	}
+
+	public void setInForge(boolean inForge) {
+		this.inForge = inForge;
+	}
+
+	public int getValue() {
+		return value;
+	}
+
+	public void setValue(int value) {
+		this.value = value;
+	}
+	
+	public boolean finishProduction(){
+		NPC npc = NPC.getFromCache(this.getNpcUID());
+
+		if (npc == null)
+			return false;
+
+
+		//update the client to ID the item in the window when item finishes rolling.
+		//If there is more than 1 item left to roll, complete the item and throw it in inventory
+		//and reproduce next item.
+	
+			try{
+
+				if(this.getAmount() > 1){
+					this.setAmount(this.getAmount() - 1);
+					npc.completeItem(this.getProducedItemID());
+
+					int pToken = 0;
+					int sToken = 0;
+
+					if (!this.isRandom()){
+						EffectsBase eb = PowersManager.getEffectByIDString(this.getPrefix());
+						if (eb != null)
+							pToken = eb.getToken();
+						eb = PowersManager.getEffectByIDString(this.getSuffix());
+						if (eb != null)
+							sToken = eb.getToken();
+
+					}
+
+					Item item = npc.produceItem(0,this.getAmount(),this.isRandom(),pToken,sToken,this.getName(),this.getItemBaseID());
+					
+					if (item == null)
+						return false;
+
+				}else{
+					
+					//update item to complete
+					MobLoot targetItem = MobLoot.getFromCache(this.getProducedItemID());
+					
+					if (targetItem == null)
+						return false;
+					
+					ItemProductionMsg outMsg = new ItemProductionMsg(npc.getBuilding(), npc, targetItem, 8, true);
+					
+	
+		
+					DispatchMessage.dispatchMsgToInterestArea(npc, outMsg, DispatchChannel.SECONDARY, 700, false, false);
+				}
+				
+			}catch(Exception e){
+				Logger.error(e);
+				return false;
+			}
+			return true;
+	}
+
+	public int getPlayerID() {
+		return playerID;
+	}
+	
+
+
+}
diff --git a/src/engine/objects/PromotionClass.java b/src/engine/objects/PromotionClass.java
new file mode 100644
index 00000000..dcccda72
--- /dev/null
+++ b/src/engine/objects/PromotionClass.java
@@ -0,0 +1,203 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.objects;
+
+import engine.Enum.GameObjectType;
+import engine.gameManager.DbManager;
+import engine.net.ByteBufferWriter;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+
+
+public class PromotionClass extends AbstractGameObject {
+
+	private final String name;
+	private final String description;
+	private int token = 0;
+
+	private final float healthMod;
+	private final float manaMod;
+	private final float staminaMod;
+
+	private final ArrayList<Integer> allowedRunes;
+	private final ArrayList<SkillReq> skillsGranted;
+	private final ArrayList<PowerReq> powersGranted;
+	private final ArrayList<RuneBaseEffect> effectsGranted;
+	private final ArrayList<RuneBaseEffect> effectsGrantedFighter;
+	private final ArrayList<RuneBaseEffect> effectsGrantedHealer;
+	private final ArrayList<RuneBaseEffect> effectsGrantedRogue;
+	private final ArrayList<RuneBaseEffect> effectsGrantedMage;
+	private ArrayList<MobBaseEffects> effectsList = new ArrayList<>();
+
+	/**
+	 * No Table ID Constructor
+	 */
+	public PromotionClass(String name, String description,
+			ArrayList<Integer> allowedRunes, ArrayList<SkillReq> skillsGranted, ArrayList<PowerReq> powersGranted) {
+		super();
+		this.name = name;
+		this.description = description;
+		this.allowedRunes = allowedRunes;
+		this.skillsGranted = skillsGranted;
+		this.powersGranted = powersGranted;
+		this.healthMod = 0f;
+		this.manaMod = 0f;
+		this.staminaMod = 0f;
+		this.effectsGranted = new ArrayList<>();
+		this.effectsGrantedFighter = new ArrayList<>();
+		this.effectsGrantedHealer = new ArrayList<>();
+		this.effectsGrantedRogue = new ArrayList<>();
+		this.effectsGrantedMage = new ArrayList<>();
+	}
+
+	/**
+	 * Normal Constructor
+	 */
+	public PromotionClass(String name, String description,
+			ArrayList<Integer> allowedRunes, ArrayList<SkillReq> skillsGranted, ArrayList<PowerReq> powersGranted, int newUUID) {
+		super(newUUID);
+		this.name = name;
+		this.description = description;
+		this.allowedRunes = allowedRunes;
+		this.skillsGranted = skillsGranted;
+		this.powersGranted = powersGranted;
+		this.healthMod = 0f;
+		this.manaMod = 0f;
+		this.staminaMod = 0f;
+		this.effectsGranted = new ArrayList<>();
+		this.effectsGrantedFighter = new ArrayList<>();
+		this.effectsGrantedHealer = new ArrayList<>();
+		this.effectsGrantedRogue = new ArrayList<>();
+		this.effectsGrantedMage = new ArrayList<>();
+	}
+
+	/**
+	 * ResultSet Constructor
+	 */
+	public PromotionClass(ResultSet rs) throws SQLException {
+		super(rs);
+
+		this.name = rs.getString("name");
+		this.description = rs.getString("description");
+		this.token = rs.getInt("token");
+		this.healthMod = rs.getFloat("healthMod");
+		this.manaMod = rs.getFloat("manaMod");
+		this.staminaMod = rs.getFloat("staminaMod");
+		this.allowedRunes = DbManager.PromotionQueries.GET_ALLOWED_RUNES(this);
+		this.skillsGranted = DbManager.SkillReqQueries.GET_REQS_FOR_RUNE(this.getObjectUUID());
+		this.powersGranted = PowerReq.getPowerReqsForRune(this.getObjectUUID());
+		this.effectsGranted = DbManager.RuneBaseEffectQueries.GET_EFFECTS_FOR_RUNEBASE(this.getObjectUUID());
+		this.effectsGrantedFighter = DbManager.RuneBaseEffectQueries.GET_EFFECTS_FOR_RUNEBASE((this.getObjectUUID() * 10) + 2500);
+		this.effectsGrantedHealer = DbManager.RuneBaseEffectQueries.GET_EFFECTS_FOR_RUNEBASE((this.getObjectUUID() * 10) + 2501);
+		this.effectsGrantedRogue = DbManager.RuneBaseEffectQueries.GET_EFFECTS_FOR_RUNEBASE((this.getObjectUUID() * 10) + 2502);
+		this.effectsGrantedMage = DbManager.RuneBaseEffectQueries.GET_EFFECTS_FOR_RUNEBASE((this.getObjectUUID() * 10) + 2503);
+		this.effectsList = DbManager.MobBaseQueries.GET_RUNEBASE_EFFECTS(this.getObjectUUID());
+
+		
+	}
+
+	/*
+	 * Getters
+	 */
+	public String getName() {
+		return name;
+	}
+
+	public String getDescription() {
+		return description;
+	}
+
+	public int getToken() {
+		return this.token;
+	}
+
+	public float getHealthMod() {
+		return this.healthMod;
+	}
+
+	public float getManaMod() {
+		return this.manaMod;
+	}
+
+	public float getStaminaMod() {
+		return this.staminaMod;
+	}
+
+	public boolean isAllowedRune(int token) {
+		for (int b : this.allowedRunes) {
+			if (token == b) {
+				return true;
+			}
+		}
+		return false;
+	}
+
+	public ArrayList<Integer> getRuneList() {
+		return this.allowedRunes;
+	}
+
+	public ArrayList<SkillReq> getSkillsGranted() {
+		return this.skillsGranted;
+	}
+
+	public ArrayList<PowerReq> getPowersGranted() {
+		return this.powersGranted;
+	}
+
+	public ArrayList<RuneBaseEffect> getEffectsGranted() {
+		return this.effectsGranted;
+	}
+
+	public ArrayList<RuneBaseEffect> getEffectsGranted(int baseClassID) {
+		if (baseClassID == 2500)
+			return this.effectsGrantedFighter;
+		else if (baseClassID == 2501)
+			return this.effectsGrantedHealer;
+		else if (baseClassID == 2502)
+			return this.effectsGrantedRogue;
+		else if (baseClassID == 2503)
+			return this.effectsGrantedMage;
+		else
+			return new ArrayList<>();
+	}
+
+	/*
+	 * Serializing
+	 */
+	
+	public static void serializeForClientMsg(PromotionClass promotionClass, ByteBufferWriter writer) {
+		writer.putInt(3); // For BaseClass
+		writer.putInt(0); // Pad
+		writer.putInt(promotionClass.getObjectUUID());
+                writer.putInt(promotionClass.getObjectType().ordinal());
+                writer.putInt(promotionClass.getObjectUUID());
+	}
+
+	@Override
+	public void updateDatabase() {
+		// TODO Create update logic.
+	}
+	
+	public static PromotionClass GetPromtionClassFromCache(int runeID){
+		if (runeID == 0)
+			return null;
+		return (PromotionClass) DbManager.getFromCache(GameObjectType.PromotionClass, runeID);
+	}
+
+	public ArrayList<MobBaseEffects> getEffectsList() {
+		return effectsList;
+	}
+
+	public void setEffectsList(ArrayList<MobBaseEffects> effectsList) {
+		this.effectsList = effectsList;
+	}
+}
diff --git a/src/engine/objects/Race.java b/src/engine/objects/Race.java
new file mode 100644
index 00000000..28a3467f
--- /dev/null
+++ b/src/engine/objects/Race.java
@@ -0,0 +1,368 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.objects;
+
+import engine.Enum;
+import engine.Enum.RaceType;
+import engine.gameManager.DbManager;
+import engine.net.ByteBufferWriter;
+import engine.server.MBServerStatics;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class Race {
+
+	// Local class cache
+
+	private static ConcurrentHashMap<Integer, Race> _raceByID = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW);
+
+	private final String name;
+	private final String description;
+	private final int raceRuneID;
+	private Enum.RaceType raceType;
+	private final short strStart;
+	private final short strMin;
+	private final short strMax;
+
+	private final short dexStart;
+	private final short dexMin;
+	private final short dexMax;
+
+	private final short conStart;
+	private final short conMin;
+	private final short conMax;
+
+	private final short intStart;
+	private final short intMin;
+	private final short intMax;
+
+	private final short spiStart;
+	private final short spiMin;
+	private final short spiMax;
+
+	private final short healthBonus;
+	private final short manaBonus;
+	private final short staminaBonus;
+
+	private final byte startingPoints;
+
+	private final float minHeight;
+	private final float strHeightMod;
+
+	private int token = 0;
+
+	private HashSet<Integer> hairStyles;
+	private HashSet<Integer> beardStyles;
+
+	private final HashSet<Integer> skinColors;
+	private final HashSet<Integer> beardColors;
+	private final HashSet<Integer> hairColors;
+
+	private final ArrayList<BaseClass> baseClasses;
+	private ArrayList<MobBaseEffects> effectsList = new ArrayList<>();
+
+
+	private final ArrayList<SkillReq> skillsGranted;
+	private final ArrayList<PowerReq> powersGranted;
+
+	public static void loadAllRaces() {
+		Race._raceByID = DbManager.RaceQueries.LOAD_ALL_RACES();
+	}
+
+
+	@Override
+	public boolean equals(Object object) {
+
+		if ((object instanceof Race) == false)
+			return false;
+
+		Race race = (Race) object;
+
+		return this.raceRuneID == race.raceRuneID;
+	}
+
+	@Override
+	public int hashCode() {
+		return this.raceRuneID;
+	}
+
+	/**
+	 * ResultSet Constructor
+	 */
+	public Race(ResultSet rs) throws SQLException {
+
+		this.raceRuneID = rs.getInt("ID");
+		this.raceType = Enum.RaceType.getRaceTypebyRuneID(raceRuneID);
+		this.name = rs.getString("name");
+		this.description = rs.getString("description");
+		this.strStart = rs.getShort("strStart");
+		this.strMin = rs.getShort("strMin");
+		this.strMax = rs.getShort("strMax");
+		this.dexStart = rs.getShort("dexStart");
+		this.dexMin = rs.getShort("dexMin");
+		this.dexMax = rs.getShort("dexMax");
+		this.conStart = rs.getShort("conStart");
+		this.conMin = rs.getShort("conMin");
+		this.conMax = rs.getShort("conMax");
+		this.intStart = rs.getShort("intStart");
+		this.intMin = rs.getShort("intMin");
+		this.intMax = rs.getShort("intMax");
+		this.spiStart = rs.getShort("spiStart");
+		this.spiMin = rs.getShort("spiMin");
+		this.spiMax = rs.getShort("spiMax");
+		this.token = rs.getInt("token");
+		this.healthBonus = rs.getShort("healthBonus");
+		this.manaBonus = rs.getShort("manaBonus");
+		this.staminaBonus = rs.getShort("staminaBonus");
+		this.startingPoints = rs.getByte("startingPoints");
+		this.raceType = RaceType.getRaceTypebyRuneID(this.raceRuneID);
+		this.minHeight = rs.getFloat("minHeight");
+		this.strHeightMod = rs.getFloat("strHeightMod");
+		this.hairStyles = DbManager.RaceQueries.HAIR_STYLES_FOR_RACE(raceRuneID);
+		this.beardStyles = DbManager.RaceQueries.BEARD_STYLES_FOR_RACE(raceRuneID);
+		this.skinColors = DbManager.RaceQueries.SKIN_COLOR_FOR_RACE(raceRuneID);
+		this.beardColors = DbManager.RaceQueries.BEARD_COLORS_FOR_RACE(raceRuneID);
+		this.hairColors = DbManager.RaceQueries.HAIR_COLORS_FOR_RACE(raceRuneID);
+		this.baseClasses = DbManager.BaseClassQueries.GET_BASECLASS_FOR_RACE(raceRuneID);
+		this.skillsGranted = DbManager.SkillReqQueries.GET_REQS_FOR_RUNE(raceRuneID);
+		this.powersGranted = PowerReq.getPowerReqsForRune(raceRuneID);
+		this.effectsList = DbManager.MobBaseQueries.GET_RUNEBASE_EFFECTS(this.raceRuneID);
+
+	}
+
+	/*
+	 * Getters
+	 */
+	public String getName() {
+		return name;
+	}
+
+	public String getDescription() {
+		return description;
+	}
+
+	public short getStrStart() {
+		return strStart;
+	}
+
+	public short getStrMin() {
+		return strMin;
+	}
+
+	public short getStrMax() {
+		return strMax;
+	}
+
+	public short getDexStart() {
+		return dexStart;
+	}
+
+	public short getDexMin() {
+		return dexMin;
+	}
+
+	public short getDexMax() {
+		return dexMax;
+	}
+
+	public short getConStart() {
+		return conStart;
+	}
+
+	public short getConMin() {
+		return conMin;
+	}
+
+	public short getConMax() {
+		return conMax;
+	}
+
+	public short getIntStart() {
+		return intStart;
+	}
+
+	public short getIntMin() {
+		return intMin;
+	}
+
+	public short getIntMax() {
+		return intMax;
+	}
+
+	public short getSpiStart() {
+		return spiStart;
+	}
+
+	public short getSpiMin() {
+		return spiMin;
+	}
+
+	public short getSpiMax() {
+		return spiMax;
+	}
+
+	public byte getStartingPoints() {
+		return startingPoints;
+	}
+
+	public int getToken() {
+		return token;
+	}
+
+	public final HashSet<Integer> getHairStyles() {
+		return hairStyles;
+	}
+
+	public final HashSet<Integer> getBeardStyles() {
+		return beardStyles;
+	}
+
+	public final HashSet<Integer> getBeardColors() {
+		return beardColors;
+	}
+
+	public HashSet<Integer> getHairColors() {
+		return hairColors;
+	}
+
+	public HashSet<Integer> getSkinColors() {
+		return skinColors;
+	}
+
+	public int getNumSkinColors() {
+		return this.skinColors.size();
+	}
+
+	public int getNumHairColors() {
+		return this.hairColors.size();
+	}
+
+	public int getNumBeardColors() {
+		return this.beardColors.size();
+	}
+
+	public final ArrayList<BaseClass> getValidBaseClasses() {
+		return baseClasses;
+	}
+
+	public ArrayList<Integer> getAllowedRunes() {
+        return RuneBase.AllowedRaceRunesMap.get(raceRuneID);
+	}
+
+	public ArrayList<SkillReq> getSkillsGranted() {
+		return this.skillsGranted;
+	}
+
+	public ArrayList<PowerReq> getPowersGranted() {
+		return this.powersGranted;
+	}
+
+	public ArrayList<RuneBaseEffect> getEffectsGranted() {
+		return RuneBaseEffect.RuneIDBaseEffectMap.get(this.raceRuneID);
+	}
+
+	/*
+	 * Validators
+	 */
+	public boolean isValidBeardStyle(int id) {
+		return this.beardStyles.contains(id);
+	}
+
+	public boolean isValidBeardColor(int id) {
+		return this.beardColors.contains(id);
+	}
+
+	public boolean isValidHairStyle(int id) {
+		return this.hairStyles.contains(id);
+	}
+
+	public boolean isValidHairColor(int id) {
+		return this.hairColors.contains(id);
+	}
+
+	public boolean isValidSkinColor(int id) {
+		return this.skinColors.contains(id);
+	}
+
+	public boolean isAllowedRune(RuneBase rb) {
+
+		if (this.getAllowedRunes() != null)
+			if (this.getAllowedRunes().contains(rb.getObjectUUID()))
+				return true;
+		if (RuneBase.AllowedBaseClassRunesMap.containsKey(111111)){
+			if (RuneBase.AllowedRaceRunesMap.get(111111).contains(rb.getObjectUUID()))
+				return true;
+		}
+		return false;
+	}
+
+	public float getHealthBonus() {
+		return this.healthBonus;
+	}
+
+	public float getManaBonus() {
+		return this.manaBonus;
+	}
+
+	public float getStaminaBonus() {
+		return this.staminaBonus;
+	}
+
+	public float getMinHeight() {
+		return this.minHeight;
+	}
+
+	public float getStrHeightMod() {
+		return this.strHeightMod;
+	}
+
+	public float getHeight(short str) {
+		return this.minHeight + (this.strHeightMod * str);
+	}
+
+	public float getCenterHeight(short str) {
+		return getHeight(str) / 2f;
+	}
+
+
+	/*
+	 * Serializing
+	 */
+
+	public void serializeForClientMsg(ByteBufferWriter writer) {
+		writer.putInt(1); // For Race
+		writer.putInt(0); // Pad
+		writer.putInt(this.raceRuneID);
+
+		writer.putInt(Enum.GameObjectType.Race.ordinal());
+		writer.putInt(raceRuneID);
+	}
+
+	public static Race getRace(int id) {
+		return _raceByID.get(id);
+	}
+
+	public int getRaceRuneID() {
+		return raceRuneID;
+	}
+
+	public Enum.RaceType getRaceType() {
+		return raceType;
+	}
+
+
+	public ArrayList<MobBaseEffects> getEffectsList() {
+		return effectsList;
+	}
+}
diff --git a/src/engine/objects/Realm.java b/src/engine/objects/Realm.java
new file mode 100644
index 00000000..3936d85a
--- /dev/null
+++ b/src/engine/objects/Realm.java
@@ -0,0 +1,460 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+
+package engine.objects;
+
+import engine.Enum;
+import engine.InterestManagement.RealmMap;
+import engine.db.archive.DataWarehouse;
+import engine.db.archive.RealmRecord;
+import engine.gameManager.DbManager;
+import engine.gameManager.PowersManager;
+import engine.net.ByteBufferWriter;
+import engine.powers.PowersBase;
+import engine.server.MBServerStatics;
+import org.pmw.tinylog.Logger;
+
+import java.net.UnknownHostException;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.util.HashSet;
+import java.util.concurrent.ConcurrentHashMap;
+
+import static engine.Enum.CharterType;
+
+
+public class Realm {
+
+	// Internal class cache
+
+	private static ConcurrentHashMap<Integer, Realm> _realms = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW);
+
+	private final float mapR; //Red color
+	private final float mapG; //Green color
+	private final float mapB; //Blue color
+	private final float mapA; //Alpha color
+	private final boolean canBeClaimed;
+	private final boolean canPlaceCities;
+	private final int numCities;
+	private final String realmName;
+	private int rulingCityUUID;
+	private int rulingCharacterUUID;
+	private int rulingCharacterOrdinal;
+	private String rulingCharacterName;
+	private int rulingNationUUID;
+	private GuildTag rulingNationTags;
+	private String rulingNationName;
+	private int charterType;
+	public LocalDateTime ruledSince;
+	private final float mapY1;
+	private final float mapX1;
+	private final float mapY2;
+	private final float mapX2;
+	private final int stretchX;
+	private final int stretchY;
+	private final int locX;
+	private final int locY;
+	private final int realmID;
+	private final HashSet<Integer> cities = new HashSet<>();
+	private String hash;
+
+	/**
+	 * ResultSet Constructor
+	 */
+	public Realm(ResultSet rs) throws SQLException, UnknownHostException {
+
+		this.mapR = rs.getFloat("mapR");
+		this.mapG = rs.getFloat("mapG");
+		this.mapB = rs.getFloat("mapB");
+		this.mapA = rs.getFloat("mapA");
+		this.canBeClaimed = rs.getBoolean("canBeClaimed");
+		this.canPlaceCities = rs.getBoolean("canPlaceCities");
+		this.numCities = rs.getInt("numCities");
+		this.realmName = rs.getString("realmName");
+		this.rulingCityUUID = rs.getInt("rulingCityUID");
+		this.charterType = rs.getInt("charterType");
+
+		java.sql.Timestamp ruledTimeStamp = rs.getTimestamp("ruledSince");
+
+		if (ruledTimeStamp != null)
+			this.ruledSince = LocalDateTime.ofInstant(ruledTimeStamp.toInstant(), ZoneId.systemDefault());
+
+		this.mapY1 = rs.getFloat("mapY1");
+		this.mapX1 = rs.getFloat("mapX1");
+		this.mapY2 = rs.getFloat("mapY2");
+		this.mapX2 = rs.getFloat("mapX2");
+		this.stretchX = rs.getInt("stretchX");
+		this.stretchY = rs.getInt("stretchY");
+		this.locX = rs.getInt("locX");
+		this.locY = rs.getInt("locY");
+		this.realmID = rs.getInt("realmID");
+		this.hash = rs.getString("hash");
+	}
+
+	/*
+	 * Getters
+	 */
+	public boolean isRuled() {
+		return (this.rulingCityUUID != 0);
+	}
+
+	public float getMapR() {
+		return this.mapR;
+	}
+
+	public float getMapG() {
+		return this.mapG;
+	}
+
+	public float getMapB() {
+		return this.mapB;
+	}
+
+	public float getMapA() {
+		return this.mapA;
+	}
+
+	public boolean getCanBeClaimed() {
+		return this.canBeClaimed;
+	}
+
+	public boolean getCanPlaceCities() {
+		return this.canPlaceCities;
+	}
+
+	public int getNumCities() {
+		return this.numCities;
+	}
+
+	public String getRealmName() {
+		return this.realmName;
+	}
+
+	public City getRulingCity() {
+		return City.getCity(this.rulingCityUUID);
+	}
+
+	public float getMapY1() {
+		return this.mapY1;
+	}
+
+	public float getMapX1() {
+		return this.mapX1;
+	}
+
+	public float getMapY2() {
+		return this.mapY2;
+	}
+
+	public float getMapX2() {
+		return this.mapX2;
+	}
+
+	public int getStretchX() {
+		return this.stretchX;
+	}
+
+	public int getStretchY() {
+		return this.stretchY;
+	}
+
+	public int getlocX() {
+		return this.locX;
+	}
+
+	public int getlocY() {
+		return this.locY;
+	}
+
+	public int getRealmID() {
+		return this.realmID;
+	}
+
+	public void addCity(int cityUUID) {
+		if (!this.cities.add(cityUUID))
+			this.cities.add(cityUUID);
+	}
+
+	public void removeCity(int cityUUID) {
+		this.cities.remove(cityUUID);
+	}
+
+	public boolean isRealmFull() {
+
+		return this.cities.size() >= this.numCities;
+	}
+
+	public boolean isRealmFullAfterBane() {
+
+		return this.cities.size() > this.numCities;
+	}
+
+	public static void configureAllRealms() {
+
+		Realm serverRealm;
+		int realmID;
+
+		for (Enum.RealmType realmType : Enum.RealmType.values()) {
+
+			realmID = realmType.getRealmID();
+			// Don't serialize seafloor
+
+			if (realmID == 0)
+				continue;
+
+			serverRealm = Realm.getRealm(realmID);
+			serverRealm.configure();
+
+		}
+	}
+
+	// Call this after changing ownership before you serialize a realm
+
+	public void configure() {
+
+		PlayerCharacter rulingCharacter;
+
+		// Configure what exactly?  We won't send any of it.
+
+		if (this.rulingCityUUID == 0)
+			return;
+		if (this.getRulingCity() == null)
+			return;
+		if (this.getRulingCity().getTOL() == null)
+			return;
+
+		rulingCharacter = PlayerCharacter.getPlayerCharacter(this.getRulingCity().getTOL().getOwnerUUID());
+		if (rulingCharacter == null){
+			Logger.info( this.realmName + " failed to load " + this.getRulingCity().getCityName() + " ID : " + this.rulingCityUUID);
+			return;
+		}
+
+		this.rulingCharacterUUID = rulingCharacter.getObjectUUID();
+		this.rulingCharacterOrdinal = rulingCharacter.getObjectType().ordinal();
+		this.rulingCharacterName = rulingCharacter.getFirstName() + ' ' + rulingCharacter.getLastName();
+		this.rulingNationUUID = rulingCharacter.getGuild().getNation().getObjectUUID();
+		this.rulingNationName = rulingCharacter.getGuild().getNation().getName();
+		this.rulingNationTags = rulingCharacter.getGuild().getNation().getGuildTag();
+	}
+
+	public void serializeForClientMsg(ByteBufferWriter writer) {
+
+		writer.putFloat(this.mapR);
+		writer.putFloat(this.mapG);
+		writer.putFloat(this.mapB);
+		writer.putFloat(this.mapA);
+		writer.put((byte) (this.canBeClaimed ? 0x1 : 0x0));
+		writer.put((byte) (this.canPlaceCities ? 0x1 : 0x0));
+		writer.putInt(this.numCities);
+		writer.putFloat(this.mapR);
+		writer.putFloat(this.mapG);
+		writer.putFloat(this.mapB);
+		writer.putFloat(this.mapA);
+		writer.putString(this.realmName);
+
+		if (isRuled() == true) {
+			writer.putInt(Enum.GameObjectType.Guild.ordinal());
+			writer.putInt(rulingNationUUID);
+
+			writer.putInt(rulingCharacterOrdinal);
+			writer.putInt(rulingCharacterUUID);
+
+			writer.putInt(Enum.GameObjectType.City.ordinal());
+			writer.putInt(rulingCityUUID);
+
+			writer.putLocalDateTime(this.ruledSince);
+
+			writer.putString(rulingNationName);
+			GuildTag._serializeForDisplay(rulingNationTags,writer);
+			writer.putString(rulingCharacterName);
+			writer.putInt(0xB); // Display Title: enum index starts at 10.
+		} else {
+			if (this.rulingCityUUID != 0)
+				Logger.error( "Failed to Load realm info for city" + this.rulingCityUUID);
+			writer.putLong(0);
+			writer.putLong(0);
+			writer.putLong(0);
+			writer.put((byte) 1);
+			writer.put((byte) 0);
+			writer.putInt(0x64);
+			writer.put((byte) 0);
+			writer.put((byte) 0);
+			writer.put((byte) 0);
+			writer.putInt(0);
+			writer.putInt(0x10);
+			writer.putInt(0x10);
+			writer.putInt(0x10);
+			writer.putInt(0x0);
+			writer.putInt(0x0);
+			writer.putInt(0);
+			writer.putInt(0);
+		}
+		writer.putInt(0); // Male/Female
+		writer.putInt(this.charterType); // Charter Type
+		writer.putFloat(this.mapY1);
+		writer.putFloat(this.mapX1);
+		writer.putFloat(this.mapY2);
+		writer.putFloat(this.mapX2);
+		writer.putInt(this.stretchX);
+		writer.putInt(this.stretchY);
+		writer.putInt(this.locX);
+		writer.putInt(this.locY);
+	}
+
+	public void updateDatabase() {
+		DbManager.RealmQueries.REALM_UPDATE(this);
+	}
+
+	public static Realm getRealm(int realmID) {
+		return _realms.get(realmID);
+	}
+
+	/**
+	 * @return the charterType
+	 */
+	public int getCharterType() {
+		return charterType;
+	}
+
+	/**
+	 * @param charterType the charterType to set
+	 */
+	public void setCharterType(int charterType) {
+		this.charterType = charterType;
+	}
+
+	public void abandonRealm() {
+
+		// Push event to warehouse
+
+		RealmRecord realmRecord = RealmRecord.borrow(this, Enum.RecordEventType.LOST);
+		DataWarehouse.pushToWarehouse(realmRecord);
+
+		// No longer own a realm
+		this.getRulingCity().getGuild().setRealmsOwned(this.getRulingCity().getGuild().getRealmsOwned() - 1);
+		if (!this.getRulingCity().getGuild().getNation().equals(this.getRulingCity().getGuild()))
+			this.getRulingCity().getGuild().getNation().setRealmsOwned(this.getRulingCity().getGuild().getNation().getRealmsOwned() - 1);
+
+		// Configure realm
+		this.charterType = 0;
+		this.rulingCityUUID = 0;
+		this.ruledSince = null;
+
+		this.updateDatabase();
+	}
+
+	public void claimRealmForCity(City city, int charterType) {
+
+		// Configure realm
+		this.charterType = charterType;
+		this.rulingCityUUID = city.getObjectUUID();
+		this.ruledSince = LocalDateTime.now();
+		this.configure();
+		this.updateDatabase();
+
+		// Push event to warehouse
+
+		RealmRecord realmRecord = RealmRecord.borrow(this, Enum.RecordEventType.CAPTURE);
+		DataWarehouse.pushToWarehouse(realmRecord);
+
+	}
+
+	public static boolean HasAllBlessings(PlayerCharacter claimer) {
+
+		if (claimer == null)
+			return false;
+
+		PowersBase powerBlessing = PowersManager.getPowerByIDString("BLS-POWER");
+		if (!claimer.effects.containsKey(Integer.toString(powerBlessing.getActions().get(0).getUUID())))
+			return false;
+		PowersBase wisdomBlessing = PowersManager.getPowerByIDString("BLS-POWER");
+		if (!claimer.effects.containsKey(Integer.toString(wisdomBlessing.getActions().get(0).getUUID())))
+			return false;
+		PowersBase fortuneBlessing = PowersManager.getPowerByIDString("BLS-FORTUNE");
+		return claimer.effects.containsKey(Integer.toString(fortuneBlessing.getActions().get(0).getUUID()));
+	}
+
+	public static float getRealmHealthMod(City city) {
+		Realm serverRealm;
+		int charterType;
+		float returnBonus = 0.0f;
+
+		serverRealm = RealmMap.getRealmForCity(city);
+		charterType = serverRealm.charterType;
+
+		switch (charterType) {
+
+		case 762228431:
+			returnBonus = 0.0f;
+			break;
+		case -15978914:
+			returnBonus = -.15f;
+			break;
+		case -600065291:
+			returnBonus = .15f;
+			break;
+		default:
+			break;
+		}
+
+		return returnBonus;
+	}
+
+	public static int getRealmMesh(City city) {
+		Realm serverRealm;
+		CharterType charterType;
+
+		serverRealm = city.getRealm();
+		charterType = CharterType.getCharterTypeByID(serverRealm.charterType);
+
+		return charterType.getMeshID();
+	}
+
+	public static void loadAllRealms() {
+
+		_realms = DbManager.RealmQueries.LOAD_ALL_REALMS();
+
+	}
+
+	public String getHash() {
+		return hash;
+	}
+
+	public void setHash() {
+
+		this.hash = DataWarehouse.hasher.encrypt(this.realmID);
+
+		// Write hash to player character table
+
+		DataWarehouse.writeHash(Enum.DataRecordType.REALM, this.realmID);
+	}
+
+	/* *** Keeping around in case needed for server wipe or some other emergency
+
+    public static void backfillRealms() {
+
+        // Backfill realm records
+
+        for (Realm realm : _realms.values()) {
+
+            realm.setHash();
+
+            if ( (realm.isRuled() == true) &&
+                    (DataWarehouse.recordExists(Enum.DataRecordType.REALM, realm.getRealmID()) == false)) {
+                RealmRecord realmRecord = RealmRecord.borrow(realm, Enum.RecordEventType.CAPTURE);
+                DataWarehouse.pushToWarehouse(realmRecord);
+            }
+
+        }
+    }
+	 */
+}
diff --git a/src/engine/objects/Regions.java b/src/engine/objects/Regions.java
new file mode 100644
index 00000000..0a910d8d
--- /dev/null
+++ b/src/engine/objects/Regions.java
@@ -0,0 +1,384 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.objects;
+
+import engine.InterestManagement.WorldGrid;
+import engine.gameManager.BuildingManager;
+import engine.math.Bounds;
+import engine.math.FastMath;
+import engine.math.Vector3f;
+import engine.math.Vector3fImmutable;
+import engine.server.MBServerStatics;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+
+
+public class Regions  {
+
+	public int room;
+	public boolean outside;
+	public int level;
+	public final boolean stairs;
+	public final boolean exit;
+	public static HashMap<Integer,Regions> FurnitureRegionMap = new HashMap<>();
+	public  Vector3fImmutable lowLerp;
+	public  Vector3fImmutable highLerp;
+	public Vector3f center;
+	public float regionDistanceSquared;
+	
+	public ArrayList<Vector3f> regionPoints;
+	
+	public int parentBuildingID;
+
+
+	public Regions(ArrayList<Vector3f> regionPoints, int level, int room, boolean outside, boolean exit,boolean stairs, Vector3f center, int parentBuildingID) {
+		super();
+        
+        this.level = level;
+        this.room = room;
+		
+		this.outside = (outside);
+		this.regionPoints = regionPoints;
+		this.exit = exit;
+		this.stairs = stairs;
+		this.center = center;
+		this.parentBuildingID = parentBuildingID;
+		//order regionpoints clockwise starting from top left, and ending bottom left.
+		
+		ArrayList<Vector3f> top = new ArrayList<>();
+		ArrayList<Vector3f> bottom = new ArrayList<>();
+		
+		for (Vector3f point : this.regionPoints){
+			if (point.y > center.y)
+				top.add(point);
+			else if (point.y < center.y)
+				bottom.add(point);
+		}
+		
+		
+		if (top.size() == 2 && bottom.size() == 2){
+			Vector3f topLeft = Vector3f.min(top.get(0), top.get(1));
+			Vector3f topRight = Vector3f.max(top.get(0), top.get(1));
+			
+			Vector3f topCenter =  topLeft.lerp(topRight, .5f);
+			
+			Vector3f bottomLeft = Vector3f.min(bottom.get(0), bottom.get(1));
+			Vector3f bottomRight = Vector3f.max(bottom.get(0), bottom.get(1));
+			
+			Vector3f bottomCenter =  bottomLeft.lerp(bottomRight, .5f);
+			
+			this.lowLerp = new Vector3fImmutable(bottomCenter);
+			this.highLerp = new Vector3fImmutable(topCenter);
+			 
+		} else if (top.size() == 2 && bottom.size() == 1){
+			Vector3f topLeft = Vector3f.min(top.get(0), top.get(1));
+			Vector3f topRight = Vector3f.max(top.get(0), top.get(1));
+			
+			Vector3f topCenter =  topLeft.lerp(topRight, .5f);
+			
+			Vector3f topCopy = topRight.subtract2D(topLeft);
+			topCopy.normalize();
+			
+			float topMagnitude = topRight.subtract2D(topLeft).length();
+			
+			topCopy.multLocal(topMagnitude);
+			
+			Vector3f bottomLeft = null;
+			Vector3f bottomRight = null;
+			if (bottom.get(0).distance2D(topLeft) <= bottom.get(0).distance2D(topRight))
+				bottomLeft = bottom.get(0);
+			else if (bottom.get(0).distance2D(topRight) <= bottom.get(0).distance2D(topLeft))
+				bottomRight = bottom.get(0);
+			//find bottom right point
+			
+			if (bottomLeft != null){
+				bottomRight = bottomLeft.add(topCopy);
+			}else if (bottomRight != null){
+				bottomLeft = bottomRight.subtract(topCopy);
+			}
+			
+			
+			
+			Vector3f bottomCenter =  bottomLeft.lerp(bottomRight, .5f);
+			
+			this.lowLerp = new Vector3fImmutable(bottomCenter);
+			this.highLerp = new Vector3fImmutable(topCenter);
+			 
+		}else if (bottom.size() == 2 && top.size() == 1){
+			Vector3f topLeft = Vector3f.min(bottom.get(0), bottom.get(1));
+			Vector3f topRight = Vector3f.max(bottom.get(0), bottom.get(1));
+			
+			Vector3f topCenter =  topLeft.lerp(topRight, .5f);
+			
+			Vector3f topCopy = topRight.subtract2D(topLeft);
+			topCopy.normalize();
+			
+			float topMagnitude = topRight.subtract2D(topLeft).length();
+			
+			topCopy.multLocal(topMagnitude);
+			
+			Vector3f bottomLeft = null;
+			Vector3f bottomRight = null;
+			if (top.get(0).distance2D(topLeft) < top.get(0).distance2D(topRight))
+				bottomLeft = bottom.get(0);
+			else if (top.get(0).distance2D(topRight) < top.get(0).distance2D(topLeft))
+				bottomRight = bottom.get(0);
+			//find bottom right point
+			
+			if (bottomLeft != null){
+				bottomRight = bottomLeft.add(topCopy);
+			}else if (bottomRight != null){
+				bottomLeft = bottomRight.subtract(topCopy);
+			}
+			
+			
+			
+			Vector3f bottomCenter =  bottomLeft.lerp(bottomRight, .5f);
+			
+			this.lowLerp = new Vector3fImmutable(bottomCenter);
+			this.highLerp = new Vector3fImmutable(topCenter);
+		}
+		
+		if (this.lowLerp == null)
+			this.lowLerp = new Vector3fImmutable(this.regionPoints.get(0));
+		
+		if (this.highLerp == null){
+			this.highLerp = new Vector3fImmutable(this.regionPoints.get(2));
+		}
+						
+		this.regionDistanceSquared = this.lowLerp.distanceSquared2D(this.highLerp);
+	}
+
+	public int getRoom() {
+		return room;
+	}
+
+	
+
+
+	public boolean isOutside() {
+		return outside;
+	}
+
+
+	public int getLevel() {
+		return level;
+	}
+	
+	public boolean collides(Vector3fImmutable collisionPoint){
+		
+		//test if inside triangle // Regions either have 3 or 4 points
+		if (this.regionPoints.size() == 3){
+			float regionArea = FastMath.area(regionPoints.get(0).x, regionPoints.get(0).z, regionPoints.get(1).x, regionPoints.get(1).z, regionPoints.get(2).x, regionPoints.get(2).z);
+		float collisionArea1 = FastMath.area(collisionPoint.x, collisionPoint.z, regionPoints.get(0).x, regionPoints.get(0).z,regionPoints.get(1).x, regionPoints.get(1).z);
+		float collisionArea2 = FastMath.area(collisionPoint.x, collisionPoint.z, regionPoints.get(1).x, regionPoints.get(1).z,regionPoints.get(2).x, regionPoints.get(2).z);
+		float collisionArea3 = FastMath.area(collisionPoint.x, collisionPoint.z, regionPoints.get(0).x, regionPoints.get(0).z,regionPoints.get(2).x, regionPoints.get(2).z);
+		
+		if ((collisionArea1 + collisionArea2 + collisionArea3) == regionArea)
+			return true;
+		
+		}else{
+			
+			  int i;
+		      int j;
+		      for (i = 0, j = this.regionPoints.size() - 1; i < this.regionPoints.size(); j = i++) {
+		        if ((regionPoints.get(i).z > collisionPoint.z) != (regionPoints.get(j).z > collisionPoint.z) &&
+		            (collisionPoint.x < (regionPoints.get(j).x - regionPoints.get(i).x) * (collisionPoint.z - regionPoints.get(i).z) / (regionPoints.get(j).z-regionPoints.get(i).z) + regionPoints.get(i).x)) {
+		         return true;
+		         }
+		      }
+		     
+		}
+		
+		return false;
+	}
+	
+	public boolean isPointInPolygon( Vector3fImmutable point)
+	{
+		   boolean inside = false;
+		   for (int i = 0, j = regionPoints.size()-1; i < regionPoints.size(); j = i++)
+		   {
+		      if (((regionPoints.get(i).z > point.z) != (regionPoints.get(j).z > point.z)) &&
+		         (point.x < (regionPoints.get(j).x - regionPoints.get(i).x) * (point.z - regionPoints.get(i).z) / (regionPoints.get(j).z - regionPoints.get(i).z) + regionPoints.get(i).x))
+		         inside = !inside;
+		   }
+		   return inside;
+		}
+	
+	public static boolean CanEnterRegion(AbstractWorldObject worldObject, Regions toEnter){
+
+        if (worldObject.getRegion() == null)
+			if (toEnter.level == 0 || toEnter.room == -1 || toEnter.exit)
+				return true;
+			else
+				return false;
+		
+		if (worldObject.getRegion().equals(toEnter))
+			return true;
+
+        if (worldObject.getRegion().level == toEnter.level)
+			return true;
+		
+		//next region is stairs, if they are on the same level as stairs or 1 up, world object can enter.
+        if (toEnter.stairs)
+			if (worldObject.getRegion().level == toEnter.level || toEnter.level - 1 == worldObject.getRegion().level)
+				return true;
+		if (worldObject.getRegion().stairs){
+			
+			boolean movingUp = false;
+			
+			boolean movingDown = false;
+			float yLerp = worldObject.getRegion().lerpY(worldObject);
+			
+			if (yLerp == (worldObject.getRegion().highLerp.y))
+				movingUp = true;
+			else if (yLerp == (worldObject.getRegion().lowLerp.y))
+					movingDown = true;
+			//Stairs are always considered on the bottom floor.
+
+
+            if (movingUp){
+                if(toEnter.level == worldObject.getRegion().level + 1)
+				return true;
+			}else if (movingDown)
+			 if (toEnter.level == worldObject.getRegion().level)
+				return true;
+			
+		}
+		
+		return false;
+	}
+	
+	public float lerpY (AbstractWorldObject lerper){
+		
+		Vector3fImmutable lengthVector = this.highLerp.subtract2D(this.lowLerp);
+		Vector3fImmutable characterVector = lerper.getLoc().subtract2D(this.lowLerp);
+		float lengthVectorMagnitude = lengthVector.magnitude();
+		float characterVectorMagnitude = characterVector.magnitude();
+		float percentDistance = characterVectorMagnitude/lengthVectorMagnitude;
+		float interpolatedY = this.lowLerp.interpolate(this.highLerp, percentDistance).y;
+		
+		if (interpolatedY > this.highLerp.y)
+			interpolatedY = this.highLerp.y;
+		else if (interpolatedY < this.lowLerp.y)
+			interpolatedY = this.lowLerp.y;
+		return interpolatedY;
+	}
+
+
+	public boolean isStairs() {
+		return stairs;
+	}
+
+
+	public boolean isExit() {
+		return exit;
+	}
+
+public static float GetMagnitudeOfRegionSlope(Regions region){
+	Vector3fImmutable lengthVector = region.highLerp.subtract2D(region.lowLerp);
+	return lengthVector.magnitude();
+	}
+
+public static float GetMagnitudeOfPlayerOnRegionSlope(Regions region, PlayerCharacter player){
+	Vector3fImmutable characterVector = player.getLoc().subtract2D(region.lowLerp);
+	return characterVector.magnitude();
+	}
+
+public static float SlopeLerpPercent(PlayerCharacter player, Regions region){
+	
+	float lengthVectorMagnitude = Regions.GetMagnitudeOfRegionSlope(region);
+	float characterVectorMagnitude = Regions.GetMagnitudeOfPlayerOnRegionSlope(region, player);
+	float percentDistance = characterVectorMagnitude/lengthVectorMagnitude * 2;
+	return percentDistance;
+}
+
+public static boolean CanEnterFromOutsideBuilding(Building building,Regions region){
+    if (!region.outside)
+		return false;
+	if (region.lowLerp.y - building.getLoc().y > 1 )
+		return false;
+	
+	return true;
+}
+
+public static boolean CanEnterNextLevel(Regions fromRegion,Regions toRegion, AbstractWorldObject worldObject){
+	
+	if (fromRegion == null)
+		return false;
+	
+	if (toRegion == null)
+		return false;
+	
+	// regions are the same, no need to go any further.
+	if (fromRegion.equals(toRegion))
+		return true;
+	
+	//cant move up a level without stairs.
+    if (!fromRegion.stairs)
+		return false;
+	
+	boolean movingUp = false;
+	
+	Vector3fImmutable closestPoint = Vector3fImmutable.ClosestPointOnLine(fromRegion.lowLerp, fromRegion.highLerp, worldObject.getLoc());
+	
+	//Closest point of a region higher than current region will always return highlerp.
+	if (closestPoint.equals(fromRegion.highLerp))
+		movingUp = true;
+	//Stairs are always considered on the bottom floor.
+	
+	if (movingUp){
+	if(toRegion.level != fromRegion.level + 1)
+		return false;
+	}else if (toRegion.level != fromRegion.level)
+		return false;
+	return true;
+}
+
+public static boolean IsGroundLevel(Regions region, Building building){
+	
+	if (region.lowLerp.y - building.getLoc().y > 1)
+		return false;
+	return true;
+}
+
+public static Building GetBuildingForRegion(Regions region){
+	return BuildingManager.getBuildingFromCache(region.parentBuildingID);
+}
+public static Regions GetRegionForTeleport(Vector3fImmutable location){
+	Regions region = null;
+
+	
+	//Find building
+	for (AbstractWorldObject awo:WorldGrid.getObjectsInRangePartial(location, MBServerStatics.STRUCTURE_LOAD_RANGE, MBServerStatics.MASK_BUILDING)){
+		Building building = (Building)awo;
+		if (!Bounds.collide(location, building.getBounds()))
+			continue;
+
+		//find regions that intersect x and z, check if object can enter.
+		for (Regions toEnter: building.getBounds().getRegions()){
+			if (toEnter.isPointInPolygon(location)){
+
+				if (region == null)
+					region = toEnter;
+				else // we're using a low level to high level tree structure, database not always in order low to high.
+					//check for highest level index.
+					if(region != null && toEnter.highLerp.y > region.highLerp.y)
+						region = toEnter;
+
+
+			}
+		}
+	}
+	return region;
+}
+}
diff --git a/src/engine/objects/Resists.java b/src/engine/objects/Resists.java
new file mode 100644
index 00000000..4342ae3b
--- /dev/null
+++ b/src/engine/objects/Resists.java
@@ -0,0 +1,562 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.objects;
+
+import engine.Enum;
+import engine.Enum.DamageType;
+import engine.Enum.ModType;
+import engine.Enum.SourceType;
+import engine.gameManager.ChatManager;
+import engine.gameManager.DbManager;
+import engine.powers.EffectsBase;
+import engine.server.MBServerStatics;
+import org.pmw.tinylog.Logger;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class Resists {
+
+	private ConcurrentHashMap<DamageType, Float> resists = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW);
+	private ConcurrentHashMap<DamageType, Boolean> immuneTo = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW);
+	private DamageType protection;
+	private int protectionTrains=0;
+	private boolean immuneToAll;
+	private static ConcurrentHashMap<Integer, Resists> mobResists = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW);
+
+	/**
+	 * Generic Constructor
+	 */
+
+	public Resists(String type) {
+		switch (type) {
+		case "Building":
+			setBuildingResists();
+			break;
+		case "Mine":
+			setMineResists();
+			break;
+		default:
+			setGenericResists();
+			break;
+		}
+	}
+
+	public Resists(Resists r) {
+		for (DamageType dt : r.resists.keySet())
+			this.resists.put(dt, r.resists.get(dt));
+		for (DamageType dt : r.immuneTo.keySet())
+			this.immuneTo.put(dt, r.immuneTo.get(dt));
+		this.protection = r.protection;
+		this.protectionTrains = r.protectionTrains;
+		this.immuneToAll = r.immuneToAll;
+	}
+
+	/**
+	 * Generic Constructor for player
+	 */
+	public Resists(PlayerCharacter pc) {
+		setGenericResists();
+	}
+
+	public Resists(Mob mob) {
+		setGenericResists();
+	}
+
+	/**
+	 * Called for mobBase when getting from the db fails
+	 */
+	public Resists(MobBase mobBase) {
+		setGenericResists();
+	}
+	/**
+	 * Database Constructor
+	 */
+	public Resists(ResultSet rs) throws SQLException {
+		this.immuneToAll = false;
+		this.resists.put(DamageType.Slash, rs.getFloat("slash"));
+		this.resists.put(DamageType.Crush, rs.getFloat("crush"));
+		this.resists.put(DamageType.Pierce,	rs.getFloat("pierce"));
+		this.resists.put(DamageType.Magic, rs.getFloat("magic"));
+		this.resists.put(DamageType.Bleed, rs.getFloat("bleed"));
+		this.resists.put(DamageType.Poison, rs.getFloat("poison"));
+		this.resists.put(DamageType.Mental, rs.getFloat("mental"));
+		this.resists.put(DamageType.Holy, rs.getFloat("holy"));
+		this.resists.put(DamageType.Unholy,	rs.getFloat("unholy"));
+		this.resists.put(DamageType.Lightning, rs.getFloat("lightning"));
+		this.resists.put(DamageType.Fire, rs.getFloat("fire"));
+		this.resists.put(DamageType.Cold, rs.getFloat("cold"));
+		this.resists.put(DamageType.Healing, 0f);
+	}
+
+	/**
+	 * Create generic resists for buildings
+	 */
+	public final void setBuildingResists() {
+		this.immuneToAll = false;
+		this.resists.put(DamageType.Slash, 85f);
+		this.resists.put(DamageType.Crush, 85f);
+		this.resists.put(DamageType.Siege, 0f);
+		this.immuneTo.put(DamageType.Pierce, true);
+		this.immuneTo.put(DamageType.Magic, true);
+		this.immuneTo.put(DamageType.Bleed, true);
+		this.immuneTo.put(DamageType.Poison, true);
+		this.immuneTo.put(DamageType.Mental, true);
+		this.immuneTo.put(DamageType.Holy, true);
+		this.immuneTo.put(DamageType.Unholy, true);
+		this.immuneTo.put(DamageType.Lightning, true);
+		this.immuneTo.put(DamageType.Fire, true);
+		this.immuneTo.put(DamageType.Cold, true);
+
+	}
+
+	/**
+	 * Create generic resists for mines
+	 */
+	public final void setMineResists() {
+		this.immuneToAll = false;
+		this.immuneTo.put(DamageType.Slash, true);
+		this.immuneTo.put(DamageType.Crush, true);
+		this.immuneTo.put(DamageType.Pierce, true);
+		this.immuneTo.put(DamageType.Magic, true);
+		this.immuneTo.put(DamageType.Bleed, true);
+		this.immuneTo.put(DamageType.Poison, true);
+		this.immuneTo.put(DamageType.Mental, true);
+		this.immuneTo.put(DamageType.Holy, true);
+		this.immuneTo.put(DamageType.Unholy, true);
+		this.immuneTo.put(DamageType.Lightning, true);
+		this.immuneTo.put(DamageType.Fire, true);
+		this.immuneTo.put(DamageType.Cold, true);
+		this.resists.put(DamageType.Siege, 0f);
+	}
+
+	/**
+	 * Create generic resists
+	 */
+	public final void setGenericResists() {
+		this.immuneToAll = false;
+		this.resists.put(DamageType.Slash, 0f);
+		this.resists.put(DamageType.Crush, 0f);
+		this.resists.put(DamageType.Pierce, 0f);
+		this.resists.put(DamageType.Magic, 0f);
+		this.resists.put(DamageType.Bleed, 0f);
+		this.resists.put(DamageType.Poison, 0f);
+		this.resists.put(DamageType.Mental, 0f);
+		this.resists.put(DamageType.Holy, 0f);
+		this.resists.put(DamageType.Unholy, 0f);
+		this.resists.put(DamageType.Lightning, 0f);
+		this.resists.put(DamageType.Fire, 0f);
+		this.resists.put(DamageType.Cold, 0f);
+		this.resists.put(DamageType.Healing, 0f);
+		this.immuneTo.put(DamageType.Siege, true);
+
+	}
+
+	/**
+	 * Get a resist
+	 */
+	public float getResist(DamageType type, int trains) {
+		//get resisted amount
+		Float amount = 0f;
+		if (this.resists.containsKey(type))
+			amount = this.resists.get(type);
+
+		//add protection
+		if (trains > 0 && protection != null && type.equals(this.protection)) {
+			float prot = 50 + this.protectionTrains - trains;
+			amount += (prot >= 0) ? prot : 0;
+		}
+
+		if (amount == null)
+			return 0f;
+		if (amount > 75f)
+			return 75f;
+		return amount;
+	}
+
+	/**
+	 * get immuneTo
+	 */
+	public boolean immuneTo(DamageType type) {
+		if (this.immuneTo.containsKey(type))
+			return this.immuneTo.get(type);
+		else
+			return false;
+	}
+
+	/**
+	 * get immuneToAll
+	 */
+	public boolean immuneToAll() {
+		return this.immuneToAll;
+	}
+
+	public boolean immuneToPowers() {
+		return immuneTo(DamageType.Powers);
+	}
+
+	public boolean immuneToAttacks() {
+		return immuneTo(DamageType.Attack);
+	}
+
+	public boolean immuneToSpires() {
+		return immuneTo(DamageType.Spires);
+	}
+
+	/**
+	 * gets immuneTo(type) and immuneToAll
+	 */
+	public boolean isImmune(DamageType type) {
+		if (this.immuneToAll)
+			return true;
+		return this.immuneTo(type);
+	}
+
+	/**
+	 * Set a resist
+	 */
+	public void setResist(DamageType type, float value) {
+		this.resists.put(type, value);
+	}
+
+	/**
+	 * add to a resist
+	 */
+	public void incResist(DamageType type, float value) {
+		Float amount = this.resists.get(type);
+		if (amount == null)
+			this.resists.put(type, value);
+		else
+			this.resists.put(type, amount + value);
+	}
+
+	/**
+	 * subtract from a resist
+	 */
+	public void decResist(DamageType type, float value) {
+		Float amount = this.resists.get(type);
+		if (amount == null)
+			this.resists.put(type, (0 - value));
+		else
+			this.resists.put(type, amount - value);
+	}
+
+	/**
+	 * set immunities from mobbase
+	 */
+	public void setImmuneTo(int immune) {
+		setImmuneTo(DamageType.Stun, ((immune & 1) != 0));
+		setImmuneTo(DamageType.PowerBlock, ((immune & 2) != 0));
+		setImmuneTo(DamageType.Drain, ((immune & 4) != 0));
+		setImmuneTo(DamageType.Snare, ((immune & 8) != 0));
+		setImmuneTo(DamageType.Siege, ((immune & 16) != 0));
+		setImmuneTo(DamageType.Slash, ((immune & 32) != 0));
+		setImmuneTo(DamageType.Crush, ((immune & 64) != 0));
+		setImmuneTo(DamageType.Pierce, ((immune & 128) != 0));
+		setImmuneTo(DamageType.Magic, ((immune & 256) != 0));
+		setImmuneTo(DamageType.Bleed, ((immune & 512) != 0));
+		setImmuneTo(DamageType.Poison, ((immune & 1024) != 0));
+		setImmuneTo(DamageType.Mental, ((immune & 2048) != 0));
+		setImmuneTo(DamageType.Holy, ((immune & 4096) != 0));
+		setImmuneTo(DamageType.Unholy, ((immune & 8192) != 0));
+		setImmuneTo(DamageType.Lightning, ((immune & 16384) != 0));
+		setImmuneTo(DamageType.Fire, ((immune & 32768) != 0));
+		setImmuneTo(DamageType.Cold, ((immune & 65536) != 0));
+		setImmuneTo(DamageType.Steel, ((immune & 131072) != 0));
+	}
+
+	/**
+	 * set/unset immuneTo
+	 */
+	public void setImmuneTo(DamageType type, boolean value) {
+		this.immuneTo.put(type, value);
+	}
+
+	/**
+	 * set immuneToAll
+	 */
+	public void setImmuneToAll(boolean value) {
+		this.immuneToAll = value;
+	}
+
+
+	/**
+	 * set resists from mobbase
+	 */
+	public void setMobResists(int resistID) {
+		//TODO add this in later
+		//calls `static_npc_mob_resists` table WHERE `ID`='resistID'
+	}
+
+	/**
+	 * get Damage after resist
+	 * Expects heals as negative damage and damage as positive damage for fortitudes.
+	 */
+	public float getResistedDamage(AbstractCharacter source, AbstractCharacter target, DamageType type, float damage, int trains) {
+		//handle fortitudes
+		damage = handleFortitude(target, type, damage);
+
+		//check to see if any damage absorbers should cancel
+		float damageAfterResists = damage * (1 - (this.getResist(type, trains) / 100));
+		if (target != null) {
+			//debug damage shields if any found
+			if (source.getDebug(2) && source.getObjectType().equals(Enum.GameObjectType.PlayerCharacter)) {
+				Effect da = target.getDamageAbsorber();
+				if (da != null && da.getEffectsBase() != null) {
+					EffectsBase eb = da.getEffectsBase();
+					String text = "Damage: " + damage + '\n';
+					text += "Damage after resists: " + damageAfterResists + '\n';
+					text += "Attack damage type: " + type.name() + '\n';
+					text += "Fortitude damage types; " + eb.getDamageTypes() + '\n';
+					text += "Fortitude damage before attack: " + da.getDamageAmount() + '\n';
+					text += "Fortitude total health: " + eb.getDamageAmount(da.getTrains()) + '\n';
+					text += "Fortitude trains: " + da.getTrains();
+					ChatManager.chatSystemInfo((PlayerCharacter) source, text);
+				}
+			}
+			target.cancelOnTakeDamage(type, (damageAfterResists));
+		}
+		return damageAfterResists;
+	}
+
+	//Handle Fortitudes
+	private static float handleFortitude(AbstractCharacter target, DamageType type, float damage) {
+		if (target == null || !(target.getObjectType().equals(Enum.GameObjectType.PlayerCharacter)))
+			return damage;
+		PlayerBonuses bonus = target.getBonuses();
+
+		//see if there is a fortitude
+		float damageCap =  bonus.getFloatPercentAll(ModType.DamageCap, SourceType.None);
+		if (damageCap == 0f || type == DamageType.Healing)
+			return damage;
+
+		//is fortitude, Are we under the cap?
+		float maxHealth = target.getHealthMax();
+		float capFire = maxHealth * (damageCap);
+		if (damage < capFire)
+			return damage;
+
+		//let's see if valid damagetype to apply it
+		boolean exclusive;
+		HashSet<SourceType> forts = bonus.getList(ModType.IgnoreDamageCap);
+		if (forts == null) {
+			exclusive = true;
+			forts = bonus.getList(ModType.ExclusiveDamageCap);
+		} else
+			exclusive = false;
+		if (forts == null || !isValidDamageCapType(forts, type, exclusive))
+			return damage;
+
+		float adjustedDamage = bonus.getFloatPercentAll(ModType.AdjustAboveDmgCap, SourceType.None);
+		//Adjust damage down and return new amount
+		float aadc = 1 +adjustedDamage;
+		return capFire * aadc;
+	}
+
+	//Test if Damagetype is valid for foritude
+	private static boolean isValidDamageCapType(HashSet<SourceType> forts, DamageType damageType, boolean exclusive) {
+		for (SourceType fort: forts) {
+			DamageType dt = DamageType.valueOf(fort.name());
+
+			if (dt == DamageType.None)
+				continue;
+
+			if (dt == damageType) {
+                return exclusive;
+			}
+		}
+		return !exclusive;
+	}
+
+
+	/**
+	 * Calculate Current Resists for Player
+	 */
+	public static void calculateResists(AbstractCharacter ac) {
+		if (ac.getResists() != null)
+			ac.getResists().calculateResists(ac, true);
+		else
+			Logger.error("Unable to find resists for character " + ac.getObjectUUID());
+	}
+
+	public void calculateResists(AbstractCharacter ac, boolean val) {
+		this.immuneTo.clear();
+
+		// get resists for runes
+		PlayerBonuses rb = ac.getBonuses();
+		float slash = 0f, crush = 0f, pierce = 0f, magic = 0f, bleed = 0f, mental = 0f, holy = 0f, unholy = 0f, poison = 0f, lightning = 0f, fire = 0f, cold = 0f, healing = 0f;
+
+		if (rb != null) {
+			// Handle immunities
+			if (rb.getBool(ModType.ImmuneTo, SourceType.Stun))
+				this.immuneTo.put(DamageType.Stun, true);
+			if (rb.getBool(ModType.ImmuneTo, SourceType.Blind))
+				this.immuneTo.put(DamageType.Blind, true);
+			if (rb.getBool(ModType.ImmuneToAttack, SourceType.None))
+				this.immuneTo.put(DamageType.Attack, true);
+			if (rb.getBool(ModType.ImmuneToPowers, SourceType.None))
+				this.immuneTo.put(DamageType.Powers, true);
+			if (rb.getBool(ModType.ImmuneTo, SourceType.Powerblock))
+				this.immuneTo.put(DamageType.Powerblock, true);
+			if (rb.getBool(ModType.ImmuneTo, SourceType.DeBuff))
+				this.immuneTo.put(DamageType.DeBuff, true);
+			if (rb.getBool(ModType.ImmuneTo, SourceType.Fear))
+				this.immuneTo.put(DamageType.Fear, true);
+			if (rb.getBool(ModType.ImmuneTo, SourceType.Charm))
+				this.immuneTo.put(DamageType.Charm, true);
+			if (rb.getBool(ModType.ImmuneTo, SourceType.Root))
+				this.immuneTo.put(DamageType.Root, true);
+			if (rb.getBool(ModType.ImmuneTo, SourceType.Snare))
+				this.immuneTo.put(DamageType.Snare, true);
+
+			// Handle resists
+			slash += rb.getFloat(ModType.Resistance, SourceType.Slash);
+			crush += rb.getFloat(ModType.Resistance, SourceType.Crush);
+			pierce += rb.getFloat(ModType.Resistance, SourceType.Pierce);
+			magic += rb.getFloat(ModType.Resistance, SourceType.Magic);
+			bleed += rb.getFloat(ModType.Resistance, SourceType.Bleed);
+			poison += rb.getFloat(ModType.Resistance, SourceType.Poison);
+			mental += rb.getFloat(ModType.Resistance, SourceType.Mental);
+			holy += rb.getFloat(ModType.Resistance, SourceType.Holy);
+			unholy += rb.getFloat(ModType.Resistance, SourceType.Unholy);
+			lightning += rb.getFloat(ModType.Resistance, SourceType.Lightning);
+			fire += rb.getFloat(ModType.Resistance, SourceType.Fire);
+			cold += rb.getFloat(ModType.Resistance, SourceType.Cold);
+			healing += rb.getFloat(ModType.Resistance, SourceType.Healing); // DamageType.Healing.name());
+
+			//HHO
+
+//			String protectionString = rb.getString("protection");
+//
+//			if (protectionString.isEmpty())
+//				this.protection = null;
+//			else try {
+//				this.protection = DamageType.valueOf(rb.getString("protection"));
+//			} catch (IllegalArgumentException e) {
+//				Logger.error( "No enum for: " + protectionString);
+//				this.protection = null;
+//			}
+//			this.protectionTrains = rb.getFloat("protection");
+		}
+
+		// get resists from equipment
+		if (ac.getObjectType().equals(Enum.GameObjectType.PlayerCharacter)) {
+			if (ac.getCharItemManager() != null && ac.getCharItemManager().getEquipped() != null) {
+				float[] phys = { 0f, 0f, 0f };
+				ConcurrentHashMap<Integer, Item> equip = ac.getCharItemManager().getEquipped();
+
+				// get base physical resists
+				phys = Resists.getArmorResists(equip.get(MBServerStatics.SLOT_HELMET), phys);
+				phys = Resists.getArmorResists(equip.get(MBServerStatics.SLOT_CHEST), phys);
+				phys = Resists.getArmorResists(equip.get(MBServerStatics.SLOT_ARMS), phys);
+				phys = Resists.getArmorResists(equip.get(MBServerStatics.SLOT_GLOVES), phys);
+				phys = Resists.getArmorResists(equip.get(MBServerStatics.SLOT_LEGGINGS), phys);
+				phys = Resists.getArmorResists(equip.get(MBServerStatics.SLOT_FEET), phys);
+				slash += phys[0];
+				crush += phys[1];
+				pierce += phys[2];
+
+			}
+		}
+
+		this.resists.put(DamageType.Slash, slash);
+		this.resists.put(DamageType.Crush, crush);
+		this.resists.put(DamageType.Pierce, pierce);
+		this.resists.put(DamageType.Magic, magic);
+		this.resists.put(DamageType.Bleed, bleed);
+		this.resists.put(DamageType.Poison, poison);
+		this.resists.put(DamageType.Mental, mental);
+		this.resists.put(DamageType.Holy, holy);
+		this.resists.put(DamageType.Unholy, unholy);
+		this.resists.put(DamageType.Lightning, lightning);
+		this.resists.put(DamageType.Fire, fire);
+		this.resists.put(DamageType.Cold, cold);
+		this.resists.put(DamageType.Healing, healing);
+
+		this.immuneTo.put(DamageType.Siege, true);
+
+		// debug printing of resists
+		// printResists(pc);
+	}
+
+	private static float[] getArmorResists(Item armor, float[] phys) {
+		if (armor == null)
+			return phys;
+		ItemBase ab = armor.getItemBase();
+		if (ab == null)
+			return phys;
+		phys[0] += ab.getSlashResist();
+		phys[1] += ab.getCrushResist();
+		phys[2] += ab.getPierceResist();
+		return phys;
+	}
+
+	public void printResistsToClient(PlayerCharacter pc) {
+		for (DamageType dt : resists.keySet())
+			ChatManager.chatSystemInfo(pc, "  resist." + dt.name() + ": " + resists.get(dt));
+		for (DamageType dt : immuneTo.keySet())
+			ChatManager.chatSystemInfo(pc, "  immuneTo." + dt.name() + ": " + immuneTo.get(dt));
+		ChatManager.chatSystemInfo(pc, "  immuneToAll: " + this.immuneToAll);
+		if (protection != null)
+			ChatManager.chatSystemInfo(pc, "  Protection: " + protection.name() + ", Trains: " + protectionTrains);
+		else
+			ChatManager.chatSystemInfo(pc, "  Protection: None");
+	}
+
+	public String getResists(PlayerCharacter pc) {
+		String out = pc.getName();
+
+		out += "Resists: ";
+		Iterator<DamageType> it = this.resists.keySet().iterator();
+		while (it.hasNext()) {
+			DamageType damType = it.next();
+			String dtName = damType.name();
+			out += dtName + '=' + this.resists.get(dtName) + ", ";
+		}
+
+		out += "ImmuneTo: ";
+		it = this.immuneTo.keySet().iterator();
+		while (it.hasNext()) {
+			DamageType damType = it.next();
+
+			String dtName = damType.name();
+			out += dtName + '=' + this.resists.get(dtName) + ", ";
+		}
+
+		if (protection != null)
+			out += "Protection: " + protection.name() + ", Trains: " + protectionTrains;
+		else
+			out += "Protection: none";
+
+		return out;
+	}
+
+	/**
+	 * Get mob resists from db if there, otherwise set defaults
+	 */
+	public static Resists getResists(int resistID) {
+		//check cache first
+		if (mobResists.containsKey(resistID))
+			return new Resists(mobResists.get(resistID));
+
+		//get from database
+		Resists resists = DbManager.ResistQueries.GET_RESISTS_FOR_MOB(resistID);
+		if (resists != null) {
+			mobResists.put(resistID, resists);
+			return new Resists(resists);
+		}
+
+		//failed, may want to debug this
+		return null;
+	}
+}
diff --git a/src/engine/objects/Resource.java b/src/engine/objects/Resource.java
new file mode 100644
index 00000000..e8b72cd4
--- /dev/null
+++ b/src/engine/objects/Resource.java
@@ -0,0 +1,69 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.objects;
+
+import engine.server.MBServerStatics;
+
+import java.util.concurrent.ConcurrentHashMap;
+
+public enum Resource {
+
+	ADAMANT("DefaultAdamant", 1557001525, 10, 1580003),
+	AGATE("DefaultAgate", -1096157543, 20, 1580009),
+	ANTIMONY("DefaultAntimony", 1256147265, 10, 1580014),
+	AZOTH("DefaultAzoth", -1205326951, 20, 1580012),
+	BLOODSTONE("DefaultBloodstone", -1912381716, 5, 1580020),
+	BRONZEWOOD("DefaultBronzewood", -519681813, 30, 1580006),
+	COAL("DefaultCoal", -1672872311, 30, 1580008),
+	DIAMOND("DefaultDiamond", 1540225085, 20, 1580010),
+	GALVOR("DefaultGalvor", -1683992404, 5, 1580017),
+	IRON("DefaultIron", -1673518119, 20, 1580002),
+	LUMBER("DefaultLumber", -1628412684, 100, 1580004),
+	MANDRAKE("DefaultMandrake", -1519910613, 10, 1580007),
+	MITHRIL("DefaultMithril", 626743397, 5, 1580021),
+	OAK("DefaultOak", -1653034775, 30, 1580005),
+	OBSIDIAN("DefaultObsidian", 778019055, 5, 1580019),
+	ONYX("DefaultOnyx", -1675952151, 10, 1580011),
+	ORICHALK("DefaultOrichalk", -1468730955, 30, 1580013),
+	QUICKSILVER("DefaultQuicksilver", -2081208434, 10, 1580016),
+	STONE("DefaultStone", -1094703863, 100, 1580000),
+	SULFUR("DefaultSulfur", -1763687412, 10, 1580015),
+	TRUESTEEL("DefaultTruesteel", -169012482, 20, 1580001),
+	WORMWOOD("DefaultWormwood", 1204785075, 5, 1580018),
+	GOLD("DefaultGold", -1670881623, 50000, 7);
+
+	public final String name;
+	public final int hash;
+	public final int baseProduction;
+	public final int UUID;
+	public static ConcurrentHashMap<Integer, Resource> resourceByHash;
+
+	Resource(String name, int hash, int baseProduction, int uuid) {
+		this.name = name;
+		this.hash = hash;
+		this.baseProduction = baseProduction;
+		this.UUID = uuid;
+	}
+	
+	public static Resource GetResourceByHash(int hash){
+		for (Resource resource: Resource.values()){
+			if (hash == resource.hash)
+				return resource;
+		}
+		return Resource.MITHRIL;
+	}
+
+	//load lookups via hashes
+	static {
+        resourceByHash = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW);
+        for (Resource r : Resource.values())
+            resourceByHash.put(r.hash, r);
+    }
+}
diff --git a/src/engine/objects/RuneBase.java b/src/engine/objects/RuneBase.java
new file mode 100644
index 00000000..9738b4bc
--- /dev/null
+++ b/src/engine/objects/RuneBase.java
@@ -0,0 +1,217 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.objects;
+
+import engine.Enum;
+import engine.gameManager.DbManager;
+import engine.net.ByteBufferWriter;
+import engine.server.MBServerStatics;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.concurrent.ConcurrentHashMap;
+
+
+public class RuneBase extends AbstractGameObject {
+
+	private final String name;
+	private final String description;
+	private final int type;
+	private final byte subtype;
+
+	private final ConcurrentHashMap<Integer, Boolean> race = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW);
+	private final ConcurrentHashMap<Integer, Boolean> baseClass = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW);
+	private final ConcurrentHashMap<Integer, Boolean> promotionClass = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW);
+	private final ConcurrentHashMap<Integer, Boolean> discipline = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW);
+	private final ArrayList<Integer> overwrite = new ArrayList<>();
+	private int levelRequired = 1;
+
+	private ArrayList<MobBaseEffects> effectsList = new ArrayList<>();
+
+	public static HashMap<Integer,ArrayList<Integer>> AllowedBaseClassRunesMap = new HashMap<>();
+	public static HashMap<Integer,ArrayList<Integer>> AllowedRaceRunesMap = new HashMap<>();
+	/**
+	 * No Table ID Constructor
+	 */
+	public RuneBase(String name, String description, int type, byte subtype, ArrayList<RuneBaseAttribute> attrs) {
+		super();
+
+		this.name = name;
+		this.description = description;
+		this.type = type;
+		this.subtype = subtype;
+
+	}
+
+	/**
+	 * Normal Constructor
+	 */
+	public RuneBase(String name, String description, int type, byte subtype, ArrayList<RuneBaseAttribute> attrs, int newUUID) {
+		super(newUUID);
+
+		this.name = name;
+		this.description = description;
+		this.type = type;
+		this.subtype = subtype;
+
+	}
+
+	/**
+	 * ResultSet Constructor
+	 */
+	public RuneBase(ResultSet rs) throws SQLException {
+		super(rs);
+
+		this.name = rs.getString("name");
+		this.description = rs.getString("description");
+		this.type = rs.getInt("type");
+		this.subtype = rs.getByte("subtype");
+
+		DbManager.RuneBaseQueries.GET_RUNE_REQS(this);
+		this.effectsList = DbManager.MobBaseQueries.GET_RUNEBASE_EFFECTS(this.getObjectUUID());
+	}
+
+	@Override
+	public boolean equals(Object obj) {
+
+		if (!super.equals(obj)) {
+			return false;
+		}
+
+		if(obj instanceof RuneBase) {
+			RuneBase rbObj = (RuneBase) obj;
+			if (!this.name.equals(rbObj.name)) {
+				return false;
+			}
+
+			if (!this.description.equals(rbObj.description)) {
+				return false;
+			}
+
+			if (this.type != rbObj.type) {
+				return false;
+			}
+
+			if (this.subtype != rbObj.subtype) {
+				return false;
+			}
+
+
+			return true;
+		}
+
+		return false;
+	}
+
+	/*
+	 * Getters
+	 */
+	public String getName() {
+		return name;
+	}
+
+	public String getDescription() {
+		return description;
+	}
+
+	public int getType() {
+		return type;
+	}
+
+	/**
+	 * @return the subtype
+	 */
+	public byte getSubtype() {
+		return subtype;
+	}
+
+	/**
+	 * @return the attrs
+	 */
+	public ArrayList<RuneBaseAttribute> getAttrs() {
+		return RuneBaseAttribute.runeBaseAttributeMap.get(this.getObjectUUID());
+	}
+
+	public ConcurrentHashMap<Integer, Boolean> getRace() {
+		return this.race;
+	}
+
+	public ConcurrentHashMap<Integer, Boolean> getBaseClass() {
+		return this.baseClass;
+	}
+
+	public ConcurrentHashMap<Integer, Boolean> getPromotionClass() {
+		return this.promotionClass;
+	}
+
+	public ConcurrentHashMap<Integer, Boolean> getDiscipline() {
+		return this.discipline;
+	}
+
+	public ArrayList<Integer> getOverwrite() {
+		return this.overwrite;
+	}
+
+	public int getLevelRequired() {
+		return this.levelRequired;
+	}
+
+	public void setLevelRequired(int levelRequired) {
+		this.levelRequired = levelRequired;
+	}
+
+	public static RuneBase getRuneBase(int tableId) {
+
+		if (tableId == 0)
+			return null;
+
+		RuneBase rb = (RuneBase) DbManager.getFromCache(Enum.GameObjectType.RuneBase, tableId);
+
+		if (rb != null)
+			return rb;
+
+		return DbManager.RuneBaseQueries.GET_RUNEBASE(tableId);
+	}
+
+	/*
+	 * Serializing
+	 */
+	
+	public static void serializeForClientMsg(RuneBase runeBase,ByteBufferWriter writer) {
+		writer.putInt(runeBase.type);
+		writer.putInt(0); // Pad
+		writer.putInt(runeBase.getObjectUUID());
+		writer.putInt(runeBase.getObjectType().ordinal());
+		writer.putInt(runeBase.getObjectUUID());
+
+	}
+
+	@Override
+	public void updateDatabase() {
+		// TODO Auto-generated method stub
+	}
+
+	/**
+	 * @return the effectsList
+	 */
+	public ArrayList<MobBaseEffects> getEffectsList() {
+		return effectsList;
+	}
+
+	public static void LoadAllRuneBases(){
+
+		DbManager.RuneBaseQueries.LOAD_ALL_RUNEBASES();
+		RuneBase.AllowedBaseClassRunesMap = DbManager.RuneBaseQueries.LOAD_ALLOWED_STARTING_RUNES_FOR_BASECLASS();
+		RuneBase.AllowedRaceRunesMap = DbManager.RuneBaseQueries.LOAD_ALLOWED_STARTING_RUNES_FOR_RACE();
+	}
+
+}
diff --git a/src/engine/objects/RuneBaseAttribute.java b/src/engine/objects/RuneBaseAttribute.java
new file mode 100644
index 00000000..5ea74162
--- /dev/null
+++ b/src/engine/objects/RuneBaseAttribute.java
@@ -0,0 +1,107 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.objects;
+
+import engine.Enum.GameObjectType;
+import engine.gameManager.DbManager;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.HashMap;
+
+
+public class RuneBaseAttribute extends AbstractGameObject {
+
+	private short attributeID;
+	private short modValue;
+
+	private int runeBaseID;
+
+	public static HashMap<Integer,ArrayList<RuneBaseAttribute>> runeBaseAttributeMap = new HashMap<>();
+
+
+	/**
+	 * No Table ID Constructor
+	 */
+	public RuneBaseAttribute(short attributeID, short modValue) {
+		super();
+
+		this.attributeID = attributeID;
+		this.modValue = modValue;
+	}
+
+	/**
+	 * Normal
+	 */
+	public RuneBaseAttribute(short attributeID, short modValue, int newUUID) {
+		super(newUUID);
+
+		this.attributeID = attributeID;
+		this.modValue = modValue;
+	}
+	/**
+	 * ResultSet Constructor
+	 */
+	public RuneBaseAttribute(ResultSet rs) throws SQLException {
+		super(rs);
+
+		this.attributeID = rs.getShort("attributeID");
+		this.modValue = rs.getShort("modValue");
+		this.runeBaseID = rs.getInt("RuneBaseID");
+	}
+
+	/*
+	 * Getters
+	 */
+	public short getAttributeID() {
+		return attributeID;
+	}
+
+	public short getModValue() {
+		return modValue;
+	}
+
+	public static void LoadAllAttributes(){
+		DbManager.RuneBaseAttributeQueries.GET_ATTRIBUTES_FOR_RUNEBASE();
+
+
+		//cache attributeLists for rune.
+		for (AbstractGameObject ago : DbManager.getList(GameObjectType.RuneBaseAttribute)){
+
+			RuneBaseAttribute runeBaseAttribute = (RuneBaseAttribute)ago;
+
+			int runeBaseID = ((RuneBaseAttribute)runeBaseAttribute).runeBaseID;
+			if (runeBaseAttributeMap.get(runeBaseID) == null){
+				ArrayList<RuneBaseAttribute> attributeList = new ArrayList<>();
+				attributeList.add(runeBaseAttribute);
+				runeBaseAttributeMap.put(runeBaseID, attributeList);
+			}
+			else{
+				ArrayList<RuneBaseAttribute>attributeList = runeBaseAttributeMap.get(runeBaseID);
+				attributeList.add(runeBaseAttribute);
+				runeBaseAttributeMap.put(runeBaseID, attributeList);
+			}
+
+		}
+
+	}
+
+	/*
+	 * Utils
+	 */
+
+
+
+	@Override
+	public void updateDatabase() {
+		// TODO Auto-generated method stub
+	}
+}
diff --git a/src/engine/objects/RuneBaseEffect.java b/src/engine/objects/RuneBaseEffect.java
new file mode 100644
index 00000000..f9d79828
--- /dev/null
+++ b/src/engine/objects/RuneBaseEffect.java
@@ -0,0 +1,72 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.objects;
+
+import engine.gameManager.DbManager;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.HashMap;
+
+
+public class RuneBaseEffect extends AbstractGameObject {
+
+	private byte type;
+	private String name;
+	private short amount;
+	private int runeBaseID;
+
+	public static HashMap<Integer,ArrayList<RuneBaseEffect>> RuneIDBaseEffectMap = new HashMap<>();
+	/**
+	 * ResultSet Constructor
+	 */
+	public RuneBaseEffect(ResultSet rs) throws SQLException {
+		super(rs);
+		this.type = rs.getByte("type");
+		this.name = rs.getString("name");
+		this.amount = rs.getShort("amount");
+		this.runeBaseID = rs.getInt("runeID");
+	}
+
+	/*
+	 * Getters
+	 */
+
+	public int getType() {
+		return this.type;
+	}
+
+	public String getName() {
+		return this.name;
+	}
+
+	public short getAmount() {
+		return this.amount;
+	}
+
+
+	@Override
+	public void updateDatabase() {
+
+	}
+
+	public int getRuneBaseID() {
+		return runeBaseID;
+	}
+
+	public static void LoadRuneBaseEffects(){
+		//cache runebase effects.
+		DbManager.RuneBaseEffectQueries.GET_ALL_RUNEBASE_EFFECTS();
+		//store runebase effects in new hashmap.
+		RuneBaseEffect.RuneIDBaseEffectMap = DbManager.RuneBaseEffectQueries.LOAD_BASEEFFECTS_FOR_RUNEBASE();
+	}
+
+}
\ No newline at end of file
diff --git a/src/engine/objects/Runegate.java b/src/engine/objects/Runegate.java
new file mode 100644
index 00000000..ad67bbaf
--- /dev/null
+++ b/src/engine/objects/Runegate.java
@@ -0,0 +1,220 @@
+package engine.objects;
+
+import engine.Enum.RunegateType;
+import engine.gameManager.BuildingManager;
+import engine.net.ByteBufferWriter;
+
+import java.util.ArrayList;
+
+/* Runegates are tied to particular buildings at
+ * bootstrap.  They aren't tighly coupled, with
+ * the Runegate merely toggling effect bits on it's
+ * parent building.
+ */
+
+public class Runegate {
+
+	// Runegate class Instance variables
+	private static final Runegate[] _runegates = new Runegate[9];
+
+	private final Portal[] _portals;
+	private final RunegateType gateType;
+
+	private Runegate(RunegateType gateType) {
+
+		this._portals = new Portal[8];
+		this.gateType = gateType;
+
+		// Each Runegate has a different destination
+		// for each portal opened.
+		configureGatePortals();
+
+		// Chaos, Khar and Oblivion are on by default
+
+		_portals[RunegateType.CHAOS.ordinal()].activate(false);
+		_portals[RunegateType.OBLIV.ordinal()].activate(false);
+		_portals[RunegateType.MERCHANT.ordinal()].activate(false);
+
+	}
+
+	public void activatePortal(RunegateType gateType) {
+
+		this._portals[gateType.ordinal()].activate(true);
+
+	}
+
+	public void deactivatePortal(RunegateType gateType) {
+
+		this._portals[gateType.ordinal()].deactivate();
+
+	}
+
+	public RunegateType getGateType() {
+		return this.gateType;
+	}
+
+	public static Runegate[] getRunegates() {
+		return Runegate._runegates;
+	}
+
+	public Portal[] getPortals() {
+
+		return this._portals;
+
+	}
+
+	public void collidePortals() {
+
+		for (Portal portal : this.getPortals()) {
+
+			if (portal.isActive())
+				portal.collide();
+		}
+	}
+
+	public static void loadAllRunegates() {
+
+		for (RunegateType runegateType : RunegateType.values()) {
+			_runegates[runegateType.ordinal()] = new Runegate(runegateType);
+		}
+
+	}
+
+	public void _serializeForEnterWorld(ByteBufferWriter writer) {
+
+		Building gateBuilding;
+
+		gateBuilding = BuildingManager.getBuilding(this.gateType.getGateUUID());
+
+		writer.putInt(gateBuilding.getObjectType().ordinal());
+		writer.putInt(gateBuilding.getObjectUUID());
+		writer.putString(gateBuilding.getParentZone().getName());
+		writer.putFloat(gateBuilding.getLoc().getLat());
+		writer.putFloat(gateBuilding.getLoc().getAlt());
+		writer.putFloat(gateBuilding.getLoc().getLong());
+	}
+
+	private void configureGatePortals() {
+
+		// Source gate type, portal type and destination gate type;
+		switch (this.gateType) {
+
+		case EARTH:
+			_portals[RunegateType.EARTH.ordinal()] = new Portal(RunegateType.EARTH, RunegateType.EARTH, RunegateType.EARTH);
+			_portals[RunegateType.AIR.ordinal()] = new Portal(RunegateType.EARTH, RunegateType.AIR, RunegateType.AIR);
+			_portals[RunegateType.FIRE.ordinal()] = new Portal(RunegateType.EARTH, RunegateType.FIRE, RunegateType.FORBID);
+			_portals[RunegateType.WATER.ordinal()] = new Portal(RunegateType.EARTH, RunegateType.WATER, RunegateType.WATER);
+			_portals[RunegateType.SPIRIT.ordinal()] = new Portal(RunegateType.EARTH, RunegateType.SPIRIT, RunegateType.SPIRIT);
+			_portals[RunegateType.CHAOS.ordinal()] = new Portal(RunegateType.EARTH, RunegateType.CHAOS, RunegateType.CHAOS);
+			_portals[RunegateType.OBLIV.ordinal()] = new Portal(RunegateType.EARTH, RunegateType.OBLIV, RunegateType.OBLIV);
+			_portals[RunegateType.MERCHANT.ordinal()] = new Portal(RunegateType.EARTH, RunegateType.MERCHANT, RunegateType.MERCHANT);
+			break;
+
+		case AIR:
+			_portals[RunegateType.EARTH.ordinal()] = new Portal(RunegateType.AIR, RunegateType.EARTH, RunegateType.EARTH);
+			_portals[RunegateType.AIR.ordinal()] = new Portal(RunegateType.AIR, RunegateType.AIR, RunegateType.FORBID);
+			_portals[RunegateType.FIRE.ordinal()] = new Portal(RunegateType.AIR, RunegateType.FIRE, RunegateType.FIRE);
+			_portals[RunegateType.WATER.ordinal()] = new Portal(RunegateType.AIR, RunegateType.WATER, RunegateType.WATER);
+			_portals[RunegateType.SPIRIT.ordinal()] = new Portal(RunegateType.AIR, RunegateType.SPIRIT, RunegateType.SPIRIT);
+			_portals[RunegateType.CHAOS.ordinal()] = new Portal(RunegateType.AIR, RunegateType.CHAOS, RunegateType.CHAOS);
+			_portals[RunegateType.OBLIV.ordinal()] = new Portal(RunegateType.AIR, RunegateType.OBLIV, RunegateType.OBLIV);
+			_portals[RunegateType.MERCHANT.ordinal()] = new Portal(RunegateType.AIR, RunegateType.MERCHANT, RunegateType.MERCHANT);
+
+			break;
+
+		case FIRE:
+			_portals[RunegateType.EARTH.ordinal()] = new Portal(RunegateType.FIRE, RunegateType.EARTH, RunegateType.EARTH);
+			_portals[RunegateType.AIR.ordinal()] = new Portal(RunegateType.FIRE, RunegateType.AIR, RunegateType.AIR);
+			_portals[RunegateType.FIRE.ordinal()] = new Portal(RunegateType.FIRE, RunegateType.FIRE, RunegateType.FORBID);
+			_portals[RunegateType.WATER.ordinal()] = new Portal(RunegateType.FIRE, RunegateType.WATER, RunegateType.WATER);
+			_portals[RunegateType.SPIRIT.ordinal()] = new Portal(RunegateType.FIRE, RunegateType.SPIRIT, RunegateType.SPIRIT);
+			_portals[RunegateType.CHAOS.ordinal()] = new Portal(RunegateType.FIRE, RunegateType.CHAOS, RunegateType.CHAOS);
+			_portals[RunegateType.OBLIV.ordinal()] = new Portal(RunegateType.FIRE, RunegateType.OBLIV, RunegateType.OBLIV);
+			_portals[RunegateType.MERCHANT.ordinal()] = new Portal(RunegateType.FIRE, RunegateType.MERCHANT, RunegateType.MERCHANT);
+			break;
+
+		case WATER:
+			_portals[RunegateType.EARTH.ordinal()] = new Portal(RunegateType.WATER, RunegateType.EARTH, RunegateType.EARTH);
+			_portals[RunegateType.AIR.ordinal()] = new Portal(RunegateType.WATER, RunegateType.AIR, RunegateType.AIR);
+			_portals[RunegateType.FIRE.ordinal()] = new Portal(RunegateType.WATER, RunegateType.FIRE, RunegateType.FIRE);
+			_portals[RunegateType.WATER.ordinal()] = new Portal(RunegateType.WATER, RunegateType.WATER, RunegateType.FORBID);
+			_portals[RunegateType.SPIRIT.ordinal()] = new Portal(RunegateType.WATER, RunegateType.SPIRIT, RunegateType.SPIRIT);
+			_portals[RunegateType.CHAOS.ordinal()] = new Portal(RunegateType.WATER, RunegateType.CHAOS, RunegateType.CHAOS);
+			_portals[RunegateType.OBLIV.ordinal()] = new Portal(RunegateType.WATER, RunegateType.OBLIV, RunegateType.OBLIV);
+			_portals[RunegateType.MERCHANT.ordinal()] = new Portal(RunegateType.WATER, RunegateType.MERCHANT, RunegateType.MERCHANT);
+			break;
+
+		case SPIRIT:
+			_portals[RunegateType.EARTH.ordinal()] = new Portal(RunegateType.SPIRIT, RunegateType.EARTH, RunegateType.EARTH);
+			_portals[RunegateType.AIR.ordinal()] = new Portal(RunegateType.SPIRIT, RunegateType.AIR, RunegateType.AIR);
+			_portals[RunegateType.FIRE.ordinal()] = new Portal(RunegateType.SPIRIT, RunegateType.FIRE, RunegateType.FIRE);
+			_portals[RunegateType.WATER.ordinal()] = new Portal(RunegateType.SPIRIT, RunegateType.WATER, RunegateType.WATER);
+			_portals[RunegateType.SPIRIT.ordinal()] = new Portal(RunegateType.SPIRIT, RunegateType.SPIRIT, RunegateType.FORBID);
+			_portals[RunegateType.CHAOS.ordinal()] = new Portal(RunegateType.SPIRIT, RunegateType.CHAOS, RunegateType.CHAOS);
+			_portals[RunegateType.OBLIV.ordinal()] = new Portal(RunegateType.SPIRIT, RunegateType.OBLIV, RunegateType.OBLIV);
+			_portals[RunegateType.MERCHANT.ordinal()] = new Portal(RunegateType.SPIRIT, RunegateType.MERCHANT, RunegateType.MERCHANT);
+			break;
+
+		case CHAOS:
+			_portals[RunegateType.EARTH.ordinal()] = new Portal(RunegateType.CHAOS, RunegateType.EARTH, RunegateType.EARTH);
+			_portals[RunegateType.AIR.ordinal()] = new Portal(RunegateType.CHAOS, RunegateType.AIR, RunegateType.AIR);
+			_portals[RunegateType.FIRE.ordinal()] = new Portal(RunegateType.CHAOS, RunegateType.FIRE, RunegateType.FIRE);
+			_portals[RunegateType.WATER.ordinal()] = new Portal(RunegateType.CHAOS, RunegateType.WATER, RunegateType.WATER);
+			_portals[RunegateType.SPIRIT.ordinal()] = new Portal(RunegateType.CHAOS, RunegateType.SPIRIT, RunegateType.SPIRIT);
+			_portals[RunegateType.CHAOS.ordinal()] = new Portal(RunegateType.CHAOS, RunegateType.CHAOS, RunegateType.MERCHANT);
+			_portals[RunegateType.OBLIV.ordinal()] = new Portal(RunegateType.CHAOS, RunegateType.OBLIV, RunegateType.OBLIV);
+			_portals[RunegateType.MERCHANT.ordinal()] = new Portal(RunegateType.CHAOS, RunegateType.MERCHANT, RunegateType.MERCHANT);
+			break;
+
+		case OBLIV:
+			_portals[RunegateType.EARTH.ordinal()] = new Portal(RunegateType.OBLIV, RunegateType.EARTH, RunegateType.EARTH);
+			_portals[RunegateType.AIR.ordinal()] = new Portal(RunegateType.OBLIV, RunegateType.AIR, RunegateType.AIR);
+			_portals[RunegateType.FIRE.ordinal()] = new Portal(RunegateType.OBLIV, RunegateType.FIRE, RunegateType.FIRE);
+			_portals[RunegateType.WATER.ordinal()] = new Portal(RunegateType.OBLIV, RunegateType.WATER, RunegateType.WATER);
+			_portals[RunegateType.SPIRIT.ordinal()] = new Portal(RunegateType.OBLIV, RunegateType.SPIRIT, RunegateType.SPIRIT);
+			_portals[RunegateType.CHAOS.ordinal()] = new Portal(RunegateType.OBLIV, RunegateType.CHAOS, RunegateType.CHAOS);
+			_portals[RunegateType.OBLIV.ordinal()] = new Portal(RunegateType.OBLIV, RunegateType.OBLIV, RunegateType.MERCHANT);
+			_portals[RunegateType.MERCHANT.ordinal()] = new Portal(RunegateType.OBLIV, RunegateType.MERCHANT, RunegateType.MERCHANT);
+			break;
+
+		case MERCHANT:
+			_portals[RunegateType.EARTH.ordinal()] = new Portal(RunegateType.MERCHANT, RunegateType.EARTH, RunegateType.EARTH);
+			_portals[RunegateType.AIR.ordinal()] = new Portal(RunegateType.MERCHANT, RunegateType.AIR, RunegateType.AIR);
+			_portals[RunegateType.FIRE.ordinal()] = new Portal(RunegateType.MERCHANT, RunegateType.FIRE, RunegateType.FIRE);
+			_portals[RunegateType.WATER.ordinal()] = new Portal(RunegateType.MERCHANT, RunegateType.WATER, RunegateType.WATER);
+			_portals[RunegateType.SPIRIT.ordinal()] = new Portal(RunegateType.MERCHANT, RunegateType.SPIRIT, RunegateType.SPIRIT);
+			_portals[RunegateType.CHAOS.ordinal()] = new Portal(RunegateType.MERCHANT, RunegateType.CHAOS, RunegateType.CHAOS);
+			_portals[RunegateType.OBLIV.ordinal()] = new Portal(RunegateType.MERCHANT, RunegateType.OBLIV, RunegateType.OBLIV);
+			_portals[RunegateType.MERCHANT.ordinal()] = new Portal(RunegateType.MERCHANT, RunegateType.MERCHANT, RunegateType.FORBID);
+			break;
+
+		case FORBID:
+			_portals[RunegateType.EARTH.ordinal()] = new Portal(RunegateType.FORBID, RunegateType.EARTH, RunegateType.EARTH);
+			_portals[RunegateType.AIR.ordinal()] = new Portal(RunegateType.FORBID, RunegateType.AIR, RunegateType.AIR);
+			_portals[RunegateType.FIRE.ordinal()] = new Portal(RunegateType.FORBID, RunegateType.FIRE, RunegateType.FIRE);
+			_portals[RunegateType.WATER.ordinal()] = new Portal(RunegateType.FORBID, RunegateType.WATER, RunegateType.WATER);
+			_portals[RunegateType.SPIRIT.ordinal()] = new Portal(RunegateType.FORBID, RunegateType.SPIRIT, RunegateType.SPIRIT);
+			_portals[RunegateType.CHAOS.ordinal()] = new Portal(RunegateType.FORBID, RunegateType.CHAOS, RunegateType.CHAOS);
+			_portals[RunegateType.OBLIV.ordinal()] = new Portal(RunegateType.FORBID, RunegateType.OBLIV, RunegateType.OBLIV);
+			_portals[RunegateType.MERCHANT.ordinal()] = new Portal(RunegateType.FORBID, RunegateType.MERCHANT, RunegateType.MERCHANT);
+			break;
+
+		}
+
+	}
+	
+	public static ArrayList<String> GetAllOpenGateIDStrings(){
+		ArrayList<String> openGateIDStrings = new ArrayList<>();
+		
+		openGateIDStrings.add("TRA-003");
+		openGateIDStrings.add("TRA-004");
+		openGateIDStrings.add("TRA-005");
+		openGateIDStrings.add("TRA-006");
+		openGateIDStrings.add("TRA-007");
+		openGateIDStrings.add("TRA-008");
+		openGateIDStrings.add("TRA-009");
+		openGateIDStrings.add("TRA-010");
+		return openGateIDStrings;
+	}
+
+}
diff --git a/src/engine/objects/Shrine.java b/src/engine/objects/Shrine.java
new file mode 100644
index 00000000..86cd9a99
--- /dev/null
+++ b/src/engine/objects/Shrine.java
@@ -0,0 +1,355 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.objects;
+
+import engine.Enum;
+import engine.Enum.BuildingGroup;
+import engine.Enum.ShrineType;
+import engine.gameManager.ChatManager;
+import engine.gameManager.DbManager;
+import org.pmw.tinylog.Logger;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class Shrine extends AbstractWorldObject implements Comparable<Shrine> {
+
+	private final ShrineType shrineType;
+	private Integer favors;
+	private final int buildingID;
+
+	public static ConcurrentHashMap<Integer, Shrine> shrinesByBuildingUUID = new ConcurrentHashMap<>();
+
+	/**
+	 * ResultSet Constructor
+	 */
+	public Shrine(ResultSet rs) throws SQLException {
+		super(rs);
+		this.shrineType = ShrineType.valueOf(rs.getString("shrine_type"));
+		this.favors = rs.getInt("shrine_favors");
+		this.buildingID = rs.getInt("parent");
+		shrinesByBuildingUUID.put(this.buildingID, this);
+	}
+
+	// Decays this shrine's favor by 10%
+
+	public void decay() {
+
+		if (this.getFavors() == 0)
+			return;
+
+		int decayAmount = (int) (this.getFavors() - (this.getFavors() *.10f));
+
+		if (decayAmount < 0)
+			decayAmount = 0;
+
+		if (!DbManager.ShrineQueries.updateFavors(this, decayAmount, this.getFavors())) {
+			Logger.error("Shrine Decay", "Error writing to DB. UUID: " + this.getObjectUUID());
+			return;
+		}
+		this.favors = decayAmount;
+
+		Logger.info( shrineType.name() + " uuid:" + this.getObjectUUID() + " Amount: " + this.getFavors() *.10f );
+
+	}
+
+	public synchronized boolean addFavor(PlayerCharacter boonOwner, Item boonItem) {
+
+		if (boonOwner == null)
+			return false;
+
+		if (boonItem == null)
+			return false;
+
+		ItemBase ib = boonItem.getItemBase();
+
+		if (ib == null)
+			return false;
+
+		if (!boonOwner.getCharItemManager().doesCharOwnThisItem(boonItem.getObjectUUID()))
+			return false;
+
+		ArrayList<Boon> boonList = Boon.GetBoonsForItemBase.get(ib.getUUID());
+
+		if (boonList == null)
+			return false;
+
+		for (Boon boon : boonList) {
+
+			ShrineType boonShrineType = boon.getShrineType();
+
+            if (boonShrineType != shrineType)
+				continue;
+
+			//Same Shrine Type, add favors and stop loop.
+			int amount = boon.getAmount() * boonItem.getNumOfItems();
+			int oldAmount = this.favors;
+
+			if (!DbManager.ShrineQueries.updateFavors(this, this.favors + amount, oldAmount)) {
+				ChatManager.chatSystemError(boonOwner, "Failed to add boon to shrine.");
+				return false;
+			}
+
+			this.favors += amount;
+			boonOwner.getCharItemManager().delete(boonItem);
+			boonOwner.getCharItemManager().updateInventory();
+			return true;
+		}
+		return false;
+	}
+
+	public synchronized boolean takeFavor(PlayerCharacter boonOwner) {
+
+		if (boonOwner == null)
+			return false;
+
+		int oldAmount = this.favors;
+		int newAmount = this.favors - 1;
+
+		if (!DbManager.ShrineQueries.updateFavors(this, newAmount, oldAmount)) {
+			ChatManager.chatSystemError(boonOwner, "Failed to add boon to shrine.");
+			return false;
+		}
+		this.favors = newAmount;
+		return true;
+	}
+
+	public static boolean canTakeFavor(PlayerCharacter grantee, Shrine shrine) {
+
+        if (shrine.shrineType.isRace())
+			switch (grantee.getRaceID()) {
+			case 2000:
+			case 2001:
+                if (shrine.shrineType == ShrineType.Aelfborn)
+					return true;
+				break;
+			case 2002:
+			case 2003:
+                if (shrine.shrineType == ShrineType.Aracoix)
+					return true;
+				break;
+			case 2004:
+			case 2005:
+                if (shrine.shrineType == ShrineType.Centaur)
+					return true;
+				break;
+			case 2006:
+                if (shrine.shrineType == ShrineType.Dwarf)
+					return true;
+				break;
+			case 2008:
+			case 2009:
+                if (shrine.shrineType == ShrineType.Elf)
+					return true;
+				break;
+			case 2010:
+			case 2027:
+                if (shrine.shrineType == ShrineType.HalfGiant)
+					return true;
+				break;
+			case 2011:
+			case 2012:
+                if (shrine.shrineType == ShrineType.Human)
+					return true;
+				break;
+			case 2013:
+			case 2014:
+                if (shrine.shrineType == ShrineType.Irekei)
+					return true;
+				break;
+			case 2015:
+			case 2016:
+                if (shrine.shrineType == ShrineType.Shade)
+					return true;
+				break;
+			case 2017:
+                if (shrine.shrineType == ShrineType.Minotaur)
+					return true;
+				break;
+
+			case 2025:
+			case 2026:
+                if (shrine.shrineType == ShrineType.Nephilim)
+					return true;
+				break;
+			case 2028:
+			case 2029:
+                if (shrine.shrineType == ShrineType.Vampire)
+					return true;
+				break;
+
+			}
+		else
+			switch (grantee.getPromotionClassID()) {
+			case 2504:
+                if (shrine.shrineType == ShrineType.Assassin)
+					return true;
+				break;
+			case 2505:
+                if (shrine.shrineType == ShrineType.Barbarian)
+					return true;
+				break;
+			case 2506:
+                if (shrine.shrineType == ShrineType.Bard)
+					return true;
+				break;
+			case 2507:
+                if (shrine.shrineType == ShrineType.Channeler)
+					return true;
+				break;
+			case 2508:
+                if (shrine.shrineType == ShrineType.Confessor)
+					return true;
+				break;
+			case 2509:
+                if (shrine.shrineType == ShrineType.Crusader)
+					return true;
+				break;
+			case 2510:
+                if (shrine.shrineType == ShrineType.Druid)
+					return true;
+				break;
+			case 2511:
+                if (shrine.shrineType == ShrineType.Fury)
+					return true;
+				break;
+			case 2512:
+                if (shrine.shrineType == ShrineType.Huntress)
+					return true;
+				break;
+			case 2513:
+                if (shrine.shrineType == ShrineType.Prelate)
+					return true;
+				break;
+			case 2514:
+                if (shrine.shrineType == ShrineType.Ranger)
+					return true;
+				break;
+			case 2515:
+                if (shrine.shrineType == ShrineType.Scout)
+					return true;
+				break;
+			case 2516:
+                if (shrine.shrineType == ShrineType.Templar)
+					return true;
+				break;
+			case 2517:
+                if (shrine.shrineType == ShrineType.Warlock)
+					return true;
+				break;
+			case 2518:
+                if (shrine.shrineType == ShrineType.Warrior)
+					return true;
+				break;
+			case 2519:
+                if (shrine.shrineType == ShrineType.Priest)
+					return true;
+				break;
+			case 2520:
+                if (shrine.shrineType == ShrineType.Thief)
+					return true;
+				break;
+			case 2521:
+                if (shrine.shrineType == ShrineType.Wizard)
+					return true;
+				break;
+			case 2523:
+                if (shrine.shrineType == ShrineType.Doomsayer)
+					return true;
+				break;
+			case 2524:
+                if (shrine.shrineType == ShrineType.Sentinel)
+					return true;
+				break;
+			case 2525:
+                if (shrine.shrineType == ShrineType.Necromancer)
+					return true;
+				break;
+			case 2526:
+                if (shrine.shrineType == ShrineType.Nightstalker)
+					return true;
+				break;
+			}
+
+		return false;
+	}
+
+	public static ShrineType getShrineTypeByBlueprintUUID(int blueprintUUID) {
+
+		for (ShrineType shrineType : ShrineType.values()) {
+
+			if (shrineType.getBlueprintUUID() == blueprintUUID)
+				return shrineType;
+		}
+		return null;
+	}
+
+	@Override
+	public int compareTo(Shrine other) {
+		return other.favors.compareTo(this.favors);
+	}
+
+	public int getRank() {
+        return shrineType.getShrinesCopy().indexOf(this);
+	}
+
+	public ShrineType getShrineType() {
+		return shrineType;
+	}
+
+	public static void RemoveShrineFromCacheByBuilding(Building building) {
+
+		if (building.getBlueprint() != null && building.getBlueprint().getBuildingGroup() == BuildingGroup.SHRINE) {
+			Shrine shrine = Shrine.shrinesByBuildingUUID.get(building.getObjectUUID());
+
+			if (shrine != null) {
+                shrine.shrineType.RemoveShrineFromServerList(shrine);
+				Shrine.shrinesByBuildingUUID.remove(building.getObjectUUID());
+				DbManager.removeFromCache(Enum.GameObjectType.Shrine,
+						shrine.getObjectUUID());
+			}
+		}
+
+	}
+
+	@Override
+	public void updateDatabase() {
+		// TODO Auto-generated method stub
+
+	}
+
+	public int getFavors() {
+		return favors;
+	}
+
+	public int getBuildingID() {
+		return buildingID;
+	}
+
+	@Override
+	public void runAfterLoad() {
+		// TODO Auto-generated method stub
+
+	}
+
+
+	@Override
+	public void removeFromCache() {
+		// TODO Auto-generated method stub
+
+	}
+
+	public void setFavors(Integer favors) {
+		this.favors = favors;
+	}
+
+}
diff --git a/src/engine/objects/SkillReq.java b/src/engine/objects/SkillReq.java
new file mode 100644
index 00000000..99f4dea9
--- /dev/null
+++ b/src/engine/objects/SkillReq.java
@@ -0,0 +1,97 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.objects;
+
+import engine.gameManager.DbManager;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+
+
+public class SkillReq extends AbstractGameObject {
+
+
+	private int skillID;
+	private short level;
+	private ArrayList<Byte> skillReqs;
+
+	/* This shouldn't be used
+	public SkillReq(SkillsBase skillsBase, short level, ArrayList<Byte>skillReqs) {
+
+		super();
+		this.skillsBase = skillsBase;
+		this.level = level;
+		this.skillReqs = skillReqs;
+	}
+	*/
+
+	/* This shouldn't be used
+	public SkillReq(SkillsBase skillsBase, short level, ArrayList<Byte>skillReqs, int newUUID) {
+
+		super(newUUID);
+		this.skillsBase = skillsBase;
+		this.level = level;
+		this.skillReqs = skillReqs;
+	}
+	*/
+
+	/* This shouldn't be used
+	public SkillReq(SkillReq a, int newUUID) {
+		super(a, newUUID);
+		this.skillsBase = a.skillsBase;
+		this.level = a.level;
+		this.skillReqs = a.skillReqs;
+	}
+	*/
+
+	/**
+	 * ResultSet Constructor
+	 */
+	public SkillReq(ResultSet rs) throws SQLException {
+		super(rs, 0);
+		this.skillID = rs.getInt("skillID");
+		this.level = rs.getShort("level");
+		skillReqs = new ArrayList<>(0);
+
+		int skillReq;
+		skillReq = rs.getInt("skillreq1");
+		if (skillReq > 0) skillReqs.add((byte)skillReq);
+		skillReq = rs.getInt("skillreq2");
+		if (skillReq > 0) skillReqs.add((byte)skillReq);
+		skillReq = rs.getInt("skillreq3");
+		if (skillReq > 0) skillReqs.add((byte)skillReq);
+	}
+
+	/*
+	 * Getters
+	 */
+	public SkillsBase getSkillsBase() {
+		return DbManager.SkillsBaseQueries.GET_BASE(this.skillID);
+	}
+
+	public int getSkillID() {
+		return this.skillID;
+	}
+
+	public short getLevel() {
+		return this.level;
+	}
+
+	public ArrayList<Byte> getSkillReqs() {
+		return this.skillReqs;
+	}
+
+
+	@Override
+	public void updateDatabase() {
+
+	}
+}
\ No newline at end of file
diff --git a/src/engine/objects/SkillsBase.java b/src/engine/objects/SkillsBase.java
new file mode 100644
index 00000000..2c37fab7
--- /dev/null
+++ b/src/engine/objects/SkillsBase.java
@@ -0,0 +1,160 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+ package engine.objects;
+
+import engine.Enum.SourceType;
+import engine.gameManager.DbManager;
+import engine.server.MBServerStatics;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.HashMap;
+import java.util.concurrent.ConcurrentHashMap;
+
+
+public class SkillsBase extends AbstractGameObject {
+
+	private final String name;
+	private final String nameNoSpace;
+	private final String description;
+	private final int token;
+	private final short strMod;
+	private final short dexMod;
+	private final short conMod;
+	private final short intMod;
+	private final short spiMod;
+	public static ConcurrentHashMap<String, SkillsBase> skillsCache = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW);
+	public static ConcurrentHashMap<Integer, SkillsBase> tokenCache = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW);
+	public static HashMap<Integer, HashMap<Integer, Integer>> runeSkillsCache = new HashMap<>();
+	public SourceType sourceType;
+	/**
+	 * No Table ID Constructor
+	 */
+	public SkillsBase(String name, String description, int token, short strMod,
+					  short dexMod, short conMod, short intMod, short spiMod) {
+		super();
+		this.name = name;
+		this.nameNoSpace = name.replace(" ", "");
+		this.sourceType = SourceType.GetSourceType(this.nameNoSpace.replace(",", ""));
+		this.description = description;
+		this.token = token;
+		this.strMod = strMod;
+		this.dexMod = dexMod;
+		this.conMod = conMod;
+		this.intMod = intMod;
+		this.spiMod = spiMod;
+	}
+
+	/**
+	 * Normal Constructor
+	 */
+	public SkillsBase(String name, String description, int token, short strMod,
+			short dexMod, short conMod, short intMod, short spiMod, int newUUID) {
+		super(newUUID);
+		this.name = name;
+		this.nameNoSpace = name.replace(" ", "");
+		this.description = description;
+		this.token = token;
+		this.strMod = strMod;
+		this.dexMod = dexMod;
+		this.conMod = conMod;
+		this.intMod = intMod;
+		this.spiMod = spiMod;
+	}
+
+	/**
+	 * ResultSet Constructor
+	 */
+	public SkillsBase(ResultSet rs) throws SQLException {
+		super(rs);
+
+		this.name = rs.getString("name");
+		this.nameNoSpace = name.replace(" ", "");
+		this.description = rs.getString("description");
+		this.sourceType = SourceType.GetSourceType(this.nameNoSpace.replace("-", "").replace("\"", "").replace(",", ""));
+		this.token = rs.getInt("token");
+		this.strMod = rs.getShort("strMod");
+		this.dexMod = rs.getShort("dexMod");
+		this.conMod = rs.getShort("conMod");
+		this.intMod = rs.getShort("intMod");
+		this.spiMod = rs.getShort("spiMod");
+	}
+
+	/*
+	 * Getters
+	 */
+	public String getName() {
+		return name;
+	}
+
+	public String getNameNoSpace() {
+		return nameNoSpace;
+	}
+	
+
+	public String getDescription() {
+		return description;
+	}
+
+	public int getToken() {
+		return this.token;
+	}
+
+	public short getStrMod() {
+		return this.strMod;
+	}
+
+	public short getDexMod() {
+		return this.dexMod;
+	}
+
+	public short getConMod() {
+		return this.conMod;
+	}
+
+	public short getIntMod() {
+		return this.intMod;
+	}
+
+	public short getSpiMod() {
+		return this.spiMod;
+	}
+
+	public static SkillsBase getFromCache(String name) {
+		if (skillsCache.containsKey(name))
+			return skillsCache.get(name);
+		else
+			return null;
+	}
+
+	public static SkillsBase getFromCache(int token) {
+		if (tokenCache.containsKey(token))
+			return tokenCache.get(token);
+		else
+			return null;
+	}
+
+	public static void putInCache(SkillsBase sb) {
+
+		if(sb == null)
+			return;
+
+		DbManager.addToCache(sb);
+        skillsCache.putIfAbsent(sb.name, sb);
+        tokenCache.putIfAbsent(sb.token, sb);
+	}
+
+
+
+	@Override
+	public void updateDatabase() {
+		// TODO Auto-generated method stub
+	}
+}
diff --git a/src/engine/objects/SkillsBaseAttribute.java b/src/engine/objects/SkillsBaseAttribute.java
new file mode 100644
index 00000000..c8ce47a2
--- /dev/null
+++ b/src/engine/objects/SkillsBaseAttribute.java
@@ -0,0 +1,70 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+ package engine.objects;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+
+public class SkillsBaseAttribute extends AbstractGameObject {
+
+	private short attributeID;
+	private short modValue;
+
+	/**
+	 * No Table ID Constructor
+	 */
+	public SkillsBaseAttribute(short attributeID, short modValue) {
+		super();
+
+		this.attributeID = attributeID;
+		this.modValue = modValue;
+	}
+
+	/**
+	 * Normal Constructor
+	 */
+	public SkillsBaseAttribute(short attributeID, short modValue, int newUUID) {
+		super(newUUID);
+
+		this.attributeID = attributeID;
+		this.modValue = modValue;
+	}
+
+	/**
+	 * ResultSet Constructor
+	 */
+	public SkillsBaseAttribute(ResultSet rs) throws SQLException {
+		super(rs);
+
+		this.attributeID = rs.getShort("attributeID");
+		this.modValue = rs.getShort("modValue");
+	}
+
+	/*
+	 * Getters
+	 */
+	public short getAttributeID() {
+		return attributeID;
+	}
+
+	public short getModValue() {
+		return modValue;
+	}
+
+
+	/*
+	 * Database
+	 */
+	@Override
+	public void updateDatabase() {
+		// TODO Auto-generated method stub
+	}
+}
diff --git a/src/engine/objects/SpecialLoot.java b/src/engine/objects/SpecialLoot.java
new file mode 100644
index 00000000..8afa6657
--- /dev/null
+++ b/src/engine/objects/SpecialLoot.java
@@ -0,0 +1,77 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.objects;
+
+import engine.gameManager.DbManager;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.HashMap;
+
+public class SpecialLoot extends AbstractGameObject {
+
+	private int itemID;
+	private int dropChance;
+	private boolean dropOnDeath;
+	private boolean noSteal;
+	private int lootSetID;
+
+	public static HashMap<Integer,ArrayList<SpecialLoot>> LootMap = new HashMap<>();
+	/**
+	 * ResultSet Constructor
+	 */
+	public SpecialLoot(ResultSet rs) throws SQLException {
+		super(rs);
+		this.itemID = rs.getInt("itemID");
+		this.dropChance = rs.getInt("dropChance");
+		this.dropOnDeath = rs.getBoolean("dropOnDeath");
+		this.noSteal = rs.getBoolean("noSteal");
+	}
+
+	public SpecialLoot(ResultSet rs,boolean specialLoot) throws SQLException {
+		super(rs);
+
+		this.lootSetID = rs.getInt("lootSet");
+		this.itemID = rs.getInt("itemID");
+		this.dropChance = rs.getInt("dropChance");
+		this.dropOnDeath = false;
+		this.noSteal = true;
+	}
+
+	/*
+	 * Getters
+	 */
+
+	public int getItemID() {
+		return this.itemID;
+	}
+
+	public int getDropChance() {
+		return this.dropChance;
+	}
+
+	public boolean dropOnDeath() {
+		return this.dropOnDeath;
+	}
+
+	public boolean noSteal() {
+		return this.noSteal;
+	}
+
+	public static ArrayList<SpecialLoot> getSpecialLoot(int mobbaseID) {
+		return DbManager.SpecialLootQueries.GET_SPECIALLOOT(mobbaseID);
+	}
+
+	@Override
+	public void updateDatabase() {
+
+	}
+}
\ No newline at end of file
diff --git a/src/engine/objects/StaticColliders.java b/src/engine/objects/StaticColliders.java
new file mode 100644
index 00000000..4794d676
--- /dev/null
+++ b/src/engine/objects/StaticColliders.java
@@ -0,0 +1,101 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.objects;
+
+import engine.gameManager.DbManager;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.HashMap;
+
+public class StaticColliders  {
+
+	private int meshID;
+	private float startX;
+	private float startY;
+	private float endX;
+	private float endY;
+	private int doorID;
+	public static HashMap<Integer,ArrayList<StaticColliders>> _staticColliders = new HashMap<>();
+	private boolean link = false;
+
+
+
+
+	/**
+	 * ResultSet Constructor
+	 */
+
+	public StaticColliders(ResultSet rs) throws SQLException {
+		this.meshID = rs.getInt("meshID");
+		this.startX = rs.getInt("startX");
+		this.startY = rs.getInt("startY");
+		this.endX = rs.getInt("endX");
+		this.endY = rs.getInt("endY");
+		this.doorID = rs.getInt("doorID");
+		this.link = rs.getBoolean("link");
+	}
+
+	public StaticColliders(int meshID, float startX, float startY, float endX,
+			float endY, int doorID,boolean link) {
+		super();
+		this.meshID = meshID;
+		this.startX = startX;
+		this.startY = startY;
+		this.endX = endX;
+		this.endY = endY;
+		this.doorID = doorID;
+		this.link = link;
+	}
+
+	public static void loadAllStaticColliders(){
+		_staticColliders = DbManager.BuildingQueries.LOAD_ALL_STATIC_COLLIDERS();
+	}
+
+	public static ArrayList<StaticColliders> GetStaticCollidersForMeshID(int meshID) {
+		return _staticColliders.get(meshID);
+	}
+
+
+
+
+	public int getMeshID() {
+		return meshID;
+	}
+
+	public float getStartX() {
+		return startX;
+	}
+
+	public float getStartY() {
+		return startY;
+	}
+
+	public float getEndX() {
+		return endX;
+	}
+
+	public float getEndY() {
+		return endY;
+	}
+
+	public int getDoorID() {
+		return doorID;
+	}
+
+	public boolean isLink() {
+		return link;
+	}
+
+	public void setLink(boolean link) {
+		this.link = link;
+	}
+}
diff --git a/src/engine/objects/Transaction.java b/src/engine/objects/Transaction.java
new file mode 100644
index 00000000..c03b1cdf
--- /dev/null
+++ b/src/engine/objects/Transaction.java
@@ -0,0 +1,111 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+
+package engine.objects;
+
+import engine.Enum.GameObjectType;
+import engine.Enum.TransactionType;
+import org.joda.time.DateTime;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.Date;
+
+
+
+public class Transaction  implements Comparable<Transaction> {
+
+	private final int warehouseUUID;
+	private final int targetUUID;
+	private final Resource resource;
+	private final DateTime date;
+	private final int amount;
+	private GameObjectType targetType;
+	private final TransactionType transactionType;
+	
+	
+	 
+	public Transaction(ResultSet rs) throws SQLException {
+		this.warehouseUUID = rs.getInt("warehouseUID");
+		this.targetUUID = rs.getInt("targetUID");
+		this.targetType = GameObjectType.valueOf(rs.getString("targetType"));
+		this.transactionType = TransactionType.valueOf(rs.getString("type").toUpperCase());
+		this.resource = Resource.valueOf(rs.getString("resource").toUpperCase());
+		this.amount = rs.getInt("amount");
+                
+		Date sqlDateTime = rs.getTimestamp("date");
+                
+		if (sqlDateTime != null)
+			this.date = new DateTime(sqlDateTime);
+		else
+			this.date = DateTime.now();
+
+	}
+
+	
+	public Transaction(int warehouseUUID,GameObjectType targetType, int targetUUID, TransactionType transactionType, Resource resource, int amount,
+			DateTime date) {
+		this.warehouseUUID = warehouseUUID;
+		this.targetUUID = targetUUID;
+		this.resource = resource;
+		this.date = date;
+		this.amount = amount;
+		this.targetType = targetType;
+		this.transactionType = transactionType;
+	}
+
+
+	public int getWarehouseUUID() {
+		return warehouseUUID;
+	}
+
+
+	public int getTargetUUID() {
+		return targetUUID;
+	}
+
+
+	public Resource getResource() {
+		return resource;
+	}
+
+
+	public DateTime getDate() {
+		return date;
+	}
+
+
+	public int getAmount() {
+		return amount;
+	}
+
+
+	public TransactionType getTransactionType() {
+		return transactionType;
+	}
+
+
+	@Override
+	public int compareTo(Transaction arg0) {
+		// TODO Auto-generated method stub
+		return 0;
+	}
+
+
+	public GameObjectType getTargetType() {
+		return targetType;
+	}
+
+
+	public void setTargetType(GameObjectType targetType) {
+		this.targetType = targetType;
+	}
+	
+}
diff --git a/src/engine/objects/VendorDialog.java b/src/engine/objects/VendorDialog.java
new file mode 100644
index 00000000..3bf2cdd1
--- /dev/null
+++ b/src/engine/objects/VendorDialog.java
@@ -0,0 +1,74 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.objects;
+
+import engine.gameManager.DbManager;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+
+
+public class VendorDialog extends AbstractGameObject {
+
+	private final String dialogType;
+	private final String intro;
+	private ArrayList<MenuOption> options = new ArrayList<>();
+
+	public VendorDialog(String dialogType, String intro, int UUID) {
+		super(UUID);
+		this.dialogType = dialogType;
+		this.intro = intro;
+	}
+
+	/**
+	 * ResultSet Constructor
+	 */
+	public VendorDialog(ResultSet rs) throws SQLException {
+		super(rs);
+		this.dialogType = rs.getString("dialogType");
+		this.intro = rs.getString("intro");
+		this.options = DbManager.MenuQueries.GET_MENU_OPTIONS(this.getObjectUUID());
+	}
+
+	/*
+	 * Getters
+	 */
+	public String getDialogType() {
+		return this.dialogType;
+	}
+
+	public String getIntro() {
+		return this.intro;
+	}
+
+	public ArrayList<MenuOption> getOptions() {
+		return this.options;
+	}
+
+	private static VendorDialog vd;
+	public static VendorDialog getHostileVendorDialog() {
+		if (VendorDialog.vd == null)
+			VendorDialog.vd = new VendorDialog("TrainerDialog", "HostileIntro", 0);
+		return VendorDialog.vd;
+	}
+
+
+	/*
+	 * Database
+	 */
+	@Override
+	public void updateDatabase() {}
+
+	public static VendorDialog getVendorDialog(int id) {
+		
+		return DbManager.VendorDialogQueries.GET_VENDORDIALOG(id);
+	}
+}
diff --git a/src/engine/objects/Warehouse.java b/src/engine/objects/Warehouse.java
new file mode 100644
index 00000000..a044810e
--- /dev/null
+++ b/src/engine/objects/Warehouse.java
@@ -0,0 +1,1340 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.objects;
+
+import ch.claude_martin.enumbitset.EnumBitSet;
+import engine.Enum;
+import engine.Enum.*;
+import engine.gameManager.BuildingManager;
+import engine.gameManager.ChatManager;
+import engine.gameManager.DbManager;
+import engine.net.Dispatch;
+import engine.net.DispatchMessage;
+import engine.net.client.ClientConnection;
+import engine.net.client.msg.*;
+import engine.server.MBServerStatics;
+import org.joda.time.DateTime;
+import org.pmw.tinylog.Logger;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class Warehouse extends AbstractWorldObject {
+
+
+	private int UID;
+
+	public EnumBitSet<Enum.ResourceType> lockedResourceTypes;
+
+	private int buildingUID;
+	private ArrayList<Transaction> transactions = new ArrayList<>();
+
+	private  ConcurrentHashMap<ItemBase, Integer> resources = new ConcurrentHashMap<>();
+
+	public static ItemBase goldIB = ItemBase.getItemBase(7);
+	public static ItemBase stoneIB = ItemBase.getItemBase(1580000);
+	public static ItemBase truesteelIB = ItemBase.getItemBase(1580001);
+	public static ItemBase ironIB = ItemBase.getItemBase(1580002);
+	public static ItemBase adamantIB = ItemBase.getItemBase(1580003);
+	public static ItemBase lumberIB = ItemBase.getItemBase(1580004);
+	public static ItemBase oakIB = ItemBase.getItemBase(1580005);
+	public static ItemBase bronzewoodIB = ItemBase.getItemBase(1580006);
+	public static ItemBase mandrakeIB = ItemBase.getItemBase(1580007);
+	public static ItemBase coalIB = ItemBase.getItemBase(1580008);
+	public static ItemBase agateIB = ItemBase.getItemBase(1580009);
+	public static ItemBase diamondIB = ItemBase.getItemBase(1580010);
+	public static ItemBase onyxIB = ItemBase.getItemBase(1580011);
+	public static ItemBase azothIB = ItemBase.getItemBase(1580012);
+	public static ItemBase orichalkIB = ItemBase.getItemBase(1580013);
+	public static ItemBase antimonyIB = ItemBase.getItemBase(1580014);
+	public static ItemBase sulferIB = ItemBase.getItemBase(1580015);
+	public static ItemBase quicksilverIB = ItemBase.getItemBase(1580016);
+	public static ItemBase galvorIB = ItemBase.getItemBase(1580017);
+	public static ItemBase wormwoodIB = ItemBase.getItemBase(1580018);
+	public static ItemBase obsidianIB = ItemBase.getItemBase(1580019);
+	public static ItemBase bloodstoneIB = ItemBase.getItemBase(1580020);
+	public static ItemBase mithrilIB = ItemBase.getItemBase(1580021);
+	public static ConcurrentHashMap<Integer, Integer> maxResources = new ConcurrentHashMap<>();
+	public static ConcurrentHashMap<Integer,Warehouse> warehouseByBuildingUUID = new ConcurrentHashMap<>();
+
+
+
+	public static ConcurrentHashMap<Integer, Integer> getMaxResources() {
+		if(maxResources.size() != 23){
+			maxResources.put(7, 100000000);
+			maxResources.put(1580000, 10000);
+			maxResources.put(1580001, 2000);
+			maxResources.put(1580002, 2000);
+			maxResources.put(1580003, 1000);
+			maxResources.put(1580004, 10000);
+			maxResources.put(1580005, 3000);
+			maxResources.put(1580006, 3000);
+			maxResources.put(1580007, 1000);
+			maxResources.put(1580008, 3000);
+			maxResources.put(1580009, 2000);
+			maxResources.put(1580010, 2000);
+			maxResources.put(1580011, 1000);
+			maxResources.put(1580012, 2000);
+			maxResources.put(1580013, 3000);
+			maxResources.put(1580014, 1000);
+			maxResources.put(1580015, 1000);
+			maxResources.put(1580016, 1000);
+			maxResources.put(1580017, 500);
+			maxResources.put(1580018, 500);
+			maxResources.put(1580019, 500);
+			maxResources.put(1580020, 500);
+			maxResources.put(1580021, 500);
+		}
+
+		return maxResources;
+	}
+
+	/**
+	 * ResultSet Constructor
+	 */
+	public Warehouse(ResultSet rs) throws SQLException {
+		super(rs);
+		this.UID = rs.getInt("UID");
+		this.resources.put(stoneIB, rs.getInt("warehouse_stone"));
+		this.resources.put(truesteelIB,rs.getInt("warehouse_truesteel"));
+		this.resources.put(ironIB,rs.getInt("warehouse_iron"));
+		this.resources.put(adamantIB,rs.getInt("warehouse_adamant"));
+		this.resources.put(lumberIB,rs.getInt("warehouse_lumber"));
+		this.resources.put(oakIB,rs.getInt("warehouse_oak"));
+		this.resources.put(bronzewoodIB,rs.getInt("warehouse_bronzewood"));
+		this.resources.put(mandrakeIB,rs.getInt("warehouse_mandrake"));
+		this.resources.put(coalIB,rs.getInt("warehouse_coal"));
+		this.resources.put(agateIB,rs.getInt("warehouse_agate"));
+		this.resources.put(diamondIB,rs.getInt("warehouse_diamond"));
+		this.resources.put(onyxIB,rs.getInt("warehouse_onyx"));
+		this.resources.put(azothIB,rs.getInt("warehouse_azoth"));
+		this.resources.put(orichalkIB,rs.getInt("warehouse_orichalk"));
+		this.resources.put(antimonyIB,rs.getInt("warehouse_antimony"));
+		this.resources.put(sulferIB,rs.getInt("warehouse_sulfur"));
+		this.resources.put(quicksilverIB,rs.getInt("warehouse_quicksilver"));
+		this.resources.put(galvorIB,rs.getInt("warehouse_galvor"));
+		this.resources.put(wormwoodIB,rs.getInt("warehouse_wormwood"));
+		this.resources.put(obsidianIB,rs.getInt("warehouse_obsidian"));
+		this.resources.put(bloodstoneIB,rs.getInt("warehouse_bloodstone"));
+		this.resources.put(mithrilIB,rs.getInt("warehouse_mithril"));
+		this.resources.put(goldIB, rs.getInt("warehouse_gold"));
+		this.lockedResourceTypes = EnumBitSet.asEnumBitSet(rs.getLong("warehouse_locks"), Enum.ResourceType.class);
+		this.buildingUID = rs.getInt("parent");
+		Warehouse.warehouseByBuildingUUID.put(this.buildingUID, this);
+	}
+
+    public static void warehouseDeposit(MerchantMsg msg, PlayerCharacter player, NPC npc, ClientConnection origin) {
+
+		Building warehouseBuilding;
+		Warehouse warehouse;
+		int depositAmount;
+		Dispatch dispatch;
+
+		Item resource = Item.getFromCache(msg.getItemID());
+
+		if (resource == null)
+			return;
+
+		depositAmount = msg.getAmount();
+		CharacterItemManager itemMan = player.getCharItemManager();
+
+		if (itemMan.doesCharOwnThisItem(resource.getObjectUUID()) == false)
+			return;
+
+		warehouseBuilding = npc.getBuilding();
+
+		if (warehouseBuilding == null)
+			return;
+
+		warehouse = warehouseByBuildingUUID.get(warehouseBuilding.getObjectUUID());
+
+		if (warehouse == null)
+			return;
+
+		ItemBase ib = resource.getItemBase();
+
+		if (!warehouse.deposit(player, resource, depositAmount, true,true)) {
+			//            ChatManager.chatGuildError(player, "Failed to deposit " + ib.getName() +".");
+			//            Logger.debug("OpenWindow", player.getName() + " Failed to deposit Item with ID " + resource.getObjectUUID() + " from Warehouse With ID = " + warehouseBuilding.getObjectUUID());
+			return;
+		}
+
+		ViewResourcesMessage vrm = new ViewResourcesMessage(player);
+		vrm.setGuild(player.getGuild());
+		vrm.setWarehouseBuilding(warehouseBuilding);
+		vrm.configure();
+		dispatch = Dispatch.borrow(player, vrm);
+		DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+	}
+
+    public static void warehouseWithdraw(MerchantMsg msg, PlayerCharacter player, NPC npc, ClientConnection origin) {
+
+		int withdrawAmount;
+		Building warehouseBuilding;
+		Warehouse warehouse;
+		Dispatch dispatch;
+
+		withdrawAmount = msg.getAmount();
+		warehouseBuilding = npc.getBuilding();
+
+		if (warehouseBuilding == null)
+			return;
+
+		if (player.getGuild() != warehouseBuilding.getGuild() || GuildStatusController.isInnerCouncil(player.getGuildStatus()) == false)
+			return;
+
+		warehouse = warehouseByBuildingUUID.get(warehouseBuilding.getObjectUUID());
+
+		if (warehouse == null)
+			return;
+
+		int hashID = msg.getHashID();
+		int itemBaseID = ItemBase.getItemHashIDMap().get(hashID);
+		ItemBase ib = ItemBase.getItemBase(itemBaseID);
+
+		if (ib == null) {
+			Logger.debug("Failed to find Resource ItemBaseID with Hash ID = " + hashID);
+			return;
+		}
+
+		if (warehouse.isResourceLocked(ib) == true) {
+			ChatManager.chatSystemInfo(player, "You cannot withdrawl a locked resource.");
+			return;
+		}
+		if (!warehouse.withdraw(player, ib, withdrawAmount, true,true)) {
+			ChatManager.chatGuildError(player, "Failed to withdrawl " + ib.getName() + '.');
+			Logger.debug(player.getName() + " Failed to withdrawl  =" + ib.getName() + " from Warehouse With ID = " + warehouseBuilding.getObjectUUID());
+			return;
+		}
+
+		ViewResourcesMessage vrm = new ViewResourcesMessage(player);
+		vrm.setGuild(player.getGuild());
+		vrm.setWarehouseBuilding(warehouseBuilding);
+		vrm.configure();
+		dispatch = Dispatch.borrow(player, vrm);
+		DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+	}
+
+    public static void warehouseLock(MerchantMsg msg, PlayerCharacter player, NPC npc, ClientConnection origin) {
+		Building warehouse;
+		int hashID;
+		Dispatch dispatch;
+
+		hashID = msg.getHashID();
+		warehouse = npc.getBuilding();
+
+		if (warehouse == null)
+			return;
+
+		if (player.getGuild() != warehouse.getGuild() || GuildStatusController.isInnerCouncil(player.getGuildStatus()) == false)
+			return;
+
+		Warehouse wh = warehouseByBuildingUUID.get(warehouse.getObjectUUID());
+
+		if (wh == null)
+			return;
+
+		int itemBaseID = ItemBase.getItemHashIDMap().get(hashID);
+		ItemBase ib = ItemBase.getItemBase(itemBaseID);
+
+		if (ib == null)
+			return;
+
+		if (wh.isResourceLocked(ib) == true) {
+			boolean worked = false;
+			EnumBitSet<ResourceType> bitSet = EnumBitSet.asEnumBitSet(wh.lockedResourceTypes.toLong(), ResourceType.class);
+			
+			bitSet.remove(ResourceType.resourceLookup.get(itemBaseID));
+			
+			worked = DbManager.WarehouseQueries.updateLocks(wh, bitSet.toLong());
+						
+			if (worked) {
+				wh.lockedResourceTypes.remove(Enum.ResourceType.resourceLookup.get(itemBaseID));
+				ViewResourcesMessage vrm = new ViewResourcesMessage(player);
+				vrm.setGuild(player.getGuild());
+				vrm.setWarehouseBuilding(warehouse);
+				vrm.configure();
+				dispatch = Dispatch.borrow(player, vrm);
+				DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+			}
+			return;
+		}
+
+		EnumBitSet<ResourceType> bitSet = EnumBitSet.asEnumBitSet(wh.lockedResourceTypes.toLong(), ResourceType.class);
+		
+		bitSet.add(ResourceType.resourceLookup.get(itemBaseID));
+		
+		if (DbManager.WarehouseQueries.updateLocks(wh,bitSet.toLong()) == false)
+			return;
+
+		wh.lockedResourceTypes.add(Enum.ResourceType.resourceLookup.get(itemBaseID));
+		ViewResourcesMessage vrm = new ViewResourcesMessage(player);
+		vrm.setGuild(player.getGuild());
+		vrm.setWarehouseBuilding(warehouse);
+		vrm.configure();
+		dispatch = Dispatch.borrow(player, vrm);
+		DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+
+	}
+
+    public  ConcurrentHashMap<ItemBase, Integer> getResources() {
+		return resources;
+	}
+
+	public int getUID() {
+		return UID;
+	}
+
+	public void setUID(int uID) {
+		UID = uID;
+	}
+
+	public synchronized boolean deposit(PlayerCharacter pc,Item resource, int amount,boolean removeFromInventory, boolean transaction){
+
+		ClientConnection origin = pc.getClientConnection();
+		if (origin == null)
+			return false;
+
+		if (amount < 0){
+			Logger.info(pc.getFirstName() + " Attempting to Dupe!!!!!!");
+			return false;
+		}
+
+		ItemBase ib = resource.getItemBase();
+
+		if (ib == null)
+			return false;
+
+		if (this.resources.get(ib) == null)
+			return false;
+
+		CharacterItemManager itemMan = pc.getCharItemManager();
+
+		if (itemMan == null)
+			return false;
+		
+	
+			if (itemMan.getGoldTrading() > 0){
+				ErrorPopupMsg.sendErrorPopup(pc, 195);
+				return false;
+			}
+		
+
+		if (!itemMan.doesCharOwnThisItem(resource.getObjectUUID()))
+			return false;
+
+		if (!resource.validForInventory(origin, pc, itemMan))
+			return false;
+
+		if (resource.getNumOfItems() < amount)
+			return false;
+
+		int oldAmount = resources.get(ib);
+
+		int newAmount = oldAmount + amount;
+
+		if (newAmount > Warehouse.getMaxResources().get(ib.getUUID())){
+			//ChatManager.chatSystemInfo(pc, "The Warehouse is at it's maximum for this type of resource.");
+			return false;
+		}
+
+
+		if (removeFromInventory){
+			if (ib.getUUID() == 7){
+
+				if (itemMan.getGoldInventory().getNumOfItems() -amount < 0)
+					return false;
+
+				if (itemMan.getGoldInventory().getNumOfItems() - amount > MBServerStatics.PLAYER_GOLD_LIMIT)
+					return false;
+
+				if (!itemMan.modifyInventoryGold(-amount)){
+					//ChatManager.chatSystemError(pc, "You do not have this Gold.");
+					return false;
+				}
+
+				UpdateGoldMsg ugm = new UpdateGoldMsg(pc);
+				ugm.configure();
+				Dispatch dispatch = Dispatch.borrow(pc, ugm);
+				DispatchMessage.dispatchMsgDispatch(dispatch, engine.Enum.DispatchChannel.SECONDARY);
+
+				itemMan.updateInventory();
+
+			}else{
+				itemMan.delete(resource);
+				itemMan.updateInventory();
+			}
+		}
+		itemMan.updateInventory();
+		int itemID = ib.getUUID();
+		boolean worked = false;
+		switch(itemID){
+		case 7:
+			worked = DbManager.WarehouseQueries.updateGold(this, newAmount);
+			break;
+		case 1580000:
+			worked = DbManager.WarehouseQueries.updateStone(this, newAmount);
+			break;
+		case 1580001:
+			worked = DbManager.WarehouseQueries.updateTruesteel(this, newAmount);
+			break;
+		case 1580002:
+			worked = DbManager.WarehouseQueries.updateIron(this, newAmount);
+			break;
+		case 1580003:
+			worked = DbManager.WarehouseQueries.updateAdamant(this, newAmount);
+			break;
+		case 1580004:
+			worked = DbManager.WarehouseQueries.updateLumber(this, newAmount);
+			break;
+		case 1580005:
+			worked = DbManager.WarehouseQueries.updateOak(this, newAmount);
+			break;
+		case 1580006:
+			worked = DbManager.WarehouseQueries.updateBronzewood(this, newAmount);
+			break;
+		case 1580007:
+			worked = DbManager.WarehouseQueries.updateMandrake(this, newAmount);
+			break;
+		case 1580008:
+			worked = DbManager.WarehouseQueries.updateCoal(this, newAmount);
+			break;
+		case 1580009:
+			worked = DbManager.WarehouseQueries.updateAgate(this, newAmount);
+			break;
+		case 1580010:
+			worked = DbManager.WarehouseQueries.updateDiamond(this, newAmount);
+			break;
+		case 1580011:
+			worked = DbManager.WarehouseQueries.updateOnyx(this, newAmount);
+			break;
+		case 1580012:
+			worked = DbManager.WarehouseQueries.updateAzoth(this, newAmount);
+			break;
+		case 1580013:
+			worked = DbManager.WarehouseQueries.updateOrichalk(this, newAmount);
+			break;
+		case 1580014:
+			worked = DbManager.WarehouseQueries.updateAntimony(this, newAmount);
+			break;
+		case 1580015:
+			worked = DbManager.WarehouseQueries.updateSulfur(this, newAmount);
+			break;
+		case 1580016:
+			worked = DbManager.WarehouseQueries.updateQuicksilver(this, newAmount);
+			break;
+		case 1580017:
+			worked = DbManager.WarehouseQueries.updateGalvor(this, newAmount);
+			break;
+		case 1580018:
+			worked = DbManager.WarehouseQueries.updateWormwood(this, newAmount);
+			break;
+		case 1580019:
+			worked = DbManager.WarehouseQueries.updateObsidian(this, newAmount);
+			break;
+		case 1580020:
+			worked = DbManager.WarehouseQueries.updateBloodstone(this, newAmount);
+			break;
+		case 1580021:
+			worked = DbManager.WarehouseQueries.updateMithril(this, newAmount);
+			break;
+		}
+
+		if (!worked)
+			return false;
+
+        resources.put(ib,newAmount);
+
+		Resource resourceType;
+
+		if (resource.getItemBase().getType().equals(engine.Enum.ItemType.GOLD))
+			resourceType = Resource.GOLD;
+		else
+			resourceType = Resource.valueOf(resource.getItemBase().getName().toUpperCase());
+
+		if (transaction)
+			this.AddTransactionToWarehouse(pc.getObjectType(), pc.getObjectUUID(), TransactionType.DEPOSIT,resourceType, amount);
+
+		return true;
+	}
+
+	//for mine deposit
+	public synchronized boolean depositFromMine(Mine mine,ItemBase resource, int amount){
+
+        if (resource == null)
+			return false;
+
+		if (this.resources.get(resource) == null)
+			return false;
+
+		int oldAmount = resources.get(resource);
+		int newAmount = oldAmount + amount;
+
+		if (newAmount > Warehouse.getMaxResources().get(resource.getUUID()))
+			return false;
+
+		int itemID = resource.getUUID();
+		boolean worked = false;
+
+		switch(itemID){
+		case 7:
+			worked = DbManager.WarehouseQueries.updateGold(this, newAmount);
+			break;
+		case 1580000:
+			worked = DbManager.WarehouseQueries.updateStone(this, newAmount);
+			break;
+		case 1580001:
+			worked = DbManager.WarehouseQueries.updateTruesteel(this, newAmount);
+			break;
+		case 1580002:
+			worked = DbManager.WarehouseQueries.updateIron(this, newAmount);
+			break;
+		case 1580003:
+			worked = DbManager.WarehouseQueries.updateAdamant(this, newAmount);
+			break;
+		case 1580004:
+			worked = DbManager.WarehouseQueries.updateLumber(this, newAmount);
+			break;
+		case 1580005:
+			worked = DbManager.WarehouseQueries.updateOak(this, newAmount);
+			break;
+		case 1580006:
+			worked = DbManager.WarehouseQueries.updateBronzewood(this, newAmount);
+			break;
+		case 1580007:
+			worked = DbManager.WarehouseQueries.updateMandrake(this, newAmount);
+			break;
+		case 1580008:
+			worked = DbManager.WarehouseQueries.updateCoal(this, newAmount);
+			break;
+		case 1580009:
+			worked = DbManager.WarehouseQueries.updateAgate(this, newAmount);
+			break;
+		case 1580010:
+			worked = DbManager.WarehouseQueries.updateDiamond(this, newAmount);
+			break;
+		case 1580011:
+			worked = DbManager.WarehouseQueries.updateOnyx(this, newAmount);
+			break;
+		case 1580012:
+			worked = DbManager.WarehouseQueries.updateAzoth(this, newAmount);
+			break;
+		case 1580013:
+			worked = DbManager.WarehouseQueries.updateOrichalk(this, newAmount);
+			break;
+		case 1580014:
+			worked = DbManager.WarehouseQueries.updateAntimony(this, newAmount);
+			break;
+		case 1580015:
+			worked = DbManager.WarehouseQueries.updateSulfur(this, newAmount);
+			break;
+		case 1580016:
+			worked = DbManager.WarehouseQueries.updateQuicksilver(this, newAmount);
+			break;
+		case 1580017:
+			worked = DbManager.WarehouseQueries.updateGalvor(this, newAmount);
+			break;
+		case 1580018:
+			worked = DbManager.WarehouseQueries.updateWormwood(this, newAmount);
+			break;
+		case 1580019:
+			worked = DbManager.WarehouseQueries.updateObsidian(this, newAmount);
+			break;
+		case 1580020:
+			worked = DbManager.WarehouseQueries.updateBloodstone(this, newAmount);
+			break;
+		case 1580021:
+			worked = DbManager.WarehouseQueries.updateMithril(this, newAmount);
+			break;
+		}
+		if (!worked)
+			return false;
+
+		this.resources.put(resource, newAmount);
+		Resource resourceType;
+
+		if (resource.getUUID() == 7)
+			resourceType = Resource.GOLD;
+		else
+			resourceType = Resource.valueOf(resource.getName().toUpperCase());
+
+		if (mine != null)
+			this.AddTransactionToWarehouse(GameObjectType.Building, mine.getBuildingID(), TransactionType.MINE, resourceType, amount);
+
+		return true;
+	}
+
+	public synchronized boolean depositRealmTaxes(PlayerCharacter taxer, ItemBase ib,int amount){
+
+		if (ib == null)
+			return false;
+
+		if (this.resources.get(ib) == null)
+			return false;
+
+		int oldAmount = resources.get(ib);
+		int newAmount = oldAmount + amount;
+
+		if (newAmount > Warehouse.getMaxResources().get(ib.getUUID()))
+			return false;
+
+		int itemID = ib.getUUID();
+		boolean worked = false;
+
+		switch(itemID){
+		case 7:
+			worked = DbManager.WarehouseQueries.updateGold(this, newAmount);
+			break;
+		case 1580000:
+			worked = DbManager.WarehouseQueries.updateStone(this, newAmount);
+			break;
+		case 1580001:
+			worked = DbManager.WarehouseQueries.updateTruesteel(this, newAmount);
+			break;
+		case 1580002:
+			worked = DbManager.WarehouseQueries.updateIron(this, newAmount);
+			break;
+		case 1580003:
+			worked = DbManager.WarehouseQueries.updateAdamant(this, newAmount);
+			break;
+		case 1580004:
+			worked = DbManager.WarehouseQueries.updateLumber(this, newAmount);
+			break;
+		case 1580005:
+			worked = DbManager.WarehouseQueries.updateOak(this, newAmount);
+			break;
+		case 1580006:
+			worked = DbManager.WarehouseQueries.updateBronzewood(this, newAmount);
+			break;
+		case 1580007:
+			worked = DbManager.WarehouseQueries.updateMandrake(this, newAmount);
+			break;
+		case 1580008:
+			worked = DbManager.WarehouseQueries.updateCoal(this, newAmount);
+			break;
+		case 1580009:
+			worked = DbManager.WarehouseQueries.updateAgate(this, newAmount);
+			break;
+		case 1580010:
+			worked = DbManager.WarehouseQueries.updateDiamond(this, newAmount);
+			break;
+		case 1580011:
+			worked = DbManager.WarehouseQueries.updateOnyx(this, newAmount);
+			break;
+		case 1580012:
+			worked = DbManager.WarehouseQueries.updateAzoth(this, newAmount);
+			break;
+		case 1580013:
+			worked = DbManager.WarehouseQueries.updateOrichalk(this, newAmount);
+			break;
+		case 1580014:
+			worked = DbManager.WarehouseQueries.updateAntimony(this, newAmount);
+			break;
+		case 1580015:
+			worked = DbManager.WarehouseQueries.updateSulfur(this, newAmount);
+			break;
+		case 1580016:
+			worked = DbManager.WarehouseQueries.updateQuicksilver(this, newAmount);
+			break;
+		case 1580017:
+			worked = DbManager.WarehouseQueries.updateGalvor(this, newAmount);
+			break;
+		case 1580018:
+			worked = DbManager.WarehouseQueries.updateWormwood(this, newAmount);
+			break;
+		case 1580019:
+			worked = DbManager.WarehouseQueries.updateObsidian(this, newAmount);
+			break;
+		case 1580020:
+			worked = DbManager.WarehouseQueries.updateBloodstone(this, newAmount);
+			break;
+		case 1580021:
+			worked = DbManager.WarehouseQueries.updateMithril(this, newAmount);
+			break;
+		}
+
+		if (!worked)
+			return false;
+
+		this.resources.put(ib, newAmount);
+		Resource resourceType;
+
+		if (ib.getUUID() == 7)
+			resourceType = Resource.GOLD;
+		else
+			resourceType = Resource.valueOf(ib.getName().toUpperCase());
+		
+		this.AddTransactionToWarehouse(taxer.getObjectType(), taxer.getObjectUUID(), TransactionType.TAXRESOURCEDEPOSIT, resourceType, amount);
+
+		return true;
+	}
+
+	public synchronized boolean depositProfitTax(ItemBase ib,int amount,Building building){
+
+		if (ib == null)
+			return false;
+
+		if (this.resources.get(ib) == null)
+			return false;
+
+		int oldAmount = resources.get(ib);
+		int newAmount = oldAmount + amount;
+
+		if (newAmount > Warehouse.getMaxResources().get(ib.getUUID()))
+			return false;
+
+		int itemID = ib.getUUID();
+		boolean worked = false;
+
+		switch(itemID){
+		case 7:
+			worked = DbManager.WarehouseQueries.updateGold(this, newAmount);
+			break;
+		case 1580000:
+			worked = DbManager.WarehouseQueries.updateStone(this, newAmount);
+			break;
+		case 1580001:
+			worked = DbManager.WarehouseQueries.updateTruesteel(this, newAmount);
+			break;
+		case 1580002:
+			worked = DbManager.WarehouseQueries.updateIron(this, newAmount);
+			break;
+		case 1580003:
+			worked = DbManager.WarehouseQueries.updateAdamant(this, newAmount);
+			break;
+		case 1580004:
+			worked = DbManager.WarehouseQueries.updateLumber(this, newAmount);
+			break;
+		case 1580005:
+			worked = DbManager.WarehouseQueries.updateOak(this, newAmount);
+			break;
+		case 1580006:
+			worked = DbManager.WarehouseQueries.updateBronzewood(this, newAmount);
+			break;
+		case 1580007:
+			worked = DbManager.WarehouseQueries.updateMandrake(this, newAmount);
+			break;
+		case 1580008:
+			worked = DbManager.WarehouseQueries.updateCoal(this, newAmount);
+			break;
+		case 1580009:
+			worked = DbManager.WarehouseQueries.updateAgate(this, newAmount);
+			break;
+		case 1580010:
+			worked = DbManager.WarehouseQueries.updateDiamond(this, newAmount);
+			break;
+		case 1580011:
+			worked = DbManager.WarehouseQueries.updateOnyx(this, newAmount);
+			break;
+		case 1580012:
+			worked = DbManager.WarehouseQueries.updateAzoth(this, newAmount);
+			break;
+		case 1580013:
+			worked = DbManager.WarehouseQueries.updateOrichalk(this, newAmount);
+			break;
+		case 1580014:
+			worked = DbManager.WarehouseQueries.updateAntimony(this, newAmount);
+			break;
+		case 1580015:
+			worked = DbManager.WarehouseQueries.updateSulfur(this, newAmount);
+			break;
+		case 1580016:
+			worked = DbManager.WarehouseQueries.updateQuicksilver(this, newAmount);
+			break;
+		case 1580017:
+			worked = DbManager.WarehouseQueries.updateGalvor(this, newAmount);
+			break;
+		case 1580018:
+			worked = DbManager.WarehouseQueries.updateWormwood(this, newAmount);
+			break;
+		case 1580019:
+			worked = DbManager.WarehouseQueries.updateObsidian(this, newAmount);
+			break;
+		case 1580020:
+			worked = DbManager.WarehouseQueries.updateBloodstone(this, newAmount);
+			break;
+		case 1580021:
+			worked = DbManager.WarehouseQueries.updateMithril(this, newAmount);
+			break;
+		}
+
+		if (!worked)
+			return false;
+
+		this.resources.put(ib, newAmount);
+		Resource resourceType;
+
+		if (ib.getUUID() == 7)
+			resourceType = Resource.GOLD;
+		else
+			resourceType = Resource.valueOf(ib.getName().toUpperCase());
+
+		if (building != null)
+			this.AddTransactionToWarehouse(GameObjectType.Building, building.getObjectUUID(), TransactionType.DEPOSIT, resourceType, amount);
+
+		return true;
+	}
+	public synchronized boolean withdraw(PlayerCharacter pc, ItemBase ib, int amount, boolean addToInventory, boolean transaction){
+
+	    if (pc == null)
+			return false;
+
+		if (ib == null)
+			return false;
+
+		if (this.resources.get(ib) == null)
+			return false;
+
+		if (amount <= 0)
+			return false;
+
+		CharacterItemManager itemMan = pc.getCharItemManager();
+
+		if(itemMan == null)
+			return false;
+
+		if (addToInventory)
+			if(!itemMan.hasRoomInventory(ib.getWeight())) {
+				ChatManager.chatSystemInfo(pc, "You can not carry any more of that item.");
+				return false;
+			}
+
+		if (addToInventory && ib.getUUID() == ItemBase.GOLD_BASE_ID){
+			if (pc.getCharItemManager().getGoldInventory().getNumOfItems() + amount > MBServerStatics.PLAYER_GOLD_LIMIT){
+				return false;
+			}
+
+			if (pc.getCharItemManager().getGoldInventory().getNumOfItems() + amount  < 0)
+				return false;
+		}
+		int oldAmount = this.resources.get(ib);
+
+		if (oldAmount < amount)
+			return false;
+
+		int hashID = ib.getHashID();
+		int newAmount = oldAmount - amount;
+
+		boolean worked = false;
+
+		switch(hashID){
+		case 2308551:
+			worked = DbManager.WarehouseQueries.updateGold(this,newAmount);
+			break;
+		case 74856115:
+			worked = DbManager.WarehouseQueries.updateStone(this, newAmount);
+			break;
+		case -317484979:
+			worked = DbManager.WarehouseQueries.updateTruesteel(this, newAmount);
+			break;
+		case 2504297:
+			worked = DbManager.WarehouseQueries.updateIron(this, newAmount);
+			break;
+		case -1741189964:
+			worked = DbManager.WarehouseQueries.updateAdamant(this, newAmount);
+			break;
+		case -1603256692:
+			worked = DbManager.WarehouseQueries.updateLumber(this, newAmount);
+			break;
+		case 74767:
+			worked = DbManager.WarehouseQueries.updateOak(this, newAmount);
+			break;
+		case 1334770447:
+			worked = DbManager.WarehouseQueries.updateBronzewood(this, newAmount);
+			break;
+		case 1191391799:
+			worked = 	DbManager.WarehouseQueries.updateMandrake(this, newAmount);
+			break;
+		case 2559427:
+			worked = DbManager.WarehouseQueries.updateCoal(this, newAmount);
+			break;
+		case 75173057:
+			worked = DbManager.WarehouseQueries.updateAgate(this, newAmount);
+			break;
+		case -1730704107:
+			worked = DbManager.WarehouseQueries.updateDiamond(this, newAmount);
+			break;
+		case 2977263:
+			worked = DbManager.WarehouseQueries.updateOnyx(this, newAmount);
+			break;
+		case 78329697:
+			worked = DbManager.WarehouseQueries.updateAzoth(this, newAmount);
+			break;
+		case -2036290524:
+			worked = DbManager.WarehouseQueries.updateOrichalk(this, newAmount);
+			break;
+		case 452320058:
+			worked = DbManager.WarehouseQueries.updateAntimony(this, newAmount);
+			break;
+		case -1586349421:
+			worked = DbManager.WarehouseQueries.updateSulfur(this, newAmount);
+			break;
+		case -472884509:
+			worked = DbManager.WarehouseQueries.updateQuicksilver(this, newAmount);
+			break;
+		case -1596311545:
+			worked = DbManager.WarehouseQueries.updateGalvor(this, newAmount);
+			break;
+		case 1532478436:
+			worked = DbManager.WarehouseQueries.updateWormwood(this, newAmount);
+			break;
+		case -697973233:
+			worked = DbManager.WarehouseQueries.updateObsidian(this, newAmount);
+			break;
+		case -1569826353:
+			worked = DbManager.WarehouseQueries.updateBloodstone(this, newAmount);
+			break;
+		case -1761257186:
+			worked = DbManager.WarehouseQueries.updateMithril(this, newAmount);
+			break;
+		}
+		if (!worked)
+			return false;
+
+		this.resources.put(ib, newAmount);
+
+		if (addToInventory){
+			if (ib.getUUID() == 7){
+
+				itemMan.addGoldToInventory(amount, false);
+				UpdateGoldMsg ugm = new UpdateGoldMsg(pc);
+				ugm.configure();
+				Dispatch dispatch = Dispatch.borrow(pc, ugm);
+				DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+
+				itemMan.updateInventory();
+			}else{
+				boolean itemWorked = false;
+				Item item = new Item( ib, pc.getObjectUUID(), OwnerType.PlayerCharacter, (byte) 0, (byte) 0,
+						(short) 1, (short) 1, true, false,ItemContainerType.INVENTORY, (byte) 0,
+						new ArrayList<>(),"");
+				item.setNumOfItems(amount);
+				item.containerType = Enum.ItemContainerType.INVENTORY;
+
+				try {
+					item = DbManager.ItemQueries.ADD_ITEM(item);
+					itemWorked = true;
+				} catch (Exception e) {
+					Logger.error(e);
+				}
+				if (itemWorked) {
+					itemMan.addItemToInventory(item);
+					itemMan.updateInventory();
+				}
+			}
+		}
+		Resource resourceType;
+
+		if (ib.getUUID() == 7)
+			resourceType = Resource.GOLD;
+		else
+			resourceType = Resource.valueOf(ib.getName().toUpperCase());
+
+		if (transaction)
+			this.AddTransactionToWarehouse(pc.getObjectType(), pc.getObjectUUID(), TransactionType.WITHDRAWL, resourceType, amount);
+
+		return true;
+	}
+
+	public synchronized boolean withdraw(NPC npc, ItemBase ib, int amount, boolean addToInventory, boolean transaction){
+
+	    if (npc == null)
+			return false;
+
+		if (ib == null)
+			return false;
+
+		if (this.resources.get(ib) == null)
+			return false;
+
+		if (amount <= 0)
+			return false;
+
+		int oldAmount = this.resources.get(ib);
+
+		if (oldAmount < amount)
+			return false;
+
+		int hashID = ib.getHashID();
+		int newAmount = oldAmount - amount;
+		boolean worked = false;
+
+		switch(hashID){
+		case 2308551:
+			worked = DbManager.WarehouseQueries.updateGold(this,newAmount);
+			break;
+		case 74856115:
+			worked = DbManager.WarehouseQueries.updateStone(this, newAmount);
+			break;
+		case -317484979:
+			worked = DbManager.WarehouseQueries.updateTruesteel(this, newAmount);
+			break;
+		case 2504297:
+			worked = DbManager.WarehouseQueries.updateIron(this, newAmount);
+			break;
+		case -1741189964:
+			worked = DbManager.WarehouseQueries.updateAdamant(this, newAmount);
+			break;
+		case -1603256692:
+			worked = DbManager.WarehouseQueries.updateLumber(this, newAmount);
+			break;
+		case 74767:
+			worked = DbManager.WarehouseQueries.updateOak(this, newAmount);
+			break;
+		case 1334770447:
+			worked = DbManager.WarehouseQueries.updateBronzewood(this, newAmount);
+			break;
+		case 1191391799:
+			worked = 	DbManager.WarehouseQueries.updateMandrake(this, newAmount);
+			break;
+		case 2559427:
+			worked = DbManager.WarehouseQueries.updateCoal(this, newAmount);
+			break;
+		case 75173057:
+			worked = DbManager.WarehouseQueries.updateAgate(this, newAmount);
+			break;
+		case -1730704107:
+			worked = DbManager.WarehouseQueries.updateDiamond(this, newAmount);
+			break;
+		case 2977263:
+			worked = DbManager.WarehouseQueries.updateOnyx(this, newAmount);
+			break;
+		case 78329697:
+			worked = DbManager.WarehouseQueries.updateAzoth(this, newAmount);
+			break;
+		case -2036290524:
+			worked = DbManager.WarehouseQueries.updateOrichalk(this, newAmount);
+			break;
+		case 452320058:
+			worked = DbManager.WarehouseQueries.updateAntimony(this, newAmount);
+			break;
+		case -1586349421:
+			worked = DbManager.WarehouseQueries.updateSulfur(this, newAmount);
+			break;
+		case -472884509:
+			worked = DbManager.WarehouseQueries.updateQuicksilver(this, newAmount);
+			break;
+		case -1596311545:
+			worked = DbManager.WarehouseQueries.updateGalvor(this, newAmount);
+			break;
+		case 1532478436:
+			worked = DbManager.WarehouseQueries.updateWormwood(this, newAmount);
+			break;
+		case -697973233:
+			worked = DbManager.WarehouseQueries.updateObsidian(this, newAmount);
+			break;
+		case -1569826353:
+			worked = DbManager.WarehouseQueries.updateBloodstone(this, newAmount);
+			break;
+		case -1761257186:
+			worked = DbManager.WarehouseQueries.updateMithril(this, newAmount);
+			break;
+		}
+
+		if (!worked)
+			return false;
+
+		this.resources.put(ib, newAmount);
+		Resource resourceType;
+
+		if (ib.getUUID() == 7)
+			resourceType = Resource.GOLD;
+		else
+			resourceType = Resource.valueOf(ib.getName().toUpperCase());
+
+		if (transaction)
+			this.AddTransactionToWarehouse(npc.getObjectType(), npc.getObjectUUID(), TransactionType.WITHDRAWL, resourceType, amount);
+
+		return true;
+	}
+
+	public synchronized boolean transferResources(PlayerCharacter taxer, TaxResourcesMsg msg, ArrayList<Integer> realmResources, float taxPercent, Warehouse toWarehouse){
+
+		for (int ibID: realmResources){
+
+			ItemBase ib = ItemBase.getItemBase(ibID);
+
+			if (ib == null)
+				return false;
+
+			if (this.resources.get(ib) == null)
+				return false;
+
+			int amount = (int) (this.resources.get(ib) * taxPercent);
+
+			if (amount <= 0){
+				msg.getResources().put(ib.getHashID(), 0);
+				continue;
+			}
+
+			int oldAmount = this.resources.get(ib);
+
+			if (oldAmount < amount)
+				amount = oldAmount;
+
+			int hashID = ib.getHashID();
+			int newAmount = oldAmount - amount;
+
+			if (newAmount < amount)
+				continue;
+
+			boolean worked = false;
+
+			switch(hashID){
+			case 2308551:
+				worked = DbManager.WarehouseQueries.updateGold(this,newAmount);
+				break;
+			case 74856115:
+				worked = DbManager.WarehouseQueries.updateStone(this, newAmount);
+				break;
+			case -317484979:
+				worked = DbManager.WarehouseQueries.updateTruesteel(this, newAmount);
+				break;
+			case 2504297:
+				worked = DbManager.WarehouseQueries.updateIron(this, newAmount);
+				break;
+			case -1741189964:
+				worked = DbManager.WarehouseQueries.updateAdamant(this, newAmount);
+				break;
+			case -1603256692:
+				worked = DbManager.WarehouseQueries.updateLumber(this, newAmount);
+				break;
+			case 74767:
+				worked = DbManager.WarehouseQueries.updateOak(this, newAmount);
+				break;
+			case 1334770447:
+				worked = DbManager.WarehouseQueries.updateBronzewood(this, newAmount);
+				break;
+			case 1191391799:
+				worked = 	DbManager.WarehouseQueries.updateMandrake(this, newAmount);
+				break;
+			case 2559427:
+				worked = DbManager.WarehouseQueries.updateCoal(this, newAmount);
+				break;
+			case 75173057:
+				worked = DbManager.WarehouseQueries.updateAgate(this, newAmount);
+				break;
+			case -1730704107:
+				worked = DbManager.WarehouseQueries.updateDiamond(this, newAmount);
+				break;
+			case 2977263:
+				worked = DbManager.WarehouseQueries.updateOnyx(this, newAmount);
+				break;
+			case 78329697:
+				worked = DbManager.WarehouseQueries.updateAzoth(this, newAmount);
+				break;
+			case -2036290524:
+				worked = DbManager.WarehouseQueries.updateOrichalk(this, newAmount);
+				break;
+			case 452320058:
+				worked = DbManager.WarehouseQueries.updateAntimony(this, newAmount);
+				break;
+			case -1586349421:
+				worked = DbManager.WarehouseQueries.updateSulfur(this, newAmount);
+				break;
+			case -472884509:
+				worked = DbManager.WarehouseQueries.updateQuicksilver(this, newAmount);
+				break;
+			case -1596311545:
+				worked = DbManager.WarehouseQueries.updateGalvor(this, newAmount);
+				break;
+			case 1532478436:
+				worked = DbManager.WarehouseQueries.updateWormwood(this, newAmount);
+				break;
+			case -697973233:
+				worked = DbManager.WarehouseQueries.updateObsidian(this, newAmount);
+				break;
+			case -1569826353:
+				worked = DbManager.WarehouseQueries.updateBloodstone(this, newAmount);
+				break;
+			case -1761257186:
+				worked = DbManager.WarehouseQueries.updateMithril(this, newAmount);
+				break;
+			}
+
+			if (!worked){
+				msg.getResources().put(ib.getHashID(), 0);
+				continue;
+			}
+			
+			msg.getResources().put(ib.getHashID(), amount);
+				
+			this.resources.put(ib, newAmount);
+			toWarehouse.depositRealmTaxes(taxer,ib, amount);
+			Resource resourceType;
+
+			if (ib.getUUID() == 7)
+				resourceType = Resource.GOLD;
+			else
+				resourceType = Resource.valueOf(ib.getName().toUpperCase());
+			
+			this.AddTransactionToWarehouse(taxer.getObjectType(), taxer.getObjectUUID(), TransactionType.TAXRESOURCE, resourceType, amount);
+
+		}
+		return true;
+	}
+
+	public synchronized boolean loot(PlayerCharacter pc, ItemBase ib, int amount, boolean addToInventory){
+
+	    if (pc == null)
+			return false;
+
+		if (ib == null)
+			return false;
+
+		if (this.resources.get(ib) == null)
+			return false;
+
+		if (amount <= 0)
+			return false;
+
+		CharacterItemManager itemMan = pc.getCharItemManager();
+
+		if(itemMan == null)
+			return false;
+
+		if(!itemMan.hasRoomInventory(ib.getWeight())) {
+			ChatManager.chatSystemInfo(pc, "You can not carry any more of that item.");
+			return false;
+		}
+
+		int oldAmount = this.resources.get(ib);
+
+		if (oldAmount < amount)
+			return false;
+
+		int newAmount = oldAmount - amount;
+
+		this.resources.put(ib, newAmount);
+
+		if (addToInventory){
+			if (ib.getUUID() == 7){
+
+				itemMan.addGoldToInventory(amount, false);
+				UpdateGoldMsg ugm = new UpdateGoldMsg(pc);
+				ugm.configure();
+				Dispatch dispatch = Dispatch.borrow(pc, ugm);
+				DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+
+				itemMan.updateInventory();
+			}else{
+				boolean itemWorked = false;
+				Item item = new Item(ib, pc.getObjectUUID(), OwnerType.PlayerCharacter, (byte) 0, (byte) 0,
+						(short) 1, (short) 1, true, false,ItemContainerType.INVENTORY, (byte) 0,
+                        new ArrayList<>(),"");
+				item.setNumOfItems(amount);
+				item.containerType = Enum.ItemContainerType.INVENTORY;
+
+				try {
+					item = DbManager.ItemQueries.ADD_ITEM(item);
+					itemWorked = true;
+				} catch (Exception e) {
+					Logger.error(e);
+				}
+				if (itemWorked) {
+					itemMan.addItemToInventory(item);
+					itemMan.updateInventory();
+				}
+			}
+		}
+
+		return true;
+	}
+
+	@Override
+	public void updateDatabase() {
+		// TODO Auto-generated method stub
+
+	}
+	@Override
+	public void runAfterLoad() {
+
+		try{
+			Building warehouseBuilding = BuildingManager.getBuilding(this.buildingUID);
+            Logger.info("configuring warehouse " + UID + " for city "  + warehouseBuilding.getCity().getCityName()  + " structure UUID " + this.buildingUID);
+
+			//Building is gone, but Warehouse still in DB?? Should never happen, sanity check anyway.
+			if (warehouseBuilding == null){
+				Logger.error( "Failed to load Building for Warehouse");
+				return;
+			}
+
+			Zone cityZone = warehouseBuilding.getParentZone();
+
+			if (cityZone == null){
+				Logger.error( "Failed to load Zone for Warehouse with UUID " + this.getObjectUUID());
+				return;
+			}
+
+			City city = City.getCity(cityZone.getPlayerCityUUID());
+
+			if (city == null){
+				Logger.error( "Failed to load City for Warehouse with UUID " + this.getObjectUUID());
+				return;
+			}
+
+			warehouseByBuildingUUID.put(this.buildingUID, this);
+			city.setWarehouseBuildingID(this.buildingUID);
+		}catch(Exception E){
+			Logger.info(this.getObjectUUID() + " failed");
+
+		}
+	}
+
+	public boolean isEmpty(){
+		int amount = 0;
+		for(ItemBase ib: ItemBase.getResourceList()){
+			if (amount > 0)
+				return false;
+            amount += resources.get(ib);
+		}
+		return true;
+	}
+
+	public int getBuildingUID() {
+		return buildingUID;
+	}
+
+	public void loadAllTransactions(){
+		this.transactions = DbManager.WarehouseQueries.GET_TRANSACTIONS_FOR_WAREHOUSE(this.buildingUID);
+	}
+
+	public  boolean AddTransactionToWarehouse(GameObjectType targetType, int targetUUID, TransactionType transactionType,Resource resource, int amount){
+		
+		
+		if (!DbManager.WarehouseQueries.CREATE_TRANSACTION(this.buildingUID, targetType, targetUUID, transactionType, resource, amount, DateTime.now()))
+			return false;
+		
+		Transaction transaction = new Transaction(this.buildingUID,targetType,targetUUID,transactionType,resource,amount, DateTime.now());
+		this.transactions.add(transaction);
+		return true;
+	}
+
+	public ArrayList<Transaction> getTransactions() {
+		return transactions;
+	}
+
+	public boolean isAboveCap(ItemBase ib, int deposit){
+		int newAmount = this.resources.get(ib) + deposit;
+        return newAmount > Warehouse.getMaxResources().get(ib.getUUID());
+
+    }
+
+    public boolean isResourceLocked(ItemBase itemBase) {
+
+        Enum.ResourceType resourceType;
+
+        resourceType = Enum.ResourceType.resourceLookup.get(itemBase.getUUID());
+
+        return resourceType.elementOf(this.lockedResourceTypes);
+    }
+}
diff --git a/src/engine/objects/Zone.java b/src/engine/objects/Zone.java
new file mode 100644
index 00000000..eb9e5792
--- /dev/null
+++ b/src/engine/objects/Zone.java
@@ -0,0 +1,512 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.objects;
+
+import engine.Enum;
+import engine.InterestManagement.HeightMap;
+import engine.db.archive.DataWarehouse;
+import engine.gameManager.DbManager;
+import engine.gameManager.ZoneManager;
+import engine.math.Bounds;
+import engine.math.Vector2f;
+import engine.math.Vector3fImmutable;
+import engine.net.ByteBufferWriter;
+import engine.server.MBServerStatics;
+import org.pmw.tinylog.Logger;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class Zone extends AbstractGameObject {
+
+	private final int playerCityID;
+	private final String zoneName;
+	private final float xCoord;
+	private final float zCoord;
+	private final float yCoord;
+	public float absX = 0.0f;
+	public float absY = 0.0f;
+	public float absZ = 0.0f;
+	private final int loadNum;
+	private final byte safeZone;
+	private final String Icon1;
+	private final String Icon2;
+	private final String Icon3;
+	private ArrayList<Zone> nodes = null;
+	private int parentZoneID;
+	private Zone parent = null;
+	private Bounds bounds;
+	private boolean isNPCCity = false;
+	private boolean isPlayerCity = false;
+	private String hash;
+	private int minLvl;
+	private int maxLvl;
+
+	private float worldAltitude = 0;
+
+	private float seaLevel = 0;
+	public final Set<Building> zoneBuildingSet = Collections.newSetFromMap(new ConcurrentHashMap<>());
+	public final Set<NPC> zoneNPCSet = Collections.newSetFromMap(new ConcurrentHashMap<>());
+	public final Set<Mob> zoneMobSet = Collections.newSetFromMap(new ConcurrentHashMap<>());
+
+	/**
+	 * ResultSet Constructor
+	 */
+	public Zone(ResultSet rs) throws SQLException {
+		super(rs);
+		this.parentZoneID = rs.getInt("parent");
+		this.playerCityID = rs.getInt("isPlayerCity");
+		this.isPlayerCity = this.playerCityID != 0;
+		this.zoneName = rs.getString("Name");
+		this.xCoord = rs.getFloat("XCoord");
+		this.zCoord = rs.getFloat("ZCoord");
+		this.yCoord = rs.getFloat("YOffset");
+		this.loadNum = rs.getInt("LoadNum");
+		this.safeZone = rs.getByte("SafeZone");
+		this.Icon1 = rs.getString("Icon1");
+		this.Icon2 = rs.getString("Icon2");
+		this.Icon3 = rs.getString("Icon3");
+		this.hash = rs.getString("hash");
+
+		this.minLvl = rs.getInt("minLvl");
+		this.maxLvl = rs.getInt("maxLvl");
+
+		//this needs to be here specifically for new zones created after server boot (e.g. player city zones)
+		Zone parentZone = ZoneManager.getZoneByUUID(parentZoneID);
+
+		this.setParent(parentZone);
+
+		if (this.minLvl == 0 && parentZone != null){
+			this.minLvl = parentZone.minLvl;
+			this.maxLvl = parentZone.maxLvl;
+		}
+
+		if (parentZone != null)
+			parentZone.addNode(this);
+
+		// If zone doesn't yet hava a hash then write it back to the zone table
+
+		if (hash == null)
+			setHash();
+		
+
+	}
+
+	/* Method sets a default value for player cities
+	 * otherwise using values derived from the loadnum
+	 * field in the obj_zone database table.
+	 */
+	public void setBounds() {
+
+		float halfExtentX;
+		float halfExtentY;
+
+		// Set initial bounds object
+
+		this.bounds = Bounds.borrow();
+
+		// Player cities are assigned default value
+
+		if (this.loadNum == 0) {
+			bounds.setBounds(new Vector2f(this.absX, this.absZ), new Vector2f(Enum.CityBoundsType.ZONE.extents, Enum.CityBoundsType.ZONE.extents), 0.0f);
+			return;
+		}
+
+		// All other zones have bounding boxes loaded from database
+		ResultSet rs = DbManager.ZoneQueries.GET_ZONE_EXTENTS(this.loadNum);
+		boolean loaded = false;
+
+		if (rs != null)
+			try {
+				if (rs.next()) {
+					halfExtentX = rs.getFloat("xRadius");
+					halfExtentY = rs.getFloat("zRadius");
+					this.bounds.setBounds(new Vector2f(this.absX, this.absZ), new Vector2f(halfExtentX, halfExtentY), 0.0f);
+					loaded = true;
+				}
+
+			} catch (SQLException e) {
+				Logger.error("SQLException: " + e.getMessage());
+			}
+
+		if (!loaded) {
+
+			// Default to Citygrid size on error
+
+			bounds.setBounds(new Vector2f(this.absX, this.absZ), new Vector2f(Enum.CityBoundsType.ZONE.extents, Enum.CityBoundsType.ZONE.extents), 0.0f);
+		}
+
+	}
+
+	/*
+	 * Getters
+	 */
+	public int getPlayerCityUUID() {
+		if (this.playerCityID == 0)
+			return 0;
+		return this.playerCityID;
+	}
+
+	public String getName() {
+		return zoneName;
+	}
+
+	public float getXCoord() {
+		return xCoord;
+	}
+
+	public float getYCoord() {
+		return yCoord;
+	}
+
+	public float getZCoord() {
+		return zCoord;
+	}
+
+	public int getLoadNum() {
+		return loadNum;
+	}
+
+	public int getLoadNumClient() {
+		return loadNum;
+	}
+
+	public byte getSafeZone() {
+		return safeZone;
+	}
+
+	public String getIcon1() {
+		return Icon1;
+	}
+
+	public String getIcon2() {
+		return Icon2;
+	}
+
+	public String getIcon3() {
+		return Icon3;
+	}
+
+	public void setParent(final Zone value) {
+
+		this.parent = value;
+		this.parentZoneID = (this.parent != null) ? this.parent.getObjectUUID() : 0;
+
+		if (this.parent != null) {
+			this.absX = this.xCoord + parent.absX;
+			this.absY = this.yCoord + parent.absY;
+			this.absZ = this.zCoord + parent.absZ;
+
+			if (this.minLvl == 0 || this.maxLvl == 0){
+				this.minLvl = this.parent.minLvl;
+				this.maxLvl = this.parent.maxLvl;
+			}
+		} else {  //only the Sea Floor zone does not have a parent
+			this.absX = this.xCoord;
+			this.absY = MBServerStatics.SEA_FLOOR_ALTITUDE;
+			this.absZ = this.zCoord;
+		}
+
+		// Zone AABB is set here as it's coordinate space is world requiring a parent.
+		this.setBounds();
+
+		if (this.getHeightMap() != null && this.getHeightMap().getSeaLevel() != 0)
+			this.seaLevel = this.getHeightMap().getSeaLevel();
+
+	}
+
+	public void generateWorldAltitude(){
+
+		if (ZoneManager.getSeaFloor().getObjectUUID() == this.getObjectUUID()){
+			this.worldAltitude = MBServerStatics.SEA_FLOOR_ALTITUDE;
+			return;
+		}
+
+		Zone parentZone = this.parent;
+
+		Zone currentZone = this;
+		float altitude = this.absY;
+
+		//seafloor only zone with null parent;
+
+		while(parentZone != ZoneManager.getSeaFloor()){
+
+			if(parentZone.getHeightMap() != null){
+
+				Vector2f zoneLoc = ZoneManager.worldToZoneSpace(currentZone.getLoc(), parentZone);
+				altitude += parentZone.getHeightMap().getInterpolatedTerrainHeight(zoneLoc);
+
+			}
+			currentZone = parentZone;
+			parentZone = parentZone.parent;
+
+		}
+
+		this.worldAltitude = altitude;
+
+		if (ZoneManager.getSeaFloor().equals(this))
+			this.seaLevel = 0;
+		else if
+		(this.getHeightMap() != null && this.getHeightMap().getSeaLevel() == 0){
+            this.seaLevel = this.parent.seaLevel;
+
+		}else if (this.getHeightMap() != null){
+			this.seaLevel = this.worldAltitude + this.getHeightMap().getSeaLevel();
+		}else {
+            this.seaLevel = this.parent.seaLevel;
+        }
+
+	}
+
+	public Zone getParent() {
+		return this.parent;
+	}
+
+	public float getAbsX() {
+		return this.absX;
+	}
+
+	public float getAbsY() {
+		return this.absY;
+	}
+
+	public float getAbsZ() {
+		return this.absZ;
+	}
+
+	public boolean isMacroZone() {
+
+		// Player cities are not considered a macrozone
+		// although their parent is always a continent.
+
+		if (this.isPlayerCity == true)
+			return false;
+
+		if (this.parent == null)
+			return false;
+
+		return (this.parent.isContininent() == true);
+	}
+
+	public boolean isNPCCity() {
+		return this.isNPCCity;
+	}
+
+	public boolean isPlayerCity() {
+		return this.isPlayerCity;
+	}
+
+	public void setNPCCity(boolean value) {
+		this.isNPCCity = value;
+	}
+
+	public void setPlayerCity(boolean value) {
+		this.isPlayerCity = value;
+	}
+
+	public Vector3fImmutable getLoc() {
+		return new Vector3fImmutable(this.absX, this.absY, this.absZ);
+	}
+
+	public int getParentZoneID() {
+		return this.parentZoneID;
+	}
+
+	public ArrayList<Zone> getNodes() {
+		if (this.nodes == null) {
+			this.nodes = DbManager.ZoneQueries.GET_MAP_NODES(super.getObjectUUID());
+
+			//Add reverse lookup for child->parent
+			if (this.nodes != null)
+				for (Zone zone : this.nodes) {
+					zone.setParent(this);
+				}
+		}
+
+		return nodes;
+	}
+
+	public void addNode(Zone child) {
+		this.nodes.add(child);
+	}
+
+	public void removeNode(Zone child) {
+		this.nodes.remove(child);
+	}
+
+	/*
+	 * Serializing
+	 */
+
+	
+	public static void serializeForClientMsg(Zone zone,ByteBufferWriter writer) {
+
+		if (zone.loadNum == 0 && zone.playerCityID == 0)
+			Logger.warn( "Warning! WorldServerMap with ID " + zone.getObjectUUID() + " has a loadnum of 0 (player city) and no city linked. This will probably crash the client!");
+
+		// Player City Terraform values serialized here.
+
+		if (zone.playerCityID > 0) {
+			writer.put((byte) 1); // Player City - True
+			writer.putFloat(Enum.CityBoundsType.TERRAFORM.extents);
+			writer.putFloat(Enum.CityBoundsType.TERRAFORM.extents);
+		} else
+			writer.put((byte) 0); // Player City - False
+
+		writer.putFloat(zone.xCoord);
+		writer.putFloat(zone.zCoord);
+		writer.putFloat(zone.yCoord);
+
+		writer.putInt(0);
+		writer.putInt(0);
+		writer.putInt(zone.loadNum);
+
+		if (zone.playerCityID > 0) {
+			City k = City.getCity(zone.playerCityID);
+
+			if (k != null) {
+				writer.putInt(k.getObjectType().ordinal());
+				writer.putInt(k.getObjectUUID());
+			}
+			else
+				writer.putLong(0x0);
+		} else {
+			writer.putInt(zone.getObjectType().ordinal());
+			writer.putInt(zone.getObjectUUID());
+		}
+		writer.putInt(zone.nodes.size());
+
+		City city = City.getCity(zone.playerCityID);
+
+		if (city != null)
+			writer.putString(city.getCityName());
+		else
+			writer.putString(zone.zoneName);
+		writer.put(zone.safeZone);
+		writer.putString(zone.Icon1);
+		writer.putString(zone.Icon2);
+		writer.putString(zone.Icon3);
+		writer.put((byte) 0); // Pad
+
+		for (Zone child : zone.nodes) {
+			Zone.serializeForClientMsg(child,writer);
+		}
+	}
+
+	@Override
+	public void updateDatabase() {
+		// TODO Auto-generated method stub
+	}
+
+	public Zone findRuinedCityZone(float centerX, float centerY, float centerZ){
+		Bounds cityBounds;
+		cityBounds = Bounds.borrow();
+		Zone RuinedZone = null;
+		cityBounds.setBounds(new Vector2f(centerX, centerZ), new Vector2f(Enum.CityBoundsType.ZONE.extents, Enum.CityBoundsType.ZONE.extents), 0.0f);
+		Zone currentZone = ZoneManager.findSmallestZone(new Vector3fImmutable(centerX, centerY, centerZ));
+		if (currentZone != null)
+			if (this.getObjectUUID() == currentZone.getObjectUUID()){
+
+				if (currentZone.getPlayerCityUUID() != 0){
+					//null player city? skip..
+					if (City.GetCityFromCache(currentZone.getPlayerCityUUID()) == null)
+						RuinedZone = null;
+					else	//no tol? skip...
+						if (City.GetCityFromCache(currentZone.getPlayerCityUUID()).getTOL() == null)
+							RuinedZone = null;
+						else
+							if (City.GetCityFromCache(currentZone.getPlayerCityUUID()).getTOL().getRank() == -1)
+								RuinedZone = currentZone;
+					//Dead tree? skip.
+					cityBounds.release();
+					return RuinedZone;
+				}
+			}
+
+		for (Zone zone : this.getNodes()) {
+
+			if (zone == this)
+				continue;
+
+			if (zone.isContininent() && zone.getPlayerCityUUID() == 0)
+				continue;
+
+			if (zone.getPlayerCityUUID() != 0){
+				//null player city? skip..
+				if (City.GetCityFromCache(zone.getPlayerCityUUID()) == null)
+					continue;
+				//no tol? skip...
+				if (City.GetCityFromCache(zone.getPlayerCityUUID()).getTOL() == null)
+					continue;
+
+				//Dead tree? skip.
+				if (Bounds.collide(zone.bounds, cityBounds, 0.0f)){
+					if (City.GetCityFromCache(zone.getPlayerCityUUID()).getTOL().getRank() == -1){
+						RuinedZone = zone;
+						break;
+					}
+				}
+			}
+		}
+		cityBounds.release();
+		return RuinedZone;
+	}
+
+	public boolean isContininent() {
+
+		if (this.parent == null)
+			return false;
+
+		return this.parent.equals(ZoneManager.getSeaFloor());
+	}
+
+	/**
+	 * @return the bounds
+	 */
+	public Bounds getBounds() {
+		return bounds;
+	}
+
+	public String getHash() {
+		return hash;
+	}
+
+	public void setHash() {
+
+		this.hash = DataWarehouse.hasher.encrypt(this.getObjectUUID());
+
+		// Write hash to player character table
+
+		DataWarehouse.writeHash(Enum.DataRecordType.ZONE, this.getObjectUUID());
+	}
+
+	// Return heightmap for this Zone.
+
+	public HeightMap getHeightMap() {
+		
+		if (this.isPlayerCity)
+			return HeightMap.PlayerCityHeightMap;
+
+		return HeightMap.heightmapByLoadNum.get(this.loadNum);
+	}
+
+	public float getSeaLevel() {
+		return seaLevel;
+	}
+
+	public float getWorldAltitude() {
+		return worldAltitude;
+	}
+
+}
diff --git a/src/engine/pooling/ByteBufferPool.java b/src/engine/pooling/ByteBufferPool.java
new file mode 100644
index 00000000..ef6459cf
--- /dev/null
+++ b/src/engine/pooling/ByteBufferPool.java
@@ -0,0 +1,69 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.pooling;
+
+import org.pmw.tinylog.Logger;
+
+import java.nio.ByteBuffer;
+
+public class ByteBufferPool extends LinkedObjectPool<ByteBuffer> {
+
+	private final int defaultBufferSize;
+
+	public ByteBufferPool(int defaultBufferSize) {
+		super(ObjectPool.DEFAULT_SIZE);
+		this.defaultBufferSize = defaultBufferSize;
+	}
+
+	public ByteBufferPool(int size, int defaultBufferSize) {
+		super(size);
+		this.defaultBufferSize = defaultBufferSize;
+	}
+
+	@Override
+	protected ByteBuffer makeNewObject() {
+		return ByteBuffer.allocate(defaultBufferSize);
+	}
+
+	@Override
+	protected void resetObject(ByteBuffer obj) {
+		obj.clear();
+	}
+
+	@Override
+	public ByteBuffer get() {
+        // Logger.debug("ByteBufferPool.get() BB.capacity(): " + bb.capacity()
+		// + ", bb.pos(): " + bb.position() + ". Pool.size() is now: "
+		// + this.getPoolSize());
+		return super.get();
+	}
+
+	@Override
+	public void put(ByteBuffer bb) {
+		if(bb.isDirect())
+			super.put(bb);
+		// Logger.debug("ByteBufferPool.put() BB.capacity(): " + bb.capacity()
+		// + ", bb.pos(): " + bb.position() + ". Pool.size() is now: "
+		// + this.getPoolSize());
+	}
+
+	@Override
+	protected void handlePoolExhaustion() {
+		Logger.debug("ByteBufferPool(" + defaultBufferSize
+				+ ") exhausted, making more objects.");
+
+		// If none exist, make (and pool) a few
+		// Dont sync the loop, let the makeNewObject()
+		// call return before locking the pool object
+		for (int i = 0; i < 5; ++i)
+			this.makeAndAdd();
+	}
+
+}
diff --git a/src/engine/pooling/ConnectionPool.java b/src/engine/pooling/ConnectionPool.java
new file mode 100644
index 00000000..cb8e240e
--- /dev/null
+++ b/src/engine/pooling/ConnectionPool.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2013 MagicBane Emulator Project
+ * All Rights Reserved   
+ */
+package engine.pooling;
+
+import engine.gameManager.ConfigManager;
+
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.SQLException;
+
+public class ConnectionPool extends LinkedObjectPool<Connection> {
+
+	static {
+		//Register the Driver
+		try {
+			Class.forName("com.mysql.cj.jdbc.Driver").newInstance();
+		} catch (InstantiationException | ClassNotFoundException | IllegalAccessException e) {
+			// TODO Auto-generated catch block
+			e.printStackTrace();
+		}
+    }
+
+	public ConnectionPool() {
+		super(10);
+	}
+
+	@Override
+	protected Connection makeNewObject() {
+		// Protocol
+		String sqlURI = "jdbc:mysql://";
+		sqlURI += ConfigManager.MB_DATABASE_ADDRESS.getValue() + ':' + ConfigManager.MB_DATABASE_PORT.getValue();
+		sqlURI += '/' + ConfigManager.MB_DATABASE_NAME.getValue() + '?';
+		sqlURI += "useServerPrepStmts=true";
+		sqlURI += "&cachePrepStmts=false";
+		sqlURI += "&cacheCallableStmts=true";
+		sqlURI += "&characterEncoding=utf8";
+
+		Connection out = null;
+		try {
+			out = DriverManager.getConnection(sqlURI, ConfigManager.MB_DATABASE_USER.getValue(),
+					ConfigManager.MB_DATABASE_PASS.getValue());
+		} catch (SQLException e) {
+			e.printStackTrace();
+		}
+
+		return out;
+	}
+
+	@Override
+	protected void resetObject(Connection obj) {
+
+	}
+}
diff --git a/src/engine/pooling/LinkedObjectPool.java b/src/engine/pooling/LinkedObjectPool.java
new file mode 100644
index 00000000..34828c5c
--- /dev/null
+++ b/src/engine/pooling/LinkedObjectPool.java
@@ -0,0 +1,142 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.pooling;
+
+
+import org.pmw.tinylog.Logger;
+
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.atomic.AtomicInteger;
+
+public abstract class LinkedObjectPool<T> {
+	private final LinkedBlockingQueue<T> pool;
+	private final AtomicInteger poolSize;
+
+	/**
+	 * Constructor that allows the caller to specify initial array size and
+	 * percent of prefill.
+	 * 
+	 * @param size
+	 *            - initial size for the containing array (pool)
+	 */
+	public LinkedObjectPool(int size) {
+		this.pool = new LinkedBlockingQueue<>();
+		this.poolSize = new AtomicInteger();
+	}
+
+	/**
+	 * Default Constructor that uses default initial Array size and percent
+	 * prefill values
+	 */
+	public LinkedObjectPool() {
+		this(0);
+	}
+
+	/**
+	 * Forces pool to add <i>numberOfObjects</i> to the pool. This may cause
+	 * internal ArrayList to resize.
+	 * 
+	 * @param numberOfObjects
+	 */
+	public void fill(int numberOfObjects) {
+		for (int i = 0; i < numberOfObjects; ++i)
+			this.makeAndAdd();
+	}
+
+	/**
+	 * Forces subclasses to implement factory routine for object creation.
+	 */
+	protected abstract T makeNewObject();
+
+	/**
+	 * Forces subclasses to implement a way to reset object to a reusable state.
+	 */
+	protected abstract void resetObject(T obj);
+
+	/**
+	 * Generic Get routine. If the pool is empty, then one (or more) of T type
+	 * objects will be created. If more than one T is created, then they will be
+	 * put into the pool. One T will always be created and returned.
+	 */
+	public T get() {
+		T obj = pool.poll();
+		
+		if(obj == null) {
+			//Oops pool is empty.. make a new obj
+			obj = this.makeNewObject();
+		} else {
+			poolSize.decrementAndGet();
+		}
+
+		return obj;
+	}
+	
+	/**
+	 * Generic put routine. If the current pool size is below threshold, this
+	 * object will be pooled, otherwise it will be NULL'ed and scheduled for
+	 * Garbage Collection
+	 * 
+	 * @param obj
+	 */
+	public void put(T obj) {
+		//Logger.debug("Objectpool.put().  Pool size: " + pool.size());
+		this.resetObject(obj);
+		this.poolSize.incrementAndGet();
+		this.pool.add(obj);
+	}
+
+	/**
+	 * Helper method. Attempts to create and add a new <i>T</i> object. If this
+	 * fails, an error is logged.
+	 */
+	protected final void makeAndAdd() {
+		T obj = this.makeNewObject();
+		if (obj == null) {
+			Logger.error("Pooling failure: Object creation failed.");
+		} else {
+			this.put(obj);
+		}
+	}
+
+	/**
+	 * 
+	 * @return the current size of the pool, not the maximum capacity of the
+	 *         Array holding the pool.
+	 */
+	public final int getPoolSize() {
+		return this.poolSize.get();
+	}
+
+	/**
+	 * Culls the pool and removes half of the stored objects. Removed objects
+	 * are NULL'ed and scheduled form Garbage Collection.
+	 * 
+	 * @return the number of Objects removed from the pool.
+	 */
+	public int cullHalf() {
+		int full, half;
+		full = this.getPoolSize();
+		if (full < 1) {
+			return full;
+		}
+
+		half = (full / 2);
+
+		for (int i = 0; i < (full / 2); ++i) {
+			T obj = this.pool.poll();
+			obj = null; // Null out for GC
+		}
+		return half;
+	}
+
+	protected void handlePoolExhaustion() {
+		//Not needed in this implementation
+	}
+}
diff --git a/src/engine/pooling/MultisizeByteBufferPool.java b/src/engine/pooling/MultisizeByteBufferPool.java
new file mode 100644
index 00000000..3196c517
--- /dev/null
+++ b/src/engine/pooling/MultisizeByteBufferPool.java
@@ -0,0 +1,190 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.pooling;
+
+import org.pmw.tinylog.Logger;
+
+import java.nio.ByteBuffer;
+import java.util.HashMap;
+
+public class MultisizeByteBufferPool {
+
+	/**
+	 * Maps a power of two (0-30) to a BB Pool
+	 */
+	private final HashMap<Integer, ByteBufferPool> powerToPoolMap = new HashMap<>();
+
+	public MultisizeByteBufferPool() {
+		super();
+	}
+
+	/**
+	 * Gets a ByteBuffer that is of the size 2^<i>powerOfTwo</i> from the
+	 * appropriate pool.
+	 *
+	 * @param powerOfTwo
+	 *            int range of 0-30
+	 * @return
+	 */
+	public ByteBuffer getBuffer(int powerOfTwo) {
+		// Validate input
+		if (powerOfTwo > 30 || powerOfTwo < 0) {
+			Logger.error("powerOfTwo out of range (0-30) in getBuffer().  Got: " + powerOfTwo);
+			return null;
+		}
+
+		// Check to see if there is a pool for this size
+		ByteBufferPool bbp = this.getByteBufferPool(powerOfTwo);
+		return bbp.get();
+	}
+
+	/**
+	 * Internal getter to provide synchronization. Adds ByteBufferPool if not mapped.
+	 *
+	 * @param powerOfTwo
+	 * @return
+	 */
+	private ByteBufferPool getByteBufferPool(Integer powerOfTwo) {
+		synchronized (this.powerToPoolMap) {
+			// Check to see if there is a pool for this size
+			ByteBufferPool bbp = powerToPoolMap.get(powerOfTwo);
+
+			if (bbp == null) {
+				bbp = MultisizeByteBufferPool.makeByteBufferPool(powersOfTwo[powerOfTwo]);
+				this.putByteBufferPool(powerOfTwo, bbp);
+			}
+			return bbp;
+		}
+	}
+
+	/**
+	 * Internal setter to provide synchronization
+	 *
+	 * @param powerOfTwo
+	 * @param bbp
+	 * @return
+	 */
+	private ByteBufferPool putByteBufferPool(Integer powerOfTwo,
+                                             ByteBufferPool bbp) {
+		synchronized (this.powerToPoolMap) {
+			return powerToPoolMap.put(powerOfTwo, bbp);
+		}
+	}
+
+	public ByteBuffer getBufferToFit(int numOfBytes) {
+		int pow = MultisizeByteBufferPool.getPowerThatWillFit(numOfBytes);
+		return this.getBuffer(pow);
+	}
+
+	/**
+	 * Puts a ByteBuffer that is of the size 2^<i>powerOfTwo</i> back into the
+	 * appropriate pool.
+	 *
+	 * @param bb
+	 *            - Bytebuffer to put into a pool
+	 *
+	 */
+	public void putBuffer(ByteBuffer bb) {
+
+		if (bb == null)
+			return;
+
+		// determine size:
+		int pow = MultisizeByteBufferPool.getPowerThatWillFit(bb.capacity());
+
+		// if we get here and pow == -1 then we have a bytebuffer > 2^30 !!!!
+		// so just file it under power of 30;
+		if (pow == -1) {
+			pow = 30;
+		}
+
+		// get pool
+		ByteBufferPool bbp = this.getByteBufferPool(pow);
+
+		// put buffer (back) into pool
+		bbp.put(bb);
+	}
+
+	/**
+	 * Returns the next power of two that is larger than or equal too the input
+	 * <i>value</i>
+	 *
+	 * @param value
+	 * @return the power of two that is larger than or equal too the input
+	 *         <i>value</i>. A return of -1 indicates out of range.
+	 */
+	public static int getPowerThatWillFit(final int value) {
+		return (value == 0 ? 0 : 32 - Integer.numberOfLeadingZeros(value - 1));
+	}
+
+	private static ByteBufferPool makeByteBufferPool(int bbInitialSize) {
+		return new ByteBufferPool(bbInitialSize);
+	}
+
+	/**
+	 * Returns the size of the ByteBufferPool mapped to the given powerOfTwo.
+	 *
+	 * @param powerOfTwo
+	 *            int range of 0-30
+	 * @return size of pool mapped to provided <i>powerOfTwo</i>. Returns -1 on
+	 *         error and lastError will be set.
+	 */
+	public int getSizeOfPool(int powerOfTwo) {
+		if (powerOfTwo > 30 || powerOfTwo < 0) {
+			Logger.error("powerOfTwo out of range (0-30) in getSizeOfPool().  Got: "
+							+ powerOfTwo);
+			return -1;
+		}
+		ByteBufferPool bbp = this.getByteBufferPool(powerOfTwo);
+
+		return bbp.getPoolSize();
+	}
+
+
+	/**
+	 * List of the powers of two from 2^0 to 2^30. The index of the array
+	 * corresponds to the power of two. Example: If you'd like to quickly lookup
+	 * 2^19, then reference powersOfTwo[19]
+	 */
+	public static final int[] powersOfTwo = {
+			1, // 2^0
+			2, // 2^1
+			4, // 2^2
+			8, // 2^3
+			16, // 2^4
+			32, // 2^5
+			64, // 2^6
+			128, // 2^7
+			256, // 2^8
+			512, // 2^9
+			1024, // 2^10
+			2048, // 2^11
+			4096, // 2^12
+			8192, // 2^13
+			16384, // 2^14
+			32768, // 2^15
+			65536, // 2^16
+			131072, // 2^17
+			262144, // 2^18
+			524288, // 2^19
+			1048576, // 2^20
+			2097152, // 2^21
+			4194304, // 2^22
+			8388608, // 2^23
+			16777216, // 2^24
+			33554432, // 2^25
+			67108864, // 2^26
+			134217728, // 2^27
+			268435456, // 2^28
+			536870912, // 2^29
+			1073741824, // 2^30
+	};
+
+}
diff --git a/src/engine/pooling/ObjectPool.java b/src/engine/pooling/ObjectPool.java
new file mode 100644
index 00000000..5ded922f
--- /dev/null
+++ b/src/engine/pooling/ObjectPool.java
@@ -0,0 +1,195 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.pooling;
+
+import org.pmw.tinylog.Logger;
+
+import java.util.ArrayList;
+
+public abstract class ObjectPool<T> {
+	protected final static int DEFAULT_SIZE = 1000;
+
+	// Simple + quick list
+	private final ArrayList<T> pool;
+
+	/**
+	 * Once the ArrayList fills to <b>threshold</b>, subsequent .put() calls
+	 * result in the object being thrown away. Default is 75% of supplied
+	 * <b>size</b>.
+	 */
+	private int threshold;
+
+	/**
+	 * Constructor that allows the caller to specify initial array size and
+	 * percent of prefill.
+	 * 
+	 * @param size
+	 *            - initial size for the containing array (pool)
+	 */
+	public ObjectPool(int size) {
+		if (size == 0) {
+			size = DEFAULT_SIZE;
+		}
+
+		threshold = (int) (0.75 * size);
+
+		this.pool = new ArrayList<>(size);
+
+	}
+
+	/**
+	 * Default Constructor that uses default initial Array size and percent
+	 * prefill values
+	 */
+	public ObjectPool() {
+		this(DEFAULT_SIZE);
+	}
+
+	/**
+	 * Forces pool to add <i>numberOfObjects</i> to the pool. This may cause
+	 * internal ArrayList to resize.
+	 * 
+	 * @param numberOfObjects
+	 */
+	public void fill(int numberOfObjects) {
+		for (int i = 0; i < numberOfObjects; ++i)
+			this.makeAndAdd();
+	}
+
+	/**
+	 * Forces subclasses to implement factory routine for object creation.
+	 */
+	protected abstract T makeNewObject();
+
+	/**
+	 * Forces subclasses to implement a way to reset object to a reusable state.
+	 */
+	protected abstract void resetObject(T obj);
+
+	/**
+	 * Generic Get routine. If the pool is empty, then one (or more) of T type
+	 * objects will be created. If more than one T is created, then they will be
+	 * put into the pool. One T will always be created and returned.
+	 */
+	public T get() {
+		synchronized (pool) {
+			//Logger.debug("Objectpool.get().  Pool size before get: " + pool.size());
+			if (pool.size() > 0) {
+				return pool.remove(0);
+			}
+		}
+		
+		this.handlePoolExhaustion();
+
+		T obj = this.makeNewObject();
+
+		if (obj == null) {
+			Logger.error("Pooling failure: Object creation failed.");
+		}
+		return obj;
+	}
+
+	protected void handlePoolExhaustion(){
+		Logger.debug("Pool exhausted, making more objects.");
+		
+		// If none exist, make (and pool) a few
+		// Dont sync the loop, let the makeNewObject()
+		// call return before locking the pool object
+		for (int i = 0; i < 5; ++i)
+			this.makeAndAdd();
+
+	}
+	
+	/**
+	 * Generic put routine. If the current pool size is below threshold, this
+	 * object will be pooled, otherwise it will be NULL'ed and scheduled for
+	 * Garbage Collection
+	 * 
+	 * @param obj
+	 */
+	public void put(T obj) {
+		synchronized (pool) {
+			if (pool.size() >= this.threshold) {
+				//Logger.debug("Objectpool.put() rejected.  Pool size: " + pool.size());
+				return;
+			}
+			//Logger.debug("Objectpool.put().  Pool size: " + pool.size());
+			this.resetObject(obj);
+			this.pool.add(obj);
+		}
+	}
+
+	/**
+	 * Helper method. Attempts to create and add a new <i>T</i> object. If this
+	 * fails, an error is logged.
+	 */
+	protected final void makeAndAdd() {
+		T obj = this.makeNewObject();
+		if (obj == null) {
+			Logger.error("Pooling failure: Object creation failed.");
+		} else {
+			this.put(obj);
+		}
+	}
+
+	/**
+	 * 
+	 * @return the current size of the pool, not the maximum capacity of the
+	 *         Array holding the pool.
+	 */
+	public final int getPoolSize() {
+		synchronized (this.pool) {
+			return this.pool.size();
+		}
+	}
+
+	/**
+	 * Culls the pool and removes half of the stored objects. Removed objects
+	 * are NULL'ed and scheduled form Garbage Collection.
+	 * 
+	 * @return the number of Objects removed from the pool.
+	 */
+	public int cullHalf() {
+		int full, half;
+		synchronized (this.pool) {
+			full = this.pool.size();
+			if (full < 1) {
+				return full;
+			}
+
+			half = (full / 2);
+
+			for (int i = 0; i < (full / 2); ++i) {
+				T obj = this.get();
+				obj = null; // Null out for GC
+			}
+		}
+		return half;
+	}
+
+	/**
+	 * @return the threshold
+	 */
+	public final int getThreshold() {
+		return threshold;
+	}
+
+	/**
+	 * @param threshold
+	 *            the threshold to set
+	 */
+	public void setThreshold(int threshold) {
+		if (threshold < 0)
+			return;
+
+		this.threshold = threshold;
+	}
+
+}
diff --git a/src/engine/pooling/Poolable.java b/src/engine/pooling/Poolable.java
new file mode 100644
index 00000000..4155cbc5
--- /dev/null
+++ b/src/engine/pooling/Poolable.java
@@ -0,0 +1,14 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.pooling;
+
+public interface Poolable {
+	public void clear();
+}
diff --git a/src/engine/powers/ActionsBase.java b/src/engine/powers/ActionsBase.java
new file mode 100644
index 00000000..0f157354
--- /dev/null
+++ b/src/engine/powers/ActionsBase.java
@@ -0,0 +1,266 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.powers;
+
+import engine.Enum.ModType;
+import engine.Enum.SourceType;
+import engine.Enum.StackType;
+import engine.gameManager.PowersManager;
+import engine.objects.*;
+import engine.powers.poweractions.AbstractPowerAction;
+import org.pmw.tinylog.Logger;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.HashMap;
+
+
+public class ActionsBase {
+
+	public int UUID;
+		public String IDString;
+	public String effectID;
+	public int minTrains;
+	public int maxTrains;
+	public float duration;
+	public float ramp;
+	public boolean addFormula;
+	public String stackType;
+	public StackType stackTypeType;
+	public int stackOrder;
+
+	public boolean greaterThanEqual = false;
+	public boolean always = false;
+	public boolean greaterThan = false;
+	public String stackPriority;
+
+	private AbstractPowerAction powerAction;
+
+	/**
+	 * No Table ID Constructor
+	 */
+	public ActionsBase() {
+
+	}
+
+	/**
+	 * ResultSet Constructor
+	 */
+	public ActionsBase(ResultSet rs, HashMap<String, AbstractPowerAction> apa) throws SQLException {
+
+		this.UUID = rs.getInt("ID");
+				this.IDString = rs.getString("powerID");
+		this.effectID = rs.getString("effectID");
+		this.minTrains = rs.getInt("minTrains");
+		this.maxTrains = rs.getInt("maxTrains");
+		this.duration = rs.getFloat("duration");
+		this.ramp = rs.getFloat("ramp");
+		this.addFormula = (rs.getInt("useAddFormula") == 1) ? true : false;
+		this.stackType = rs.getString("stackType");
+		this.stackTypeType = StackType.GetStackType(this.stackType);
+		this.stackOrder = rs.getInt("stackOrder");
+		this.stackPriority = rs.getString("stackPriority");
+
+		switch (stackPriority) {
+		case "GreaterThanOrEqualTo":
+			this.greaterThanEqual = true;
+			break;
+		case "Always":
+			this.always = true;
+			break;
+		case "GreaterThan":
+			this.greaterThan = true;
+			break;
+		}
+		this.powerAction = apa.get(this.effectID);
+	}
+
+	protected ActionsBase(int uUID, String effectID, int minTrains, int maxTrains, float duration, float ramp,
+			boolean addFormula, String stackType, int stackOrder, boolean greaterThanEqual, boolean always,
+			boolean greaterThan, AbstractPowerAction powerAction) {
+		super();
+		UUID = uUID;
+		this.effectID = effectID;
+		this.minTrains = minTrains;
+		this.maxTrains = maxTrains;
+		this.duration = duration;
+		this.ramp = ramp;
+		this.addFormula = addFormula;
+		this.stackType = stackType;
+		this.stackTypeType = StackType.GetStackType(this.stackType);
+		if (this.stackTypeType == null)
+			Logger.info("Invalid Stack Type " + this.stackTypeType + " for " + this.effectID);
+		this.stackOrder = stackOrder;
+		this.greaterThanEqual = greaterThanEqual;
+		this.always = always;
+		this.greaterThan = greaterThan;
+		this.powerAction = powerAction;
+		
+		if (this.greaterThanEqual)
+			this.stackPriority = "GreaterThanOrEqualTo";
+		else if(this.always)
+			this.stackPriority = "Always";
+		else if (this.greaterThan)
+			this.stackPriority = "GreaterThan";
+	
+	}
+
+	//	public static ArrayList<ActionsBase> getActionsBase(String ID) {
+	//		PreparedStatementShared ps = null;
+	//		ArrayList<ActionsBase> out = new ArrayList<ActionsBase>();
+	//		try {
+	//			ps = new PreparedStatementShared("SELECT * FROM actions where powerID = ?");
+	//			ps.setString(1, ID);
+	//			ResultSet rs = ps.executeQuery();
+	//			while (rs.next()) {
+	//				ActionsBase toAdd = new ActionsBase(rs);
+	//				out.add(toAdd);
+	//			}
+	//			rs.close();
+	//		} catch (Exception e) {
+	//			Logger.error("ActionsBase", e);
+	//		} finally {
+	//			ps.release();
+	//		}
+	//		return out;
+	//	}
+
+	public static void getActionsBase(HashMap<String, PowersBase> powers, HashMap<String, AbstractPowerAction> apa) {
+		PreparedStatementShared ps = null;
+		try {
+			ps = new PreparedStatementShared("SELECT * FROM static_power_action");
+			ResultSet rs = ps.executeQuery();
+			String IDString; ActionsBase toAdd; PowersBase pb;
+			while (rs.next()) {
+				IDString = rs.getString("powerID");
+				pb = powers.get(IDString);
+				if (pb != null) {
+					toAdd = new ActionsBase(rs, apa);
+					pb.getActions().add(toAdd);
+				}
+			}
+			rs.close();
+		} catch (Exception e) {
+			Logger.error( e.toString());
+		} finally {
+			ps.release();
+		}
+		
+		int gateID = 5000;
+		for (String IDString : Runegate.GetAllOpenGateIDStrings()){
+			gateID++;
+			ActionsBase openGateActionBase = new ActionsBase(gateID, "OPENGATE", 5, 9999, 0, 0, true, "IgnoreStack", 0, true, false, false, PowersManager.getPowerActionByIDString("OPENGATE"));
+		
+			PowersBase openGatePower = powers.get(IDString);
+			
+			if (openGatePower == null){
+				Logger.error( "no powerbase for action " + IDString);
+				break;
+			}
+			openGatePower.getActions().add(openGateActionBase);
+		}
+	}
+	
+
+	public int getUUID() {
+		return this.UUID;
+	}
+
+	public String getEffectID() {
+		return this.effectID;
+	}
+
+	public int getMinTrains() {
+		return this.minTrains;
+	}
+
+	public int getMaxTrains() {
+		return this.maxTrains;
+	}
+
+	public float getDuration() {
+		return this.duration;
+	}
+
+	public AbstractPowerAction getPowerAction() {
+		return this.powerAction;
+	}
+
+	public int getDuration(int trains) {
+		if (this.addFormula)
+			return (int)((this.duration + (this.ramp * trains)) * 1000);
+		else
+			return (int)((this.duration * (1 + (this.ramp * trains))) * 1000);
+	}
+
+	public float getDurationAsFloat(int trains) {
+		if (this.addFormula)
+			return ((this.duration + (this.ramp * trains)) * 1000);
+		else
+			return ((this.duration * (1 + (this.ramp * trains))) * 1000);
+	}
+
+	public int getDurationInSeconds(int trains) {
+		if (this.addFormula)
+			return (int)(this.duration + (this.ramp * trains));
+		else
+			return (int)(this.duration * (1 + (this.ramp * trains)));
+	}
+
+	public String getStackType() {
+		return this.stackType;
+	}
+
+	public int getStackOrder() {
+		return this.stackOrder;
+	}
+
+	public boolean greaterThanEqual() {
+		return this.greaterThanEqual;
+	}
+
+	public boolean greaterThan() {
+		return this.greaterThan;
+	}
+
+	public boolean always() {
+		return this.always;
+	}
+
+	//Add blocked types here
+	public boolean blocked(AbstractWorldObject awo, PowersBase pb, int trains) {
+		if (AbstractWorldObject.IsAbstractCharacter(awo)) {
+			AbstractCharacter ac = (AbstractCharacter) awo;
+			PlayerBonuses bonus = ac.getBonuses();
+			if (bonus == null)
+				return false;
+			
+			//TODO make this more efficient then testing strings
+			if (this.stackType.equals("Stun") && bonus.getBool(ModType.ImmuneTo, SourceType.Stun))
+				return true; //Currently stun immune. Skip stun
+			else if(this.stackType.equals("Snare") && bonus.getBool(ModType.ImmuneTo, SourceType.Snare))
+				return true; //Currently snare immune. Skip snare
+			else if(this.stackType.equals("Blindness") && bonus.getBool(ModType.ImmuneTo, SourceType.Blind))
+				return true; //Currently blind immune. Skip blind
+			else if(this.stackType.equals("PowerInhibitor") && bonus.getBool(ModType.ImmuneTo, SourceType.Powerblock))
+				return true; //Currently power block immune. Skip power block
+			else if (this.stackType.equals("Root") && bonus.getBool(ModType.ImmuneTo, SourceType.Root))
+				return true;
+			//			else if (pb.isHeal() && (bonus.getByte("immuneTo.Heal")) >= trains)
+			//				return true; //Currently shadowmantled. Skip heals
+			else if (this.stackType.equals("Flight") && bonus.getBool(ModType.NoMod, SourceType.Fly))
+				return true;
+			else if (this.stackType.equals("Track") && bonus.getBool(ModType.CannotTrack, SourceType.None))
+				return true;
+			else return pb.vampDrain() && bonus.getBool(ModType.BlockedPowerType, SourceType.VAMPDRAIN);
+		}
+		return false;
+	}
+}
diff --git a/src/engine/powers/DamageShield.java b/src/engine/powers/DamageShield.java
new file mode 100644
index 00000000..58b4af0b
--- /dev/null
+++ b/src/engine/powers/DamageShield.java
@@ -0,0 +1,43 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+ package engine.powers;
+
+import engine.Enum.DamageType;
+
+public class DamageShield {
+
+	private final DamageType damageType;
+	private final float amount;
+	private final boolean usePercent;
+
+	public DamageShield(DamageType damageType, float amount, boolean usePercent) {
+		super();
+		this.damageType = damageType;
+		this.amount = amount;
+		this.usePercent = usePercent;
+	}
+
+	public DamageType getDamageType() {
+		return this.damageType;
+	}
+
+	public float getAmount() {
+		return this.amount;
+	}
+
+	public boolean usePercent() {
+		return this.usePercent;
+	}
+
+	@Override
+	public String toString() {
+		return "ds.DamageType: " + this.damageType.name() + ", Amount: " + this.amount + ", UsePercent: " + this.usePercent;
+	}
+}
diff --git a/src/engine/powers/EffectsBase.java b/src/engine/powers/EffectsBase.java
new file mode 100644
index 00000000..e8cfca49
--- /dev/null
+++ b/src/engine/powers/EffectsBase.java
@@ -0,0 +1,892 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+
+
+
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.powers;
+
+import engine.Enum;
+import engine.Enum.DamageType;
+import engine.Enum.EffectSourceType;
+import engine.Enum.GameObjectType;
+import engine.Enum.PowerFailCondition;
+import engine.gameManager.DbManager;
+import engine.gameManager.PowersManager;
+import engine.job.JobContainer;
+import engine.jobs.AbstractEffectJob;
+import engine.jobs.DamageOverTimeJob;
+import engine.jobs.FinishSpireEffectJob;
+import engine.jobs.NoTimeJob;
+import engine.net.Dispatch;
+import engine.net.DispatchMessage;
+import engine.net.client.ClientConnection;
+import engine.net.client.msg.ApplyEffectMsg;
+import engine.objects.*;
+import engine.powers.effectmodifiers.AbstractEffectModifier;
+import engine.server.MBServerStatics;
+import org.pmw.tinylog.Logger;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class EffectsBase {
+
+	private int UUID;
+	private String IDString;
+	// private String name;
+	private int token;
+	private float amount;
+	private float amountRamp;
+
+	// flags
+	private boolean isItemEffect;
+	private boolean isSpireEffect;
+	private boolean ignoreMod;
+	private boolean dontSave;
+
+	private boolean cancelOnAttack = false;
+	private boolean cancelOnAttackSwing = false;
+	private boolean cancelOnCast = false;
+	private boolean cancelOnCastSpell = false;
+	private boolean cancelOnEquipChange = false;
+	private boolean cancelOnLogout = false;
+	private boolean cancelOnMove = false;
+	private boolean cancelOnNewCharm = false;
+	private boolean cancelOnSit = false;
+	private boolean cancelOnTakeDamage = false;
+	private boolean cancelOnTerritoryClaim = false;
+	private boolean cancelOnUnEquip = false;
+	private boolean useRampAdd;
+	private boolean isPrefix = false; //used by items
+	private boolean isSuffix = false; //used by items
+	private String name = "";
+	private float value = 0;
+	private  ConcurrentHashMap<ItemBase, Integer> resourceCosts = new ConcurrentHashMap<>();
+	private ConcurrentHashMap<String, Boolean> sourceTypes = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW);
+	public static HashMap<Integer,HashSet<EffectSourceType>> effectSourceTypeMap = new HashMap<>();
+	public static HashMap<String, HashSet<AbstractEffectModifier>> modifiersMap = new HashMap<>();
+	private static ConcurrentHashMap<String, String> itemEffectsByName =  new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW);
+	private static int NewID = 3000;
+	public static HashMap<String,HashMap<String,ArrayList<String>>> OldEffectsMap = new HashMap<>();
+	public static HashMap<String,HashMap<String,ArrayList<String>>> NewEffectsMap = new HashMap<>();
+	public static HashMap<String,HashMap<String,ArrayList<String>>> ChangedEffectsMap = new HashMap<>();
+	public static HashMap<String,HashSet<PowerFailCondition>> EffectFailConditions = new HashMap<>();
+	public static HashMap<Integer,HashSet<DamageType>> EffectDamageTypes = new HashMap<>();
+	
+	public static HashSet<AbstractEffectModifier> DefaultModifiers = new HashSet<>();
+	/**
+	 * No Table ID Constructor
+	 */
+	public EffectsBase() {
+
+	}
+
+	public EffectsBase(EffectsBase copyEffect, int newToken, String IDString) {
+	
+		UUID = NewID++;
+		this.IDString = IDString;
+		this.token = newToken;
+		
+		//filll 
+		if (copyEffect == null){
+			int flags = 0;
+			this.isItemEffect = ((flags & 1) != 0) ? true : false;
+			this.isSpireEffect = ((flags & 2) != 0) ? true : false;
+			this.ignoreMod = ((flags & 4) != 0) ? true : false;
+			this.dontSave = ((flags & 8) != 0) ? true : false;
+
+			if (this.IDString.startsWith("PRE-"))
+				this.isPrefix = true;
+			else if (this.IDString.startsWith("SUF-"))
+				this.isSuffix = true;
+			
+		}
+		
+		
+		this.amount = copyEffect.amount;
+		this.amountRamp = copyEffect.amountRamp;
+		this.isItemEffect = copyEffect.isItemEffect;
+		this.isSpireEffect = copyEffect.isSpireEffect;
+		this.ignoreMod = copyEffect.ignoreMod;
+		this.dontSave = copyEffect.dontSave;
+		this.cancelOnAttack = copyEffect.cancelOnAttack;
+		this.cancelOnAttackSwing = copyEffect.cancelOnAttackSwing;
+		this.cancelOnCast = copyEffect.cancelOnCast;
+		this.cancelOnCastSpell = copyEffect.cancelOnCastSpell;
+		this.cancelOnEquipChange = copyEffect.cancelOnEquipChange;
+		this.cancelOnLogout = copyEffect.cancelOnLogout;
+		this.cancelOnMove = copyEffect.cancelOnMove;
+		this.cancelOnNewCharm = copyEffect.cancelOnNewCharm;
+		this.cancelOnSit = copyEffect.cancelOnSit;
+		this.cancelOnTakeDamage = copyEffect.cancelOnTakeDamage;
+		this.cancelOnTerritoryClaim = copyEffect.cancelOnTerritoryClaim;
+		this.cancelOnUnEquip = copyEffect.cancelOnUnEquip;
+		this.useRampAdd = copyEffect.useRampAdd;
+		this.isPrefix = copyEffect.isPrefix;
+		this.isSuffix = copyEffect.isSuffix;
+		this.name = copyEffect.name;
+		this.value = copyEffect.value;
+		this.resourceCosts = copyEffect.resourceCosts;
+		
+	}
+
+	/**
+	 * ResultSet Constructor
+	 */
+	public EffectsBase(ResultSet rs) throws SQLException {
+
+		this.UUID = rs.getInt("ID");
+		this.IDString = rs.getString("IDString");
+		this.name = rs.getString("name");
+		this.token = rs.getInt("Token");
+		
+		//override tokens for some effects like Safemode that use the Action Token instead of the effect Token,
+		switch (this.IDString){
+		case "INVIS-D":
+			this.token = -1661751254;
+			break;
+		case "SafeMode":
+			this.token = -1661750486;
+			break;
+			
+		}
+		int flags = rs.getInt("flags");
+		this.isItemEffect = ((flags & 1) != 0) ? true : false;
+		this.isSpireEffect = ((flags & 2) != 0) ? true : false;
+		this.ignoreMod = ((flags & 4) != 0) ? true : false;
+		this.dontSave = ((flags & 8) != 0) ? true : false;
+
+		if (this.IDString.startsWith("PRE-"))
+			this.isPrefix = true;
+		else if (this.IDString.startsWith("SUF-"))
+			this.isSuffix = true;
+		// getFailConditions();
+	}
+	
+	
+	public static EffectsBase createNoDbEffectsBase(EffectsBase copyEffect, int newToken, String IDString){
+		EffectsBase cachedEffectsBase = new EffectsBase(copyEffect,newToken,IDString);
+		
+		if (cachedEffectsBase == null)
+			return null;
+		
+		//add to Lists.
+		PowersManager.effectsBaseByIDString.put(cachedEffectsBase.IDString, cachedEffectsBase);
+		PowersManager.effectsBaseByToken.put(cachedEffectsBase.token, cachedEffectsBase);
+		
+		return cachedEffectsBase;
+	}
+
+
+
+	public static ArrayList<EffectsBase> getAllEffectsBase() {
+		PreparedStatementShared ps = null;
+		ArrayList<EffectsBase> out = new ArrayList<>();
+		try {
+			ps = new PreparedStatementShared("SELECT * FROM static_power_effectbase ORDER BY `IDString` DESC");
+			ResultSet rs = ps.executeQuery();
+			while (rs.next()) {
+				EffectsBase toAdd = new EffectsBase(rs);
+				out.add(toAdd);
+			}
+			rs.close();
+		} catch (Exception e) {
+			Logger.error(e);
+		} finally {
+			ps.release();
+		}
+		//testHash(out);
+		return out;
+	}
+	
+	public static ArrayList<EffectsBase> getAllLiveEffectsBase() {
+		PreparedStatementShared ps = null;
+		ArrayList<EffectsBase> out = new ArrayList<>();
+		try {
+			ps = new PreparedStatementShared("SELECT * FROM static_power_effectbase_24 ORDER BY `IDString` DESC");
+			ResultSet rs = ps.executeQuery();
+			while (rs.next()) {
+				EffectsBase toAdd = new EffectsBase(rs);
+				out.add(toAdd);
+			}
+			rs.close();
+		} catch (Exception e) {
+			Logger.error(e);
+		} finally {
+			ps.release();
+		}
+		//testHash(out);
+		return out;
+	}
+
+	//private static void testHash(ArrayList<EffectsBase> effs) {
+	//	int valid = 0, invalid = 0;
+	//	for (EffectsBase eff : effs) {
+	//		String ids = eff.getIDString();
+	//		int tok = eff.getToken();
+	//		if (ids.length() != 8 || ids.startsWith("PRE-") || ids.startsWith("SUF-") || ids.endsWith("X") || !ids.substring(3,4).equals("-"))
+	//			continue;
+	//
+	////		if ((tok > 1 || tok < 0) && ids.length() == 8) {
+	//			int out = Hash(ids);
+	//			if (out != tok) {
+	//				System.out.println(ids + ": " + Integer.toHexString(out) + "(" + out + ")");
+	//				invalid++;
+	//			} else
+	//				valid++;
+	////		}
+	//	}
+	//	System.out.println("valid: " + valid + ", invalid: " + invalid);
+	//}
+
+	//private static int Hash(String IDString) {
+	//	char[] val = IDString.toCharArray();
+	//	int out = 360;
+	//	out ^= val[0];
+	//	out ^= (val[1] << 5);
+	//	out ^= (val[2] << 10);
+	//	out ^= (val[4] << 23);
+	//	out ^= (val[5] << 19);
+	//	out ^= (val[6] << 15);
+	//	out ^= (val[7] << 26);
+	//	out ^= (val[7] >> 6);
+	//	out ^= 17;
+	//	return out;
+	//}
+
+
+	public static void getFailConditions(HashMap<String, EffectsBase> effects) {
+		PreparedStatementShared ps = null;
+		try {
+			ps = new PreparedStatementShared("SELECT * FROM static_power_failcondition WHERE powerOrEffect = 'Effect';");
+		
+			ResultSet rs = ps.executeQuery();
+			PowerFailCondition failCondition = null;
+	
+			Object value;
+			while (rs.next()) {
+				String fail = rs.getString("type");
+				
+				
+				
+				String IDString = rs.getString("IDString");
+				int token = DbManager.hasher.SBStringHash(IDString);
+				failCondition = PowerFailCondition.valueOf(fail);
+				if (failCondition == null){
+					Logger.error( "Couldn't Find FailCondition " + fail + " for " + IDString);
+					continue;
+				}
+				
+				if (EffectsBase.EffectFailConditions.get(IDString) == null){
+				EffectsBase.EffectFailConditions.put(IDString, new HashSet<>());
+				}
+				
+				EffectsBase.EffectFailConditions.get(IDString).add(failCondition);
+				EffectsBase eb = effects.get(IDString);
+			
+					switch (failCondition) {
+					
+					case TakeDamage:
+						
+						
+						
+						// dont go any further.
+						if (eb == null){
+						break;
+						}
+						
+						eb.cancelOnTakeDamage = true;	
+						
+						
+						
+					
+						eb.amount = rs.getFloat("amount");
+						eb.amountRamp = rs.getFloat("ramp");
+						eb.useRampAdd = rs.getBoolean("UseAddFormula");
+						
+						String damageType1 = rs.getString("damageType1");
+						String damageType2 = rs.getString("damageType1");
+						String damageType3 = rs.getString("damageType1");
+						
+						
+						if (damageType1.isEmpty() && damageType2.isEmpty() && damageType3.isEmpty())
+							break;
+						
+						if (!EffectsBase.EffectDamageTypes.containsKey(eb.getToken())){
+							EffectsBase.EffectDamageTypes.put(eb.getToken(), new HashSet<>());
+						}
+						if (damageType1.equalsIgnoreCase("Crushing"))
+							damageType1 = "Crush";
+						if (damageType1.equalsIgnoreCase("Piercing"))
+							damageType1 = "Pierce";
+						if (damageType1.equalsIgnoreCase("Slashing"))
+							damageType1 = "Slash";
+						
+						if (damageType2.equalsIgnoreCase("Crushing"))
+							damageType2 = "Crush";
+						if (damageType2.equalsIgnoreCase("Piercing"))
+							damageType2 = "Pierce";
+						if (damageType2.equalsIgnoreCase("Slashing"))
+							damageType2 = "Slash";
+						
+						if (damageType3.equalsIgnoreCase("Crushing"))
+							damageType3 = "Crush";
+						if (damageType3.equalsIgnoreCase("Piercing"))
+							damageType3 = "Pierce";
+						if (damageType3.equalsIgnoreCase("Slashing"))
+							damageType3 = "Slash";
+						DamageType dt = getDamageType(damageType1);
+						if (dt != null)
+							EffectsBase.EffectDamageTypes.get(eb.token).add(dt);
+							
+						 dt = getDamageType(damageType2);
+						if (dt != null)
+							EffectsBase.EffectDamageTypes.get(eb.token).add(dt);
+						 dt = getDamageType(damageType3);
+						if (dt != null)
+							EffectsBase.EffectDamageTypes.get(eb.token).add(dt);
+						break;
+					case Attack:
+						eb.cancelOnAttack = true;
+						break;
+					case AttackSwing:
+						eb.cancelOnAttackSwing = true;
+						break;
+					case Cast:
+						eb.cancelOnCast = true;
+						break;
+					case CastSpell:
+						eb.cancelOnCastSpell = true;
+						break;
+					case EquipChange:
+						eb.cancelOnEquipChange = true;
+						break;
+					case Logout:
+						eb.cancelOnLogout = true;
+						break;
+					case Move:
+						eb.cancelOnMove = true;
+						break;
+					case NewCharm:
+						eb.cancelOnNewCharm = true;
+						break;
+					case Sit:
+						eb.cancelOnSit = true;
+						break;
+					case TerritoryClaim:
+						eb.cancelOnTerritoryClaim = true;
+						break;
+					case UnEquip:
+						eb.cancelOnUnEquip = true;
+						break;
+					}
+				}
+			
+			rs.close();
+		} catch (Exception e) {
+			Logger.error( e);
+		} finally {
+			ps.release();
+		}
+		
+	}
+	
+	public float getDamageAmount(int trains) {
+		if (useRampAdd)
+			return (amount + (amountRamp * trains));
+		else
+			return (amount * (1 + (amountRamp * trains)));
+	}
+
+	public boolean damageTypeSpecific() {
+		
+		return EffectsBase.EffectDamageTypes.containsKey(this.token);
+		
+	}
+
+	public boolean containsDamageType(DamageType dt) {
+		if (!EffectsBase.EffectDamageTypes.containsKey(this.token))
+			return false;
+		return EffectsBase.EffectDamageTypes.get(this.token).contains(dt);
+	}
+
+	private static DamageType getDamageType(String name) {
+		try {
+			switch (name) {
+			case "Crushing":
+				name = "Crush";
+				break;
+			case "Slashing":
+				name = "Slash";
+				break;
+			case "Piercing":
+				name = "Pierce";
+				break;
+			}
+			if (name.isEmpty())
+				return null;
+			else
+				return DamageType.valueOf(name);
+		} catch (Exception e) {
+			Logger.error(name);
+			return null;
+		}
+	}
+
+	// public String getName() {
+	// return this.name;
+	// }
+
+	public int getUUID() {
+		return this.UUID;
+	}
+
+	public String getIDString() {
+		return this.IDString;
+	}
+
+	public int getToken() {
+		return this.token;
+	}
+
+	public ConcurrentHashMap<String, Boolean> getSourceTypes() {
+		return this.sourceTypes;
+	}
+
+	public HashSet<AbstractEffectModifier> getModifiers() {
+		
+		if (EffectsBase.modifiersMap.containsKey(this.IDString) == false)
+			return EffectsBase.DefaultModifiers;
+		
+		return EffectsBase.modifiersMap.get(this.IDString);
+	}
+
+	public boolean isItemEffect() {
+		return this.isItemEffect;
+	}
+
+	public boolean isSpireEffect() {
+		return this.isSpireEffect;
+	}
+
+	public boolean ignoreMod() {
+		return this.ignoreMod;
+	}
+
+	public boolean dontSave() {
+		return this.dontSave;
+	}
+
+	public boolean isPrefix() {
+		return this.isPrefix;
+	}
+
+	public boolean isSuffix() {
+		return this.isSuffix;
+	}
+
+	public void startEffect(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) {
+
+
+
+		// Add SourceTypes for dispel
+	
+		if (this.token != 0) {
+			if (effect == null) {
+				Logger.error("AbstractEffectModifier.applyEffectModifier: missing FinishEffectTimeJob");
+				return;
+			}
+			// AbstractWorldObject source = effect.getSource();
+			if (source == null) {
+				Logger.error( "AbstractEffectModifier.applyEffectModifier: missing source");
+				return;
+			}
+			PowersBase pb = effect.getPower();
+			if (pb == null) {
+				Logger.error( "AbstractEffectModifier.applyEffectModifier: missing power");
+				return;
+			}
+			ActionsBase ab = effect.getAction();
+			if (ab == null) {
+				Logger.error( "AbstractEffectModifier.applyEffectModifier: missing action");
+				return;
+			}
+
+			//don't send effect if dead, except for death shroud
+			if (!awo.isAlive()) {
+				if (pb.getToken() != 1672601862)
+					return;
+			}
+
+
+			if (!effect.skipSendEffect()) {
+				//				float duration = (pb.isChant()) ? pb.getChantDuration() * 1000 : ab.getDuration(trains);
+				float duration = ab.getDurationInSeconds(trains);
+				if (pb.getToken() == 1672601862){
+
+					Effect eff = awo.getEffects().get("DeathShroud");
+
+
+
+
+					if (eff != null) {
+						JobContainer jc = eff.getJobContainer();
+
+
+						if (jc != null){
+							duration = jc.timeOfExection() - System.currentTimeMillis();
+							duration *= .001f;
+						}
+					}
+				}
+				
+				
+				
+				if (duration > 0f) {
+					int removeToken = this.token;
+					ApplyEffectMsg pum = new ApplyEffectMsg();
+					if (effect.getAction() != null)
+					if ( effect.getAction().getPowerAction() != null
+							&& PowersManager.ActionTokenByIDString.containsKey(effect.getAction().getPowerAction().getIDString()))
+						try{
+							removeToken = PowersManager.ActionTokenByIDString.get(effect.getAction().getPowerAction().getIDString());
+						}catch(Exception e){
+							removeToken = this.token;
+						}
+						
+						pum.setEffectID(removeToken);
+					pum.setSourceType(source.getObjectType().ordinal());
+					pum.setSourceID(source.getObjectUUID());
+					pum.setTargetType(awo.getObjectType().ordinal());
+					pum.setTargetID(awo.getObjectUUID());
+					pum.setNumTrains(trains);
+					pum.setDuration((int) duration);
+					//					pum.setDuration((pb.isChant()) ? (int)pb.getChantDuration() : ab.getDurationInSeconds(trains));
+					pum.setPowerUsedID(pb.getToken());
+					pum.setPowerUsedName(pb.getName());
+					DispatchMessage.sendToAllInRange(awo, pum);
+				}
+
+				if (awo.getObjectType().equals(GameObjectType.Item)) {
+					if (source.getCharItemManager() != null) {
+						source.getCharItemManager().updateInventory();
+					}
+				}
+			}
+
+			// call modifiers to do their job
+			if (!effect.skipApplyEffect()) {
+				for (AbstractEffectModifier em : this.getModifiers())
+					em.applyEffectModifier(source, awo, trains, effect);
+			}
+		}
+	}
+
+	// Send end effect message to client
+	public void endEffect(AbstractWorldObject source, AbstractWorldObject awo, int trains, PowersBase pb, AbstractEffectJob effect) {
+		if (awo == null) {
+			Logger.error("endEffect(): Null AWO object passed in.");
+			return;
+		}
+		if (pb == null) {
+			Logger.error("endEffect(): Null PowerBase object passed in.");
+			return;
+		}
+		if (!effect.skipCancelEffect() && !effect.isNoOverwrite()) {
+			
+			int sendToken = this.token;
+			
+			if (effect.getAction() != null)
+			if ( effect.getAction().getPowerAction() != null
+					&& PowersManager.ActionTokenByIDString.containsKey(effect.getAction().getPowerAction().getIDString()))
+				try{
+					sendToken = PowersManager.ActionTokenByIDString.get(effect.getAction().getPowerAction().getIDString());
+				}catch(Exception e){
+					sendToken = this.token;
+				}
+			ApplyEffectMsg pum = new ApplyEffectMsg();
+			pum.setEffectID(sendToken);
+			if (source != null) {
+				pum.setSourceType(source.getObjectType().ordinal());
+				pum.setSourceID(source.getObjectUUID());
+			} else {
+				pum.setSourceType(0);
+				pum.setSourceID(0);
+			}
+			pum.setTargetType(awo.getObjectType().ordinal());
+			pum.setTargetID(awo.getObjectUUID());
+			pum.setUnknown02(2);
+			pum.setNumTrains(0);
+			pum.setDuration(-1);
+			pum.setPowerUsedID(pb.getToken());
+			pum.setPowerUsedName(pb.getName());
+			DispatchMessage.sendToAllInRange(awo, pum);
+
+		}
+	}
+
+	public void endEffectNoPower(int trains, AbstractEffectJob effect) {
+
+		AbstractWorldObject source = effect.getSource();
+
+		if (source == null)
+			return;
+
+		if (!effect.skipCancelEffect() && !effect.isNoOverwrite()) {
+			ApplyEffectMsg pum = new ApplyEffectMsg();
+			pum.setEffectID(this.token);
+
+			pum.setSourceType(source.getObjectType().ordinal());
+			pum.setSourceID(source.getObjectUUID());
+			pum.setTargetType(source.getObjectType().ordinal());
+			pum.setTargetID(source.getObjectUUID());
+			pum.setUnknown02(2);
+			pum.setNumTrains(0);
+			pum.setDuration(-1);
+			pum.setUnknown06((byte)1);
+			pum.setEffectSourceType(effect.getEffectSourceType());
+			pum.setEffectSourceID(effect.getEffectSourceID());
+			pum.setPowerUsedID(0);
+			pum.setPowerUsedName(this.name);
+
+			if (source.getObjectType() == GameObjectType.PlayerCharacter){
+				Dispatch dispatch = Dispatch.borrow((PlayerCharacter)source, pum);
+				DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.PRIMARY);
+			}
+		}
+	}
+
+	public void sendEffect(AbstractEffectJob effect, int duration, ClientConnection conn) {
+		if (effect == null && conn != null)
+			return;
+
+		if (conn == null)
+			return;
+		AbstractWorldObject source = effect.getSource();
+		AbstractWorldObject awo = effect.getTarget();
+		int trains = effect.getTrains();
+		if (source == null || awo == null)
+			return;
+
+		if (this.token != 0) {
+			PowersBase pb = effect.getPower();
+			if (pb == null) {
+				Logger.error( "AbstractEffectModifier.applyEffectModifier: missing power");
+				return;
+			}
+			ActionsBase ab = effect.getAction();
+			if (ab == null) {
+				Logger.error("AbstractEffectModifier.applyEffectModifier: missing action");
+				return;
+			}
+
+			//don't send effect if dead, except for death shroud
+			if (!awo.isAlive()) {
+				if (pb.getToken() != 1672601862)
+					return;
+			}
+
+			//duration for damage over times is (total time - (number of ticks x 5 seconds per tick))
+			if (effect instanceof DamageOverTimeJob)
+				duration = ((DamageOverTimeJob)effect).getTickLength();
+
+			//			float dur = (pb.isChant()) ? pb.getChantDuration() * 1000 : ab.getDuration(trains);
+			float dur = ab.getDuration(trains);
+			if (dur > 0f) {
+				ApplyEffectMsg pum = new ApplyEffectMsg();
+				pum.setEffectID(this.token);
+				pum.setSourceType(source.getObjectType().ordinal());
+				pum.setSourceID(source.getObjectUUID());
+				pum.setTargetType(awo.getObjectType().ordinal());
+				pum.setTargetID(awo.getObjectUUID());
+				pum.setNumTrains(trains);
+				pum.setDuration(duration);
+				pum.setPowerUsedID(pb.getToken());
+				pum.setPowerUsedName(pb.getName());
+
+				Dispatch dispatch = Dispatch.borrow(conn.getPlayerCharacter(), pum);
+				DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.PRIMARY);
+
+			}
+		}
+	}
+
+	public void sendEffectNoPower(AbstractEffectJob effect, int duration, ClientConnection conn) {
+
+		if (effect == null && conn != null)
+			return;
+
+		if (conn == null)
+			return;
+
+		AbstractWorldObject source = effect.getSource();
+		AbstractWorldObject awo = effect.getTarget();
+		int trains = effect.getTrains();
+
+		if (source == null || awo == null)
+			return;
+
+		if (this.token != 0) {
+			//don't send effect if dead, except for death shroud
+			if (!awo.isAlive()) {
+				return;
+			}
+
+			//duration for damage over times is (total time - (number of ticks x 5 seconds per tick))
+			if (effect instanceof DamageOverTimeJob)
+				duration = ((DamageOverTimeJob)effect).getTickLength();
+			else if (effect instanceof FinishSpireEffectJob)
+				duration = 45;
+			else if (effect instanceof NoTimeJob)
+				duration = -1;
+
+			//			float dur = (pb.isChant()) ? pb.getChantDuration() * 1000 : ab.getDuration(trains);
+
+			ApplyEffectMsg pum = new ApplyEffectMsg();
+			pum.setEffectID(this.token);
+			pum.setSourceType(source.getObjectType().ordinal());
+			pum.setSourceID(source.getObjectUUID());
+			pum.setTargetType(source.getObjectType().ordinal());
+			pum.setTargetID(source.getObjectUUID());
+			pum.setUnknown06((byte)1);
+			pum.setEffectSourceType(effect.getEffectSourceType());
+			pum.setEffectSourceID(effect.getEffectSourceID());
+			pum.setNumTrains(trains);
+			pum.setDuration(duration);
+			pum.setPowerUsedID(0);
+			pum.setPowerUsedName(this.name);
+
+			Dispatch dispatch = Dispatch.borrow(conn.getPlayerCharacter(), pum);
+			DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.PRIMARY);
+
+		}
+	}
+
+	public boolean containsSource(EffectSourceType sourceType) {
+		if (EffectsBase.effectSourceTypeMap.containsKey(this.token) == false)
+			return false;
+		return EffectsBase.effectSourceTypeMap.get(this.token).contains(sourceType);
+		
+	}
+
+	public boolean cancelOnAttack() {
+		return this.cancelOnAttack;
+	}
+
+	public boolean cancelOnAttackSwing() {
+		return this.cancelOnAttackSwing;
+	}
+
+	public boolean cancelOnCast() {
+		return this.cancelOnCast;
+	}
+
+	public boolean cancelOnCastSpell() {
+		return this.cancelOnCastSpell;
+	}
+
+	public boolean cancelOnEquipChange() {
+		return this.cancelOnEquipChange;
+	}
+
+	public boolean cancelOnLogout() {
+		return this.cancelOnLogout;
+	}
+
+	public boolean cancelOnMove() {
+		return this.cancelOnMove;
+	}
+
+	public boolean cancelOnNewCharm() {
+		return this.cancelOnNewCharm;
+	}
+
+	public boolean cancelOnSit() {
+		return this.cancelOnSit;
+	}
+
+	public boolean cancelOnTakeDamage() {
+		return this.cancelOnTakeDamage;
+	}
+
+	public boolean cancelOnTerritoryClaim() {
+		return this.cancelOnTerritoryClaim;
+	}
+
+	public boolean cancelOnUnEquip() {
+		return this.cancelOnUnEquip;
+	}
+
+	//For Debugging purposes.
+	public void setToken(int token) {
+		this.token = token;
+	}
+
+	public static String getItemEffectsByName(String string) {
+		if (EffectsBase.itemEffectsByName.containsKey(string))
+			return EffectsBase.itemEffectsByName.get(string);
+		return "";
+	}
+
+	public static void addItemEffectsByName(String name, String ID) {
+		EffectsBase.itemEffectsByName.put(name, ID);
+	}
+
+	public String getDamageTypes() {
+		String text = "";
+		if (!EffectsBase.EffectDamageTypes.containsKey(this.token))
+			return text;
+		for (DamageType type: EffectsBase.EffectDamageTypes.get(this.token)) {
+			text += type.name() + ' ';
+		}
+		return text;
+	}
+
+	public String getName() {
+		
+		return name;
+	}
+	
+	public void setName(String name){
+		this.name = name;
+	}
+
+	public float getValue() {
+		return value;
+	}
+	
+	public void setValue(float Value){
+		this.value = Value;
+	}
+	public ConcurrentHashMap<ItemBase,Integer> getResourcesForEffect() {
+		if (this.resourceCosts.isEmpty()){
+			ArrayList<EffectsResourceCosts> effectsCostList = DbManager.EffectsResourceCostsQueries.GET_ALL_EFFECT_RESOURCES(this.IDString);
+			for (EffectsResourceCosts erc : effectsCostList){
+				this.resourceCosts.put(ItemBase.getItemBase(erc.getResourceID()), erc.getAmount());
+			}
+		}
+		return this.resourceCosts;
+	}
+
+
+}
diff --git a/src/engine/powers/FailCondition.java b/src/engine/powers/FailCondition.java
new file mode 100644
index 00000000..5ee49f51
--- /dev/null
+++ b/src/engine/powers/FailCondition.java
@@ -0,0 +1,118 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.powers;
+
+import engine.objects.PreparedStatementShared;
+import org.pmw.tinylog.Logger;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+
+
+public class FailCondition {
+
+	private String IDString;
+	private Boolean forPower;
+	private String type;
+	private float amount;
+	private float ramp;
+	private boolean rampAdd;
+
+	// private String damageType1;
+	// private String damageType2;
+	// private String damageType3;
+
+	/**
+	 * No Table ID Constructor
+	 */
+	public FailCondition() {
+
+	}
+
+	/**
+	 * ResultSet Constructor
+	 */
+	public FailCondition(ResultSet rs) throws SQLException {
+
+		this.IDString = rs.getString("IDString");
+		this.forPower = (rs.getString("powerOrEffect").equals("Power")) ? true : false;
+		this.type = rs.getString("type");
+		this.amount = rs.getFloat("amount");
+		this.ramp = rs.getFloat("ramp");
+		this.rampAdd = (rs.getInt("useAddFormula") == 1) ? true : false;
+		// this.damageType1 = rs.getString("damageType1");
+		// this.damageType2 = rs.getString("damageType2");
+		// this.damageType3 = rs.getString("damageType3");
+	}
+
+	public static ArrayList<FailCondition> getAllFailConditions() {
+		PreparedStatementShared ps = null;
+		ArrayList<FailCondition> out = new ArrayList<>();
+		try {
+			ps = new PreparedStatementShared("SELECT * FROM failconditions");
+			ResultSet rs = ps.executeQuery();
+			while (rs.next()) {
+				FailCondition toAdd = new FailCondition(rs);
+				out.add(toAdd);
+			}
+			rs.close();
+		} catch (Exception e) {
+			Logger.error( e);
+
+		} finally {
+			ps.release();
+		}
+		return out;
+	}
+
+	public String getIDString() {
+		return this.IDString;
+	}
+
+	public String getType() {
+		return this.type;
+	}
+
+	public boolean forPower() {
+		return this.forPower;
+	}
+
+	public float getAmount() {
+		return this.amount;
+	}
+
+	public float getRamp() {
+		return this.ramp;
+	}
+
+	public float getAmountForTrains(float trains) {
+		if (this.rampAdd)
+			return this.amount + (this.ramp * trains);
+		else
+			return this.amount * (1 + (this.ramp * trains));
+	}
+
+	public boolean useRampAdd() {
+		return this.rampAdd;
+	}
+
+	// public String getDamageType1() {
+	// return this.damageType1;
+	// }
+
+	// public String getDamageType2() {
+	// return this.damageType2;
+	// }
+
+	// public String getDamageType3() {
+	// return this.damageType3;
+	// }
+}
diff --git a/src/engine/powers/PowerPrereq.java b/src/engine/powers/PowerPrereq.java
new file mode 100644
index 00000000..b280e878
--- /dev/null
+++ b/src/engine/powers/PowerPrereq.java
@@ -0,0 +1,105 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.powers;
+
+import engine.objects.PreparedStatementShared;
+import org.pmw.tinylog.Logger;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.HashMap;
+
+
+public class PowerPrereq {
+
+	private String effect;
+	private String message;
+	private boolean mainHand;
+	private boolean required;
+
+	/**
+	 * No Table ID Constructor
+	 */
+	public PowerPrereq() {
+
+	}
+
+	/**
+	 * ResultSet Constructor
+	 */
+	public PowerPrereq(ResultSet rs, int type) throws SQLException {
+
+		//		this.IDString = rs.getString("IDString");
+		if (type == 1) {
+			this.effect = rs.getString("messageone");
+			this.message = rs.getString("messagetwo");
+			this.mainHand = false;
+			this.required = false;
+		} else if (type == 2) {
+			String sl = rs.getString("messageone");
+			if (sl.equals("RHELD"))
+				this.mainHand = true;
+			else if (sl.equals("LHELD"))
+				this.mainHand = false;
+			this.effect = "";
+			this.message = rs.getString("messagetwo");
+			this.required = (rs.getInt("required") == 1) ? true : false;
+		} else { //targetEffectPrereq
+			this.effect = rs.getString("messageone");
+			this.message = "";
+			this.mainHand = false;
+			this.required = (rs.getInt("required") == 1) ? true : false;
+		}
+	}
+
+	public static void getAllPowerPrereqs(HashMap<String, PowersBase> powers) {
+		PreparedStatementShared ps = null;
+		try {
+			ps = new PreparedStatementShared("SELECT * FROM static_power_powercastprereq");
+			ResultSet rs = ps.executeQuery();
+			int type; String IDString; PowerPrereq toAdd; PowersBase pb;
+			while (rs.next()) {
+				IDString = rs.getString("IDString");
+				pb = powers.get(IDString);
+				if (pb != null) {
+					type = rs.getInt("Type");
+					toAdd = new PowerPrereq(rs, type);
+					if (type == 1)
+						pb.getEffectPrereqs().add(toAdd);
+					else if (type == 2)
+						pb.getEquipPrereqs().add(toAdd);
+					else
+						pb.getTargetEffectPrereqs().add(toAdd);
+				}
+			}
+			rs.close();
+		} catch (Exception e) {
+			Logger.error( e.toString());
+		} finally {
+			ps.release();
+		}
+	}
+
+	public String getEffect() {
+		return this.effect;
+	}
+
+	public String getMessage() {
+		return this.message;
+	}
+
+	public boolean mainHand() {
+		return this.mainHand;
+	}
+
+	public boolean isRequired() {
+		return this.required;
+	}
+}
diff --git a/src/engine/powers/PowersBase.java b/src/engine/powers/PowersBase.java
new file mode 100644
index 00000000..89e4aa37
--- /dev/null
+++ b/src/engine/powers/PowersBase.java
@@ -0,0 +1,700 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.powers;
+
+import engine.Enum.PowerCategoryType;
+import engine.Enum.PowerTargetType;
+import engine.objects.PreparedStatementShared;
+import org.pmw.tinylog.Logger;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.HashMap;
+
+public class PowersBase {
+
+	public int UUID;
+	public String name;
+	public int token;
+	public String IDString;
+	public String category;
+	public int skillID;
+	public float range;
+	public float cost;
+	public float costRamp;
+	public float castTime;
+	public float castTimeRamp;
+	public float cooldown;
+	public float recycleTime;
+	public float recycleRamp;
+	public int maxTrains;
+	public float hateValue;
+	public float hateRamp;
+	public String monsterTypePrereq; // target limited to these types
+	public String skillName;
+	public float weaponRange = 15f;
+
+	// aoe related
+	public boolean isAOE = true;
+	public boolean useCone = false;
+	public boolean usePointBlank = false;
+	public boolean useSphere = false;
+	public float radius;
+	public byte groupReq; // who the spell won't hit
+	public int maxNumMobTargets;
+	public int maxNumPlayerTargets;
+
+	// chant related
+	public float chantDuration;
+	public int chantIterations;
+
+	// valid target types from targetType field
+	public boolean targetPlayer = false;
+	public boolean targetMob = false;
+	public boolean targetPet = false;
+	public boolean targetNecroPet = false;
+	public boolean targetSelf = false;
+	public boolean targetWeapon = false;
+	public boolean targetCorpse = false;
+	public boolean targetBuilding = false;
+	public boolean targetGroup = false;
+	public boolean targetGuildLeader = false;
+	public boolean targetJewelry = false;
+	public boolean targetArmor = false;
+	public boolean targetItem = false;
+
+
+	// flags
+	public boolean isCasterFriendly = false; // from groupReq
+	public boolean isGroupFriendly = false; // from groupReq
+	public boolean isGroupOnly = false; // from groupReq
+	public boolean mustHitPets = false; // from groupReq
+	public boolean isNationFriendly = false; // from groupReq
+	public boolean targetFromLastTarget = false; // from unknown06
+	public boolean targetFromSelf = false; // from unknown06
+	public boolean targetFromName = false; // from unknown06
+	public boolean targetFromNearbyMobs = false; // from unknown06
+	public boolean useHealth = false; // from costType
+	public boolean useMana = false; // from costType
+	public boolean useStamina = false; // from costType
+	public boolean isSpell = true; // from skillOrSpell field
+	public boolean allowedInCombat = false; // from combat field
+	public boolean allowedOutOfCombat = false; // from combat field
+	public boolean regularPlayerCanCast = false; // from grantOverrideVar
+	public boolean hateRampAdd = true; // 1 bit flag
+	public boolean costRampAdd = true; // 2 bit flag
+	public boolean recycleRampAdd = true; // 4 bit flag
+	public boolean initRampAdd = true; // 8 bit flag
+	public boolean canCastWhileMoving = false; // 16 bit flag
+	public boolean canCastWhileFlying = false; // 32 bit flag
+	public boolean isChant = false; // 64 bit flag
+	public boolean losCheck = false; // 128 bit flag
+	public boolean sticky = false; // 256 bit flag
+	public boolean isAdminPower = false; // 512 bit flag
+	public boolean requiresHitRoll = false; // 1024 bit flag
+	public boolean isWeaponPower = false; // from category
+	public boolean isHeal = false; //from category
+	public boolean isTrack = false; //from category
+	public boolean isHarmful = true;
+	public boolean vampDrain = false;
+
+	public boolean cancelOnCastSpell = false;
+	public boolean cancelOnTakeDamage = false;
+
+	public final ArrayList<String> monsterTypeRestrictions = new ArrayList<>();
+	public final ArrayList<ActionsBase> actions = new ArrayList<>();
+	public final ArrayList<PowerPrereq> effectPrereqs = new ArrayList<>();
+	public final ArrayList<PowerPrereq> targetEffectPrereqs = new ArrayList<>();
+	public final ArrayList<PowerPrereq> equipPrereqs = new ArrayList<>();
+	
+	public PowerTargetType targetType;
+	public PowerCategoryType powerCategory;
+	public String description;
+
+	/**
+	 * No Table ID Constructor
+	 */
+	public PowersBase() {
+
+	}
+
+	/**
+	 * ResultSet Constructor
+	 */
+	public PowersBase(ResultSet rs) throws SQLException {
+
+		this.UUID = rs.getInt("ID");
+		this.name = rs.getString("name").trim();
+		this.token = rs.getInt("token");
+		this.skillName = rs.getString("skillName").trim();
+		this.IDString = rs.getString("IDString").trim();
+		this.isSpell = (rs.getString("skillOrSpell").equals("SPELL")) ? true : false;
+		this.skillID = rs.getInt("skillID");
+		this.range = rs.getFloat("range");
+		this.description = (rs.getString("description")).trim().replace("\r\n ", "");
+		String ct = rs.getString("costType").trim();
+		if (ct.equals("HEALTH"))
+			this.useHealth = true;
+		else if (ct.equals("MANA"))
+			this.useMana = true;
+		else if (ct.equals("STAMINA"))
+			this.useStamina = true;
+		ct = rs.getString("targetType").trim();
+		if (ct.equals("BUILDING"))
+			this.targetBuilding = true;
+		else if (ct.equals("CORPSE"))
+			this.targetCorpse = true;
+		else if (ct.equals("GROUP"))
+			this.targetGroup = true;
+		else if (ct.equals("GUILDLEADER"))
+			this.targetGuildLeader = true;
+		else if (ct.equals("MOBILE")) {
+			this.targetMob = true;
+			this.targetPet = true; // sure on this one?
+		} else if (ct.equals("PC"))
+			this.targetPlayer = true;
+		else if (ct.equals("SELF"))
+			this.targetSelf = true;
+		else if (ct.equals("PET"))
+			this.targetPet = true;
+		else if (ct.equals("NECROPET"))
+			this.targetNecroPet = true;
+		else if (ct.equals("ARMOR"))
+			this.targetArmor = true;
+		else if (ct.equals("WEAPON"))
+			this.targetWeapon = true;
+		else if (ct.equals("JEWELRY"))
+			this.targetJewelry = true;
+		else if (ct.equals("ITEM")) {
+			this.targetItem = true;
+			this.targetJewelry = true;
+			this.targetArmor = true;
+			this.targetWeapon = true;
+		} else if (ct.equals("ARMORWEAPONJEWELRY")) {
+			this.targetArmor = true;
+			this.targetWeapon = true;
+			this.targetJewelry = true;
+		} else if (ct.equals("PCMOBILE")) {
+			this.targetPlayer = true;
+			this.targetMob = true;
+			this.targetPet = true; // sure on this one?
+		} else if (ct.equals("WEAPONARMOR")) {
+			this.targetWeapon = true;
+			this.targetArmor = true;
+		} else {
+			Logger.info("Missed " + ct + " targetType");
+		}
+		String cat = rs.getString("category").trim();
+		this.category = cat;
+		
+		
+		if (cat.isEmpty())
+			this.powerCategory = PowerCategoryType.NONE;
+		else
+		this.powerCategory = PowerCategoryType.valueOf(cat.replace("-", ""));
+		
+		
+		
+		
+		if (cat.equals("WEAPON")) {
+			this.isWeaponPower = true;
+			this.isHarmful = false;
+			if (this.skillName.equals("Bow") || this.skillName.equals("Crossbow") || this.skillName.equals("Archery"))
+				this.weaponRange = 1000f;
+			else if (this.skillName.equals("Throwing"))
+				this.weaponRange = 60f;
+			else
+				this.weaponRange = 15f;
+		} else if (cat.equals("HEAL") || cat.equals("GROUPHEAL")) {
+			this.isHeal = true;
+			this.isHarmful = false;
+		} else if (cat.equals("TRACK")) {
+			this.isTrack = true;
+			this.isHarmful = false;
+		} else if (cat.equals("AE") || cat.equals("AEDAMAGE") ||
+				cat.equals("BREAKFLY") ||
+				cat.equals("DAMAGE") || cat.equals("DEBUFF") ||
+				cat.equals("MOVE") || cat.equals("SPECIAL") ||
+				cat.equals("SPIREDISABLE"))
+			this.isHarmful = true;
+		else if (cat.equals("CHANT")) {
+			this.isHarmful = ct.equals("MOBILE") || ct.equals("PC") || ct.equals("PCMOBILE");
+		} else if (cat.equals("DISPEL")) {
+			//TODO this needs broken down better later
+			this.isHarmful = false;
+		} else if (cat.isEmpty()) {
+			if (ct.equals("MOBILE") || ct.equals("PCMOBILE"))
+				this.isHarmful = true;
+			else if (ct.equals("PC")) {
+				this.isHarmful = this.token != 429607195 && this.token != 429425915;
+			} else
+				this.isHarmful = false;
+		} else
+			this.isHarmful = false;
+
+		if (cat.equals("VAMPDRAIN")) {
+			this.vampDrain = true;
+			this.isHarmful = true;
+		}
+
+		this.cost = rs.getFloat("cost");
+		this.costRamp = rs.getFloat("costRamp");
+		this.castTime = rs.getFloat("castTime");
+		this.castTimeRamp = rs.getFloat("initRamp");
+		this.cooldown = rs.getFloat("cooldown");
+		this.recycleTime = rs.getFloat("recycleTime");
+		this.recycleRamp = rs.getFloat("recycleRamp");
+		this.maxTrains = rs.getInt("maxTrains");
+		this.hateValue = rs.getFloat("hateValue");
+		this.hateRamp = rs.getFloat("hateRamp");
+		ct = rs.getString("unknown06").trim();
+		if (this.targetSelf) {
+		} else if (ct.equals("CLICK"))
+			if (!this.targetGroup)
+			this.targetFromLastTarget = true;
+		else if (ct.equals("NAME"))
+			this.targetFromName = true;
+		else if (ct.equals("NEARBYMOBS"))
+			this.targetFromNearbyMobs = true;
+		this.monsterTypePrereq = rs.getString("monsterTypePrereqs").trim();
+		ct = rs.getString("radiusType").trim();
+		if (ct.equals("CONE"))
+			this.useCone = true;
+		else if (ct.equals("POINTBLANK"))
+			this.usePointBlank = true;
+		else if (ct.equals("SPHERE"))
+			this.useSphere = true;
+		else
+			this.isAOE = false;
+		this.radius = rs.getFloat("radius");
+		ct = rs.getString("groupReq").trim();
+		if (ct.equals("CASTER"))
+			this.isCasterFriendly = true;
+		else if (ct.equals("GROUP")) {
+			this.isGroupFriendly = true;
+			this.isCasterFriendly = true;
+		}
+		else if (ct.equals("ALLBUTGROUP"))
+			this.isGroupOnly = true;
+		else if (ct.equals("ALLBUTPETS"))
+			this.mustHitPets = true;
+		else if (ct.equals("NATION")) {
+			this.isNationFriendly = true;
+			this.isCasterFriendly = true;
+		}
+		this.maxNumMobTargets = rs.getInt("maxNumMobTargets");
+		this.maxNumPlayerTargets = rs.getInt("maxNumPlayerTargets");
+		this.chantDuration = rs.getFloat("chantDuration");
+		this.chantIterations = rs.getInt("chantIterations");
+		ct = rs.getString("combat").trim();
+		if (ct.equals("COMBAT"))
+			this.allowedInCombat = true;
+		else if (ct.equals("NONCOMBAT"))
+			this.allowedOutOfCombat = true;
+		else if (ct.equals("BOTH")) {
+			this.allowedInCombat = true;
+			this.allowedOutOfCombat = true;
+		}
+		ct = rs.getString("grantOverideVar").trim();
+		if (ct.equals("PGOV_PLAYER"))
+			this.regularPlayerCanCast = true;
+		int flags = rs.getInt("flags");
+		if ((flags & 1) == 0)
+			this.hateRampAdd = false;
+		if ((flags & 2) == 0)
+			this.costRampAdd = false;
+		if ((flags & 4) == 0)
+			this.recycleRampAdd = false;
+		if ((flags & 8) == 0)
+			this.initRampAdd = false;
+		if ((flags & 16) != 0)
+			this.canCastWhileMoving = true;
+		if ((flags & 32) != 0)
+			this.canCastWhileFlying = true;
+		if ((flags & 64) != 0)
+			this.isChant = true;
+		if ((flags & 128) != 0)
+			this.losCheck = true;
+		if ((flags & 256) != 0)
+			this.sticky = true;
+		if ((flags & 512) != 0)
+			this.isAdminPower = true;
+		if ((flags & 1024) != 0)
+			this.requiresHitRoll = true;
+		ct = rs.getString("monsterTypeRestrict1").trim();
+		if (!ct.isEmpty())
+			this.monsterTypeRestrictions.add(ct);
+		ct = rs.getString("monsterTypeRestrict2").trim();
+		if (!ct.isEmpty())
+			this.monsterTypeRestrictions.add(ct);
+		ct = rs.getString("monsterTypeRestrict3").trim();
+		if (!ct.isEmpty())
+			this.monsterTypeRestrictions.add(ct);
+	}
+
+	public static ArrayList<PowersBase> getAllPowersBase() {
+		PreparedStatementShared ps = null;
+		ArrayList<PowersBase> out = new ArrayList<>();
+		try {
+			ps = new PreparedStatementShared("SELECT * FROM static_power_powerbase");
+			ResultSet rs = ps.executeQuery();
+			while (rs.next()) {
+				PowersBase toAdd = new PowersBase(rs);
+				out.add(toAdd);
+			}
+			rs.close();
+		} catch (Exception e) {
+			Logger.error( e.toString());
+		} finally {
+			ps.release();
+		}
+		return out;
+	}
+	
+
+	public static void getFailConditions(HashMap<String, PowersBase> powers) {
+		PreparedStatementShared ps = null;
+		try {
+			ps = new PreparedStatementShared("SELECT IDString, type FROM static_power_failcondition where powerOrEffect = 'Power'");
+			ResultSet rs = ps.executeQuery();
+			String type, IDString; PowersBase pb;
+			while (rs.next()) {
+				type = rs.getString("type");
+				IDString = rs.getString("IDString");
+				pb = powers.get(IDString);
+				if (pb != null) {
+					switch (type) {
+					case "CastSpell":
+						pb.cancelOnCastSpell = true;
+						break;
+					case "TakeDamage":
+						pb.cancelOnTakeDamage = true;
+						break;
+					}
+				}else{
+					Logger.error("null power for Grief " + IDString);
+				}
+			}
+			rs.close();
+		} catch (Exception e) {
+			Logger.error( e.toString());
+		} finally {
+			ps.release();
+		}
+	}
+	
+
+	public String getName() {
+		return this.name;
+	}
+
+	public int getMaxTrains() {
+		return this.maxTrains;
+	}
+
+	public int getUUID() {
+		return this.UUID;
+	}
+
+	public String getIDString() {
+		return this.IDString;
+	}
+
+	public int getToken() {
+		if (this.IDString.equals("BLEED-DOT-10.5-RANGE"))
+			return -369682965;
+		return this.token;
+	}
+
+	public int getCastTime(int trains) { // returns cast time in ms
+		if (this.initRampAdd)
+			return (int) ((this.castTime + (this.castTimeRamp * trains)) * 1000);
+		else
+			return (int) ((this.castTime * (1 + (this.castTimeRamp * trains))) * 1000);
+	}
+
+	public int getRecycleTime(int trains) { // returns cast time in ms
+		if (this.recycleRampAdd)
+			return (int) (((this.recycleTime + (this.recycleRamp * trains)) * 1000) + getCastTime(trains));
+		else
+			return (int) (((this.recycleTime * (1 + (this.recycleRamp * trains))) * 1000) + getCastTime(trains));
+	}
+
+	// public ArrayList<FailCondition> getConditions() {
+	// return this.conditions;
+	// }
+
+	public ArrayList<PowerPrereq> getEffectPrereqs() {
+		return this.effectPrereqs;
+	}
+
+	public ArrayList<PowerPrereq> getTargetEffectPrereqs() {
+		return this.targetEffectPrereqs;
+	}
+
+	public ArrayList<PowerPrereq> getEquipPrereqs() {
+		return this.equipPrereqs;
+	}
+
+	public ArrayList<ActionsBase> getActions() {
+		return this.actions;
+	}
+
+	public boolean usePointBlank() {
+		return this.usePointBlank;
+	}
+
+	public float getRadius() {
+		return this.radius;
+	}
+
+	public int getMaxNumMobTargets() {
+		return this.maxNumMobTargets;
+	}
+
+	public int getMaxNumPlayerTargets() {
+		return this.maxNumPlayerTargets;
+	}
+
+	public boolean cancelOnCastSpell() {
+		return this.cancelOnCastSpell;
+	}
+
+	public boolean cancelOnTakeDamage() {
+		return this.cancelOnTakeDamage;
+	}
+
+	public boolean allowedInCombat() {
+		return this.allowedInCombat;
+	}
+
+	public boolean allowedOutOfCombat() {
+		return this.allowedOutOfCombat;
+	}
+
+	public boolean isCasterFriendly() {
+		return this.isCasterFriendly;
+	}
+
+	public boolean isGroupFriendly() {
+		return this.isGroupFriendly;
+	}
+
+	public boolean isNationFriendly() {
+		return this.isNationFriendly;
+	}
+
+	public boolean isGroupOnly() {
+		return this.isGroupOnly;
+	}
+
+	public boolean mustHitPets() {
+		return this.mustHitPets;
+	}
+
+	public boolean targetFromLastTarget() {
+		return this.targetFromLastTarget;
+	}
+
+	public boolean targetFromSelf() {
+		return this.targetFromSelf;
+	}
+
+	public boolean targetFromName() {
+		return this.targetFromName;
+	}
+
+	public boolean targetFromNearbyMobs() {
+		return this.targetFromNearbyMobs;
+	}
+
+	public float getRange() {
+		return this.range;
+	}
+
+	public boolean requiresHitRoll() {
+		return this.requiresHitRoll;
+	}
+
+	public boolean regularPlayerCanCast() {
+		return this.regularPlayerCanCast;
+	}
+
+	public boolean isSpell() {
+		return this.isSpell;
+	}
+
+	public boolean isHarmful() {
+		return this.isHarmful;
+	}
+
+	public boolean targetPlayer() {
+		return this.targetPlayer;
+	}
+
+	public boolean targetMob() {
+		return this.targetMob;
+	}
+
+	public boolean targetPet() {
+		return this.targetPet;
+	}
+
+	public boolean targetNecroPet() {
+		return this.targetNecroPet;
+	}
+
+	public boolean targetSelf() {
+		return this.targetSelf;
+	}
+
+	public boolean targetCorpse() {
+		return this.targetCorpse;
+	}
+
+	public boolean targetBuilding() {
+		return this.targetBuilding;
+	}
+
+	public boolean targetGroup() {
+		return this.targetGroup;
+	}
+
+	public boolean targetGuildLeader() {
+		return this.targetGuildLeader;
+	}
+
+	public boolean targetJewelry() {
+		return this.targetJewelry;
+	}
+
+	public boolean targetArmor() {
+		return this.targetArmor;
+	}
+
+	public boolean targetWeapon() {
+		return this.targetWeapon;
+	}
+
+	public boolean targetItem() {
+		return this.targetItem;
+	}
+
+	public long getCooldown() {
+		return  (long) (this.cooldown * 1000); // return
+		// in ms
+	}
+
+	public boolean useHealth() {
+		return this.useHealth;
+	}
+
+	public boolean useMana() {
+		return this.useMana;
+	}
+
+	public boolean useStamina() {
+		return this.useStamina;
+	}
+
+	public float getCost(int trains) {
+		if (this.costRampAdd)
+			return this.cost + (this.costRamp * trains);
+		else
+			return this.cost * (1 + (this.costRamp * trains));
+
+	}
+
+	public float getHateValue() {
+		return this.hateValue;
+	}
+
+	public float getHateRamp() {
+		return this.hateRamp;
+	}
+
+	public float getHateValue(int trains) {
+		return this.hateValue + (this.hateRamp * trains);
+	}
+
+	public boolean canCastWhileMoving() {
+		return this.canCastWhileMoving;
+	}
+
+	public boolean canCastWhileFlying() {
+		return this.canCastWhileFlying;
+	}
+
+	public boolean isAOE() {
+		return isAOE;
+	}
+
+	public boolean isChant() {
+		return isChant;
+	}
+
+	public int getChantIterations() {
+		return chantIterations;
+	}
+
+	public float getChantDuration() {
+		return chantDuration;
+	}
+
+	public boolean isWeaponPower() {
+		return isWeaponPower;
+	}
+
+	public boolean isHeal() {
+		return isHeal;
+	}
+
+	public boolean isTrack() {
+		return isTrack;
+	}
+
+	public boolean vampDrain() {
+		return vampDrain;
+	}
+
+	public void setCancelOnCastSpell(boolean value) {
+		this.cancelOnCastSpell = value;
+	}
+
+	public void setCancelOnTakeDamage(boolean value) {
+		this.cancelOnTakeDamage = value;
+	}
+
+	public String getSkillName() {
+		return this.skillName;
+	}
+
+	public String getMonsterTypePrereq() {
+		return this.monsterTypePrereq;
+	}
+
+	public String getCategory() {
+		return this.category;
+	}
+
+	public float getWeaponRange() {
+		return this.weaponRange;
+	}
+	
+	public PowerCategoryType getPowerCategoryType(){
+		return this.powerCategory;
+	}
+
+	public String getDescription() {
+		return description;
+	}
+
+}
diff --git a/src/engine/powers/RangeBasedAwo.java b/src/engine/powers/RangeBasedAwo.java
new file mode 100644
index 00000000..b80c654e
--- /dev/null
+++ b/src/engine/powers/RangeBasedAwo.java
@@ -0,0 +1,104 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.powers;
+
+import engine.Enum;
+import engine.Enum.GameObjectType;
+import engine.math.Vector3fImmutable;
+import engine.objects.AbstractCharacter;
+import engine.objects.AbstractWorldObject;
+import engine.objects.PlayerCharacter;
+
+import java.util.HashSet;
+
+
+//This package creates a list of AbstractWorldObjects
+//sorted by range from a specified point.
+public class RangeBasedAwo implements Comparable<RangeBasedAwo> {
+
+	private float range;
+	private AbstractWorldObject awo;
+
+	public RangeBasedAwo(Float range, AbstractWorldObject awo) {
+		super();
+		this.range = range;
+		this.awo = awo;
+	}
+
+	public static HashSet<RangeBasedAwo> createList(HashSet<AbstractWorldObject> awolist, Vector3fImmutable searchLoc) {
+		HashSet<RangeBasedAwo> rbal = new HashSet<>();
+		for (AbstractWorldObject awo: awolist) {
+			RangeBasedAwo rba = new RangeBasedAwo(searchLoc.distance(awo.getLoc()), awo);
+			rbal.add(rba);
+		}
+		return rbal;
+	}
+
+	@Override
+	public int compareTo(RangeBasedAwo obj) throws ClassCastException {
+		return (int)(this.range - obj.range);
+	}
+
+	public static HashSet<AbstractWorldObject> getSortedList(HashSet<AbstractWorldObject> awolist, Vector3fImmutable searchLoc, int maxPlayers, int maxMobs) {
+		int playerCnt = 0;
+		int mobCnt = 0;
+		int maxCnt = (maxPlayers > maxMobs) ? maxPlayers : maxMobs;
+		HashSet<RangeBasedAwo> rbal = RangeBasedAwo.createList(awolist, searchLoc);
+		awolist = new HashSet<>();
+		for (RangeBasedAwo rba : rbal) {
+			if (awolist.size() >= maxCnt)
+				return awolist;
+			AbstractWorldObject awo = rba.awo;
+
+			if (awo.getObjectType().equals(Enum.GameObjectType.PlayerCharacter)) {
+				if (playerCnt < maxPlayers) {
+					awolist.add(awo);
+					playerCnt++;
+				}
+			} else if (awo.getObjectType().equals(Enum.GameObjectType.Mob)) {
+				if (mobCnt < maxMobs) {
+					awolist.add(awo);
+					mobCnt++;
+				}
+			}
+		}
+		return awolist;
+	}
+
+	public static HashSet<AbstractCharacter> getTrackList(HashSet<AbstractWorldObject> awolist, PlayerCharacter pc, int max) {
+		Vector3fImmutable searchLoc = pc.getLoc();
+		int cnt = 0;
+		HashSet<RangeBasedAwo> rbal = RangeBasedAwo.createList(awolist, searchLoc);
+		HashSet<AbstractCharacter> aclist = new HashSet<>();
+		for (RangeBasedAwo rba : rbal) {
+			if (aclist.size() >= max)
+				return aclist;
+			AbstractWorldObject awo = rba.awo;
+			
+			if (awo.getObjectType().equals(GameObjectType.PlayerCharacter))
+				if (((PlayerCharacter)awo).isCSR())
+					continue;
+					
+			if (AbstractWorldObject.IsAbstractCharacter(awo) && !(pc.equals(awo))) {
+				aclist.add((AbstractCharacter)awo);
+				cnt++;
+			}
+		}
+		return aclist;
+	}
+
+	public float getRange() {
+		return this.range;
+	}
+
+	public AbstractWorldObject getAwo() {
+		return this.awo;
+	}
+}
diff --git a/src/engine/powers/effectmodifiers/AbstractEffectModifier.java b/src/engine/powers/effectmodifiers/AbstractEffectModifier.java
new file mode 100644
index 00000000..6a8f385d
--- /dev/null
+++ b/src/engine/powers/effectmodifiers/AbstractEffectModifier.java
@@ -0,0 +1,373 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.powers.effectmodifiers;
+
+import engine.Enum.ModType;
+import engine.Enum.SourceType;
+import engine.gameManager.DbManager;
+import engine.gameManager.PowersManager;
+import engine.jobs.AbstractEffectJob;
+import engine.objects.*;
+import engine.powers.EffectsBase;
+import org.pmw.tinylog.Logger;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.HashSet;
+
+
+public abstract class AbstractEffectModifier {
+
+	protected EffectsBase parent;
+	protected int UUID;
+	protected String IDString;
+	protected String effectType;
+	protected float minMod;
+	protected float maxMod;
+	protected float percentMod;
+	protected float ramp;
+	protected boolean useRampAdd;
+	protected String type;
+	public SourceType sourceType;
+	
+	protected String string1;
+	protected String string2;
+	public ModType modType;
+
+	public AbstractEffectModifier(ResultSet rs) throws SQLException {
+
+		this.UUID = rs.getInt("ID");
+		this.IDString = rs.getString("IDString");
+		this.effectType = rs.getString("modType");
+		this.modType = ModType.GetModType(this.effectType);		
+		this.type = rs.getString("type").replace("\"", "");
+			this.sourceType = SourceType.GetSourceType(this.type.replace(" ", "").replace("-", ""));
+			this.minMod = rs.getFloat("minMod");
+		this.maxMod = rs.getFloat("maxMod");
+		this.percentMod = rs.getFloat("percentMod");
+		this.ramp = rs.getFloat("ramp");
+		this.useRampAdd = (rs.getInt("useRampAdd") == 1) ? true : false;
+		
+		this.string1 = rs.getString("string1");
+		this.string2 = rs.getString("string2");
+	}
+
+	public static ArrayList<AbstractEffectModifier> getAllEffectModifiers() {
+		PreparedStatementShared ps = null;
+		ArrayList<AbstractEffectModifier> out = new ArrayList<>();
+		try {
+			ps = new PreparedStatementShared("SELECT * FROM static_power_effectmod");
+			ResultSet rs = ps.executeQuery();
+		 String IDString;
+			AbstractEffectModifier aem = null;
+			while (rs.next()) {
+				IDString = rs.getString("IDString");
+				int token = DbManager.hasher.SBStringHash(IDString);
+
+				EffectsBase eb = PowersManager.getEffectByIDString(IDString);
+
+				ModType modifier = ModType.GetModType(rs.getString("modType"));
+
+				//combine item prefix and suffix effect modifiers
+				switch (modifier){
+				case AdjustAboveDmgCap:
+					aem = new AdjustAboveDmgCapEffectModifier(rs);
+					break;
+				case Ambidexterity:
+					aem = new AmbidexterityEffectModifier(rs);
+					break;
+				case AnimOverride:
+					break;
+				case ArmorPiercing:
+					aem = new ArmorPiercingEffectModifier(rs);
+					break;
+				case AttackDelay:
+					aem = new AttackDelayEffectModifier(rs);
+					break;
+				case Attr:
+					aem = new AttributeEffectModifier(rs);
+					break;
+				case BlackMantle:
+					aem = new BlackMantleEffectModifier(rs);
+					break;
+				case BladeTrails:
+					aem = new BladeTrailsEffectModifier(rs);
+  					break;
+				case Block:
+					aem = new BlockEffectModifier(rs);
+					break;
+				case BlockedPowerType:
+					aem = new BlockedPowerTypeEffectModifier(rs);
+					break;
+				case CannotAttack:
+					aem = new CannotAttackEffectModifier(rs);
+					break;
+				case CannotCast:
+					aem = new CannotCastEffectModifier(rs);
+					break;
+				case CannotMove:
+					aem = new CannotMoveEffectModifier(rs);
+					break;
+				case CannotTrack:
+					aem = new CannotTrackEffectModifier(rs);
+					break;
+				case Charmed:
+					aem = new CharmedEffectModifier(rs);
+					break;
+				case ConstrainedAmbidexterity:
+					aem = new ConstrainedAmbidexterityEffectModifier(rs);
+					break;
+				case DamageCap:
+					aem = new DamageCapEffectModifier(rs);
+					break;
+				case DamageShield:
+					aem = new DamageShieldEffectModifier(rs);
+					break;
+				case DCV:
+					aem = new DCVEffectModifier(rs);
+					break;
+				case Dodge:
+					aem = new DodgeEffectModifier(rs);
+					break;
+				case DR:
+					aem = new DREffectModifier(rs);
+					break;
+				case Durability:
+					aem = new DurabilityEffectModifier(rs);
+					break;
+				case ExclusiveDamageCap:
+					aem = new ExclusiveDamageCapEffectModifier(rs);
+					break;
+				case Fade:
+					aem = new FadeEffectModifier(rs);
+					break;
+				case Fly:
+					aem = new FlyEffectModifier(rs);
+					break;
+				case Health:
+					aem = new HealthEffectModifier(rs);
+					break;
+				case HealthFull:
+					aem = new HealthFullEffectModifier(rs);
+					break;
+				case HealthRecoverRate:
+					aem = new HealthRecoverRateEffectModifier(rs);
+					break;
+				case IgnoreDamageCap:
+					aem = new IgnoreDamageCapEffectModifier(rs);
+					break;
+				case IgnorePassiveDefense:
+					aem = new IgnorePassiveDefenseEffectModifier(rs);
+					break;
+				case ImmuneTo:
+					aem = new ImmuneToEffectModifier(rs);
+					break;
+				case ImmuneToAttack:
+					aem = new ImmuneToAttackEffectModifier(rs);
+					break;
+				case ImmuneToPowers:
+					aem = new ImmuneToPowersEffectModifier(rs);
+					break;
+				case Invisible:
+					aem = new InvisibleEffectModifier(rs);
+					break;
+				case ItemName:
+					aem = new ItemNameEffectModifier(rs);
+					if ((((ItemNameEffectModifier)aem).name.isEmpty()))
+							break;
+					if (eb != null)
+						eb.setName((((ItemNameEffectModifier)aem).name));
+					break;
+				case Mana:
+					aem = new ManaEffectModifier(rs);
+					break;
+				case ManaFull:
+					aem = new ManaFullEffectModifier(rs);
+					break;
+				case ManaRecoverRate:
+					aem = new ManaRecoverRateEffectModifier(rs);
+					break;
+				case MaxDamage:
+					aem = new MaxDamageEffectModifier(rs);
+					break;
+				case MeleeDamageModifier:
+					aem = new MeleeDamageEffectModifier(rs);
+					break;
+				case MinDamage:
+					aem = new MinDamageEffectModifier(rs);
+					break;
+				case NoMod:
+					aem = new NoModEffectModifier(rs);
+					break;
+				case OCV:
+					aem = new OCVEffectModifier(rs);
+					break;
+				case Parry:
+					aem = new ParryEffectModifier(rs);
+					break;
+				case PassiveDefense:
+					aem = new PassiveDefenseEffectModifier(rs);
+				case PowerCost:
+					aem = new PowerCostEffectModifier(rs);
+					break;
+				case PowerCostHealth:
+					aem = new PowerCostHealthEffectModifier(rs);
+					break;
+				case PowerDamageModifier:
+					aem = new PowerDamageEffectModifier(rs);
+					break;
+				case ProtectionFrom:
+					aem = new ProtectionFromEffectModifier(rs);
+					break;
+				case Resistance:
+					aem = new ResistanceEffectModifier(rs);
+					break;
+				case ScaleHeight:
+					aem = new ScaleHeightEffectModifier(rs);
+					break;
+				case ScaleWidth:
+					aem = new ScaleWidthEffectModifier(rs);
+					break;
+				case ScanRange:
+					aem = new ScanRangeEffectModifier(rs);
+					break;
+				case SeeInvisible:
+					aem = new SeeInvisibleEffectModifier(rs);
+					break;
+				case Silenced:
+					aem = new SilencedEffectModifier(rs);
+					break;
+				case Skill:
+					aem = new SkillEffectModifier(rs);
+					break;
+				case Slay:
+					aem = new SlayEffectModifier(rs);
+					break;
+				case Speed:
+					aem = new SpeedEffectModifier(rs);
+					break;
+				case SpireBlock:
+					aem = new SpireBlockEffectModifier(rs);
+					break;
+				case Stamina:
+					aem = new StaminaEffectModifier(rs);
+					break;
+				case StaminaFull:
+					aem = new StaminaFullEffectModifier(rs);
+					break;
+				case StaminaRecoverRate:
+					aem = new StaminaRecoverRateEffectModifier(rs);
+					break;
+				case Stunned:
+					aem = new StunnedEffectModifier(rs);
+					break;
+				case Value:
+					aem = new ValueEffectModifier(rs);
+					if (eb != null){
+						ValueEffectModifier valueEffect = (ValueEffectModifier)aem;
+						eb.setValue(valueEffect.minMod);
+					}
+					break;
+				case WeaponProc:
+					aem = new WeaponProcEffectModifier(rs);
+					break;
+				case WeaponRange:
+					aem = new WeaponRangeEffectModifier(rs);
+					break;
+				case WeaponSpeed:
+					aem = new WeaponSpeedEffectModifier(rs);
+					break;
+			
+			}
+
+			if (aem != null){
+			
+		
+				if (EffectsBase.modifiersMap.containsKey(eb.getIDString()) == false)
+					EffectsBase.modifiersMap.put(eb.getIDString(), new HashSet<>());
+				EffectsBase.modifiersMap.get(eb.getIDString()).add(aem);
+				
+			}
+			}
+			rs.close();
+		} catch (Exception e) {
+			Logger.error( e);
+		} finally {
+			ps.release();
+		}
+		return out;
+	}
+
+
+
+
+
+
+
+	public int getUUID() {
+		return this.UUID;
+	}
+
+	// public String getIDString() {
+	// return this.IDString;
+	// }
+
+	public String getmodType() {
+		return this.effectType;
+	}
+
+	public float getMinMod() {
+		return this.minMod;
+	}
+
+	public float getMaxMod() {
+		return this.maxMod;
+	}
+
+	public float getPercentMod() {
+		return this.percentMod;
+	}
+
+	public float getRamp() {
+		return this.ramp;
+	}
+
+	public String getType() {
+		return this.type;
+	}
+
+	public String getString1() {
+		return this.string1;
+	}
+
+	public String getString2() {
+		return this.string2;
+	}
+
+	public EffectsBase getParent() {
+		return this.parent;
+	}
+
+	public void setParent(EffectsBase value) {
+		this.parent = value;
+	}
+
+	public void applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) {
+
+		_applyEffectModifier(source, awo, trains, effect);
+	}
+
+	protected abstract void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect);
+
+	public abstract void applyBonus(AbstractCharacter ac, int trains);
+	public abstract void applyBonus(Item item, int trains);
+	public abstract void applyBonus(Building building, int trains);
+}
diff --git a/src/engine/powers/effectmodifiers/AdjustAboveDmgCapEffectModifier.java b/src/engine/powers/effectmodifiers/AdjustAboveDmgCapEffectModifier.java
new file mode 100644
index 00000000..bf64a6c0
--- /dev/null
+++ b/src/engine/powers/effectmodifiers/AdjustAboveDmgCapEffectModifier.java
@@ -0,0 +1,49 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.powers.effectmodifiers;
+
+import engine.jobs.AbstractEffectJob;
+import engine.objects.*;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+
+public class AdjustAboveDmgCapEffectModifier extends AbstractEffectModifier {
+
+	public AdjustAboveDmgCapEffectModifier(ResultSet rs) throws SQLException {
+		super(rs);
+	}
+
+	@Override
+	protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) {
+
+	}
+
+	@Override
+	public void applyBonus(AbstractCharacter ac, int trains) {
+		Float amount = 0f;
+		
+			if (this.useRampAdd)
+				amount = this.percentMod + (this.ramp * trains);
+			else
+				amount = this.percentMod * (1 + (this.ramp * trains));
+			amount =  amount / 100;
+			PlayerBonuses bonus = ac.getBonuses();
+			bonus.setFloat(this, amount);
+		
+		
+	}
+
+	@Override
+	public void applyBonus(Item item, int trains) {}
+	@Override
+	public void applyBonus(Building building, int trains) {}
+}
diff --git a/src/engine/powers/effectmodifiers/AmbidexterityEffectModifier.java b/src/engine/powers/effectmodifiers/AmbidexterityEffectModifier.java
new file mode 100644
index 00000000..44f72554
--- /dev/null
+++ b/src/engine/powers/effectmodifiers/AmbidexterityEffectModifier.java
@@ -0,0 +1,42 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.powers.effectmodifiers;
+
+import engine.Enum.ModType;
+import engine.Enum.SourceType;
+import engine.jobs.AbstractEffectJob;
+import engine.objects.*;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+
+public class AmbidexterityEffectModifier extends AbstractEffectModifier {
+
+	public AmbidexterityEffectModifier(ResultSet rs) throws SQLException {
+		super(rs);
+	}
+
+	@Override
+	protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) {
+
+	}
+
+	@Override
+	public void applyBonus(AbstractCharacter ac, int trains) {
+		PlayerBonuses bonus = ac.getBonuses();
+		bonus.setBool(ModType.Ambidexterity, SourceType.None, true);
+	}
+
+	@Override
+	public void applyBonus(Item item, int trains) {}
+	@Override
+	public void applyBonus(Building building, int trains) {}
+}
diff --git a/src/engine/powers/effectmodifiers/ArmorPiercingEffectModifier.java b/src/engine/powers/effectmodifiers/ArmorPiercingEffectModifier.java
new file mode 100644
index 00000000..29ee21fe
--- /dev/null
+++ b/src/engine/powers/effectmodifiers/ArmorPiercingEffectModifier.java
@@ -0,0 +1,42 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.powers.effectmodifiers;
+
+import engine.jobs.AbstractEffectJob;
+import engine.objects.AbstractCharacter;
+import engine.objects.AbstractWorldObject;
+import engine.objects.Building;
+import engine.objects.Item;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+
+public class ArmorPiercingEffectModifier extends AbstractEffectModifier {
+
+	public ArmorPiercingEffectModifier(ResultSet rs) throws SQLException {
+		super(rs);
+	}
+
+	@Override
+	protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) {
+
+	}
+
+	@Override
+	public void applyBonus(AbstractCharacter ac, int trains) {
+
+	}
+
+	@Override
+	public void applyBonus(Item item, int trains) {}
+	@Override
+	public void applyBonus(Building building, int trains) {}
+}
diff --git a/src/engine/powers/effectmodifiers/AttackDelayEffectModifier.java b/src/engine/powers/effectmodifiers/AttackDelayEffectModifier.java
new file mode 100644
index 00000000..2ce2f6a3
--- /dev/null
+++ b/src/engine/powers/effectmodifiers/AttackDelayEffectModifier.java
@@ -0,0 +1,46 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.powers.effectmodifiers;
+
+import engine.jobs.AbstractEffectJob;
+import engine.objects.*;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+
+public class AttackDelayEffectModifier extends AbstractEffectModifier {
+
+	public AttackDelayEffectModifier(ResultSet rs) throws SQLException {
+		super(rs);
+	}
+
+	@Override
+	protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) {
+
+	}
+
+	@Override
+	public void applyBonus(AbstractCharacter ac, int trains) {
+		Float amount = 0f;
+		if (this.useRampAdd)
+			amount = this.percentMod + (this.ramp * trains);
+		else
+			amount = this.percentMod * (1 + (this.ramp * trains));
+		amount = amount/100;
+		PlayerBonuses bonus = ac.getBonuses();
+		bonus.addFloat(this, amount);
+	}
+
+	@Override
+	public void applyBonus(Item item, int trains) {}
+	@Override
+	public void applyBonus(Building building, int trains) {}
+}
diff --git a/src/engine/powers/effectmodifiers/AttributeEffectModifier.java b/src/engine/powers/effectmodifiers/AttributeEffectModifier.java
new file mode 100644
index 00000000..4aceb60c
--- /dev/null
+++ b/src/engine/powers/effectmodifiers/AttributeEffectModifier.java
@@ -0,0 +1,56 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.powers.effectmodifiers;
+
+import engine.jobs.AbstractEffectJob;
+import engine.objects.*;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+
+public class AttributeEffectModifier extends AbstractEffectModifier {
+
+	public AttributeEffectModifier(ResultSet rs) throws SQLException {
+		super(rs);
+	}
+
+	@Override
+	protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) {
+
+	}
+
+	@Override
+	public void applyBonus(AbstractCharacter ac, int trains) {
+		
+		ac.update();
+		Float amount = 0f;
+		PlayerBonuses bonus = ac.getBonuses();
+		if (this.percentMod != 0f) { //Stat Percent Modifiers
+			if (this.useRampAdd)
+				amount = this.percentMod + (this.ramp * trains);
+			else
+				amount = this.percentMod * (1 + (this.ramp * trains));
+			amount = amount/100;
+			bonus.addFloat(this, amount);
+		} else { //Stat Modifiers
+			if (this.useRampAdd)
+				amount = this.minMod + (this.ramp * trains);
+			else
+				amount = this.minMod * (1 + (this.ramp * trains));
+			bonus.addFloat(this, amount);
+		}
+	}
+
+	@Override
+	public void applyBonus(Item item, int trains) {}
+	@Override
+	public void applyBonus(Building building, int trains) {}
+}
diff --git a/src/engine/powers/effectmodifiers/BlackMantleEffectModifier.java b/src/engine/powers/effectmodifiers/BlackMantleEffectModifier.java
new file mode 100644
index 00000000..1a6d7402
--- /dev/null
+++ b/src/engine/powers/effectmodifiers/BlackMantleEffectModifier.java
@@ -0,0 +1,53 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.powers.effectmodifiers;
+
+import engine.Enum.ModType;
+import engine.Enum.SourceType;
+import engine.jobs.AbstractEffectJob;
+import engine.objects.*;
+import org.pmw.tinylog.Logger;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+
+public class BlackMantleEffectModifier extends AbstractEffectModifier {
+
+	public BlackMantleEffectModifier(ResultSet rs) throws SQLException {
+		super(rs);
+	}
+
+	@Override
+	protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) {
+
+	}
+
+	@Override
+	public void applyBonus(AbstractCharacter ac, int trains) {
+		PlayerBonuses bonus = ac.getBonuses();
+		SourceType sourceType = SourceType.valueOf(this.type);
+		
+		if (sourceType == null){
+			Logger.error("Bad Source Type for " + this.type);
+			return;
+		}
+
+		if (this.type.equals("Heal"))
+			bonus.setFloat(this, trains);
+		else
+			bonus.setBool(ModType.ImmuneTo, this.sourceType, true);
+	}
+
+	@Override
+	public void applyBonus(Item item, int trains) {}
+	@Override
+	public void applyBonus(Building building, int trains) {}
+}
diff --git a/src/engine/powers/effectmodifiers/BladeTrailsEffectModifier.java b/src/engine/powers/effectmodifiers/BladeTrailsEffectModifier.java
new file mode 100644
index 00000000..891e3781
--- /dev/null
+++ b/src/engine/powers/effectmodifiers/BladeTrailsEffectModifier.java
@@ -0,0 +1,41 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.powers.effectmodifiers;
+
+import engine.jobs.AbstractEffectJob;
+import engine.objects.AbstractCharacter;
+import engine.objects.AbstractWorldObject;
+import engine.objects.Building;
+import engine.objects.Item;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+public class BladeTrailsEffectModifier extends AbstractEffectModifier {
+
+	public BladeTrailsEffectModifier(ResultSet rs) throws SQLException {
+		super(rs);
+	}
+
+	@Override
+	protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) {
+
+	}
+
+	@Override
+	public void applyBonus(AbstractCharacter ac, int trains) {
+
+	}
+
+	@Override
+	public void applyBonus(Item item, int trains) {}
+	@Override
+	public void applyBonus(Building building, int trains) {}
+}
diff --git a/src/engine/powers/effectmodifiers/BlockEffectModifier.java b/src/engine/powers/effectmodifiers/BlockEffectModifier.java
new file mode 100644
index 00000000..b388dac1
--- /dev/null
+++ b/src/engine/powers/effectmodifiers/BlockEffectModifier.java
@@ -0,0 +1,46 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.powers.effectmodifiers;
+
+import engine.jobs.AbstractEffectJob;
+import engine.objects.*;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+
+public class BlockEffectModifier extends AbstractEffectModifier {
+
+	public BlockEffectModifier(ResultSet rs) throws SQLException {
+		super(rs);
+	}
+
+	@Override
+	protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) {
+
+	}
+
+	@Override
+	public void applyBonus(AbstractCharacter ac, int trains) {
+		Float amount = 0f;
+		if (this.useRampAdd)
+			amount = this.percentMod + (this.ramp * trains);
+		else
+			amount = this.percentMod * (1 + (this.ramp * trains));
+		amount = amount/100;
+		PlayerBonuses bonus = ac.getBonuses();
+		bonus.setFloat(this, amount);
+	}
+
+	@Override
+	public void applyBonus(Item item, int trains) {}
+	@Override
+	public void applyBonus(Building building, int trains) {}
+}
diff --git a/src/engine/powers/effectmodifiers/BlockedPowerTypeEffectModifier.java b/src/engine/powers/effectmodifiers/BlockedPowerTypeEffectModifier.java
new file mode 100644
index 00000000..3e447d31
--- /dev/null
+++ b/src/engine/powers/effectmodifiers/BlockedPowerTypeEffectModifier.java
@@ -0,0 +1,62 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.powers.effectmodifiers;
+
+import engine.Enum.ModType;
+import engine.jobs.AbstractEffectJob;
+import engine.objects.*;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.HashSet;
+
+
+public class BlockedPowerTypeEffectModifier extends AbstractEffectModifier {
+
+	public BlockedPowerTypeEffectModifier(ResultSet rs) throws SQLException {
+		super(rs);
+	}
+
+	@Override
+	protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) {
+
+	}
+
+	@Override
+	public void applyBonus(AbstractCharacter ac, int trains) {
+		PlayerBonuses bonus = ac.getBonuses();
+		bonus.setBool(this.modType,this.sourceType, true);
+		
+		
+		for (String effect : ac.getEffects().keySet()){
+			Effect eff = ac.getEffects().get(effect);
+			ModType toBlock = ModType.None;
+			
+			switch (this.sourceType){
+			case Invisible:
+				toBlock = ModType.Invisible;
+				break;
+			}
+			
+			HashSet<AbstractEffectModifier> aemList = eff.getEffectModifiers();
+			for (AbstractEffectModifier aem : aemList ){
+				if (aem.modType.equals(toBlock)){
+					ac.endEffect(effect);
+				}
+			}
+			
+		}
+	}
+
+	@Override
+	public void applyBonus(Item item, int trains) {}
+	@Override
+	public void applyBonus(Building building, int trains) {}
+}
diff --git a/src/engine/powers/effectmodifiers/CannotAttackEffectModifier.java b/src/engine/powers/effectmodifiers/CannotAttackEffectModifier.java
new file mode 100644
index 00000000..86435c20
--- /dev/null
+++ b/src/engine/powers/effectmodifiers/CannotAttackEffectModifier.java
@@ -0,0 +1,41 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.powers.effectmodifiers;
+
+import engine.jobs.AbstractEffectJob;
+import engine.objects.*;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+
+public class CannotAttackEffectModifier extends AbstractEffectModifier {
+
+	public CannotAttackEffectModifier(ResultSet rs) throws SQLException {
+		super(rs);
+	}
+
+	@Override
+	protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) {
+
+	}
+
+	@Override
+	public void applyBonus(AbstractCharacter ac, int trains) {
+		PlayerBonuses bonus = ac.getBonuses();
+		
+		bonus.setBool(this.modType,this.sourceType, true);
+	}
+
+	@Override
+	public void applyBonus(Item item, int trains) {}
+	@Override
+	public void applyBonus(Building building, int trains) {}
+}
diff --git a/src/engine/powers/effectmodifiers/CannotCastEffectModifier.java b/src/engine/powers/effectmodifiers/CannotCastEffectModifier.java
new file mode 100644
index 00000000..3dca97b7
--- /dev/null
+++ b/src/engine/powers/effectmodifiers/CannotCastEffectModifier.java
@@ -0,0 +1,46 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.powers.effectmodifiers;
+
+import engine.Enum;
+import engine.jobs.AbstractEffectJob;
+import engine.objects.*;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+
+public class CannotCastEffectModifier extends AbstractEffectModifier {
+
+	public CannotCastEffectModifier(ResultSet rs) throws SQLException {
+		super(rs);
+	}
+
+	@Override
+	protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) {
+
+	}
+
+	@Override
+	public void applyBonus(AbstractCharacter ac, int trains) {
+
+		if (ac.getObjectType().equals(Enum.GameObjectType.Mob)) {
+			Mob mob = (Mob) ac;
+		}
+
+		PlayerBonuses bonus = ac.getBonuses();
+		bonus.setBool(this.modType,this.sourceType, true);
+	}
+
+	@Override
+	public void applyBonus(Item item, int trains) {}
+	@Override
+	public void applyBonus(Building building, int trains) {}
+}
diff --git a/src/engine/powers/effectmodifiers/CannotMoveEffectModifier.java b/src/engine/powers/effectmodifiers/CannotMoveEffectModifier.java
new file mode 100644
index 00000000..51fe46cb
--- /dev/null
+++ b/src/engine/powers/effectmodifiers/CannotMoveEffectModifier.java
@@ -0,0 +1,41 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.powers.effectmodifiers;
+
+import engine.jobs.AbstractEffectJob;
+import engine.objects.*;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+
+public class CannotMoveEffectModifier extends AbstractEffectModifier {
+
+	public CannotMoveEffectModifier(ResultSet rs) throws SQLException {
+		super(rs);
+	}
+
+	@Override
+	protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) {
+
+	}
+
+	@Override
+	public void applyBonus(AbstractCharacter ac, int trains) {
+		PlayerBonuses bonus = ac.getBonuses();
+		bonus.setBool(this.modType,this.sourceType,true);
+		ac.stopMovement(ac.getMovementLoc());
+	}
+
+	@Override
+	public void applyBonus(Item item, int trains) {}
+	@Override
+	public void applyBonus(Building building, int trains) {}
+}
diff --git a/src/engine/powers/effectmodifiers/CannotTrackEffectModifier.java b/src/engine/powers/effectmodifiers/CannotTrackEffectModifier.java
new file mode 100644
index 00000000..a4e6a4c0
--- /dev/null
+++ b/src/engine/powers/effectmodifiers/CannotTrackEffectModifier.java
@@ -0,0 +1,40 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.powers.effectmodifiers;
+
+import engine.jobs.AbstractEffectJob;
+import engine.objects.*;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+
+public class CannotTrackEffectModifier extends AbstractEffectModifier {
+
+	public CannotTrackEffectModifier(ResultSet rs) throws SQLException {
+		super(rs);
+	}
+
+	@Override
+	protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) {
+
+	}
+
+	@Override
+	public void applyBonus(AbstractCharacter ac, int trains) {
+		PlayerBonuses bonus = ac.getBonuses();
+		bonus.setBool(this.modType,this.sourceType,true);
+	}
+
+	@Override
+	public void applyBonus(Item item, int trains) {}
+	@Override
+	public void applyBonus(Building building, int trains) {}
+}
diff --git a/src/engine/powers/effectmodifiers/CharmedEffectModifier.java b/src/engine/powers/effectmodifiers/CharmedEffectModifier.java
new file mode 100644
index 00000000..324dde83
--- /dev/null
+++ b/src/engine/powers/effectmodifiers/CharmedEffectModifier.java
@@ -0,0 +1,41 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.powers.effectmodifiers;
+
+import engine.jobs.AbstractEffectJob;
+import engine.objects.AbstractCharacter;
+import engine.objects.AbstractWorldObject;
+import engine.objects.Building;
+import engine.objects.Item;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+public class CharmedEffectModifier extends AbstractEffectModifier {
+
+	public CharmedEffectModifier(ResultSet rs) throws SQLException {
+		super(rs);
+	}
+
+	@Override
+	protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) {
+
+	}
+
+	@Override
+	public void applyBonus(AbstractCharacter ac, int trains) {
+
+	}
+
+	@Override
+	public void applyBonus(Item item, int trains) {}
+	@Override
+	public void applyBonus(Building building, int trains) {}
+}
diff --git a/src/engine/powers/effectmodifiers/ConstrainedAmbidexterityEffectModifier.java b/src/engine/powers/effectmodifiers/ConstrainedAmbidexterityEffectModifier.java
new file mode 100644
index 00000000..22c7e497
--- /dev/null
+++ b/src/engine/powers/effectmodifiers/ConstrainedAmbidexterityEffectModifier.java
@@ -0,0 +1,40 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.powers.effectmodifiers;
+
+import engine.jobs.AbstractEffectJob;
+import engine.objects.*;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+
+public class ConstrainedAmbidexterityEffectModifier extends AbstractEffectModifier {
+
+	public ConstrainedAmbidexterityEffectModifier(ResultSet rs) throws SQLException {
+		super(rs);
+	}
+
+	@Override
+	protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) {
+
+	}
+
+	@Override
+	public void applyBonus(AbstractCharacter ac, int trains) {
+		PlayerBonuses bonus = ac.getBonuses();
+		bonus.setString(this,this.type);
+	}
+
+	@Override
+	public void applyBonus(Item item, int trains) {}
+	@Override
+	public void applyBonus(Building building, int trains) {}
+}
diff --git a/src/engine/powers/effectmodifiers/DCVEffectModifier.java b/src/engine/powers/effectmodifiers/DCVEffectModifier.java
new file mode 100644
index 00000000..29dcd20e
--- /dev/null
+++ b/src/engine/powers/effectmodifiers/DCVEffectModifier.java
@@ -0,0 +1,54 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.powers.effectmodifiers;
+
+import engine.jobs.AbstractEffectJob;
+import engine.objects.*;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+
+public class DCVEffectModifier extends AbstractEffectModifier {
+
+	public DCVEffectModifier(ResultSet rs) throws SQLException {
+		super(rs);
+	}
+
+	@Override
+	protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) {
+
+	}
+
+	@Override
+	public void applyBonus(AbstractCharacter ac, int trains) {
+		Float amount = 0f;
+		PlayerBonuses bonus = ac.getBonuses();
+		if (this.percentMod != 0f) { //Stat Percent Modifiers
+			if (this.useRampAdd)
+				amount = this.percentMod + (this.ramp * trains);
+			else
+				amount = this.percentMod * (1 + (this.ramp * trains));
+			amount = (amount) / 100;
+				bonus.addFloat(this, amount);
+		} else { //Stat Modifiers
+			if (this.useRampAdd)
+				amount = this.minMod + (this.ramp * trains);
+			else
+				amount = this.minMod * (1 + (this.ramp * trains));
+			bonus.addFloat(this,amount);
+		}
+	}
+
+	@Override
+	public void applyBonus(Item item, int trains) {}
+	@Override
+	public void applyBonus(Building building, int trains) {}
+}
diff --git a/src/engine/powers/effectmodifiers/DREffectModifier.java b/src/engine/powers/effectmodifiers/DREffectModifier.java
new file mode 100644
index 00000000..54b606a5
--- /dev/null
+++ b/src/engine/powers/effectmodifiers/DREffectModifier.java
@@ -0,0 +1,61 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.powers.effectmodifiers;
+
+import engine.jobs.AbstractEffectJob;
+import engine.objects.AbstractCharacter;
+import engine.objects.AbstractWorldObject;
+import engine.objects.Building;
+import engine.objects.Item;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+public class DREffectModifier extends AbstractEffectModifier {
+
+	public DREffectModifier(ResultSet rs) throws SQLException {
+		super(rs);
+	}
+
+	@Override
+	protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) {
+
+	}
+
+	@Override
+	public void applyBonus(AbstractCharacter ac, int trains) {
+		//Defense Rating (defense bonus for armor)
+	}
+
+	@Override
+	public void applyBonus(Item item, int trains) {
+		if (item == null)
+			return;
+		String key; float amount = 0f;
+		if (this.percentMod != 0f) {
+			if (this.useRampAdd)
+				amount = (this.percentMod + (this.ramp * trains)) / 100f;
+			else
+				amount = (this.percentMod * (1 + (this.ramp * trains))) / 100f;
+			amount = amount/100;
+			key = "DR.percent";
+		} else {
+			if (this.useRampAdd)
+				amount = this.minMod + (this.ramp * trains);
+			else
+				amount = this.minMod * (1 + (this.ramp * trains));
+			key = "DR";
+		}
+		item.addBonus(this, amount);
+	}
+
+	@Override
+	public void applyBonus(Building building, int trains) {}
+}
diff --git a/src/engine/powers/effectmodifiers/DamageCapEffectModifier.java b/src/engine/powers/effectmodifiers/DamageCapEffectModifier.java
new file mode 100644
index 00000000..905805f2
--- /dev/null
+++ b/src/engine/powers/effectmodifiers/DamageCapEffectModifier.java
@@ -0,0 +1,46 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.powers.effectmodifiers;
+
+import engine.jobs.AbstractEffectJob;
+import engine.objects.*;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+
+public class DamageCapEffectModifier extends AbstractEffectModifier {
+
+	public DamageCapEffectModifier(ResultSet rs) throws SQLException {
+		super(rs);
+	}
+
+	@Override
+	protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) {
+
+	}
+
+	@Override
+	public void applyBonus(AbstractCharacter ac, int trains) {
+		Float amount = 0f;
+		if (this.useRampAdd)
+			amount = this.percentMod + (this.ramp * trains);
+		else
+			amount = this.percentMod * (1 + (this.ramp * trains));
+		amount = amount/100;
+		PlayerBonuses bonus = ac.getBonuses();
+		bonus.setFloat(this, amount);
+	}
+
+	@Override
+	public void applyBonus(Item item, int trains) {}
+	@Override
+	public void applyBonus(Building building, int trains) {}
+}
diff --git a/src/engine/powers/effectmodifiers/DamageShieldEffectModifier.java b/src/engine/powers/effectmodifiers/DamageShieldEffectModifier.java
new file mode 100644
index 00000000..1a4bf094
--- /dev/null
+++ b/src/engine/powers/effectmodifiers/DamageShieldEffectModifier.java
@@ -0,0 +1,64 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.powers.effectmodifiers;
+
+import engine.Enum.DamageType;
+import engine.jobs.AbstractEffectJob;
+import engine.objects.*;
+import engine.powers.DamageShield;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+
+public class DamageShieldEffectModifier extends AbstractEffectModifier {
+
+	public DamageShieldEffectModifier(ResultSet rs) throws SQLException {
+		super(rs);
+	}
+
+	@Override
+	protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) {
+
+	}
+
+	@Override
+	public void applyBonus(AbstractCharacter ac, int trains) {
+		float amount; boolean usePercent;
+		if (this.percentMod != 0) {
+			amount = this.percentMod;
+			usePercent = true;
+		} else {
+			amount = this.minMod;
+			usePercent = false;
+		}
+
+		if (this.ramp > 0f) {
+			float mod = this.ramp * trains;
+			if (this.useRampAdd)
+				amount += mod;
+			else
+				amount *= (1 + mod);
+		}
+
+		DamageType dt = DamageType.valueOf(this.type);
+		if (dt != null) {
+			DamageShield ds = new DamageShield(dt, amount, usePercent);
+			PlayerBonuses bonus = ac.getBonuses();
+			if (bonus != null)
+				bonus.addDamageShield(this, ds);
+		}
+	}
+
+	@Override
+	public void applyBonus(Item item, int trains) {}
+	@Override
+	public void applyBonus(Building building, int trains) {}
+}
diff --git a/src/engine/powers/effectmodifiers/DodgeEffectModifier.java b/src/engine/powers/effectmodifiers/DodgeEffectModifier.java
new file mode 100644
index 00000000..5580f7f0
--- /dev/null
+++ b/src/engine/powers/effectmodifiers/DodgeEffectModifier.java
@@ -0,0 +1,54 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.powers.effectmodifiers;
+
+import engine.jobs.AbstractEffectJob;
+import engine.objects.*;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+
+public class DodgeEffectModifier extends AbstractEffectModifier {
+
+	public DodgeEffectModifier(ResultSet rs) throws SQLException {
+		super(rs);
+	}
+
+	@Override
+	protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) {
+
+	}
+
+	@Override
+	public void applyBonus(AbstractCharacter ac, int trains) {
+		Float amount = 0f;
+		PlayerBonuses bonus = ac.getBonuses();
+		if (this.percentMod != 0f) { //Stat Percent Modifiers
+			if (this.useRampAdd)
+				amount = this.percentMod + (this.ramp * trains);
+			else
+				amount = this.percentMod * (1 + (this.ramp * trains));
+			amount = amount/100;
+			bonus.addFloat(this, amount);
+		} else { //Stat Modifiers
+			if (this.useRampAdd)
+				amount = this.minMod + (this.ramp * trains);
+			else
+				amount = this.minMod * (1 + (this.ramp * trains));
+			bonus.addFloat(this, amount);
+		}
+	}
+
+	@Override
+	public void applyBonus(Item item, int trains) {}
+	@Override
+	public void applyBonus(Building building, int trains) {}
+}
diff --git a/src/engine/powers/effectmodifiers/DurabilityEffectModifier.java b/src/engine/powers/effectmodifiers/DurabilityEffectModifier.java
new file mode 100644
index 00000000..2af78999
--- /dev/null
+++ b/src/engine/powers/effectmodifiers/DurabilityEffectModifier.java
@@ -0,0 +1,41 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.powers.effectmodifiers;
+
+import engine.jobs.AbstractEffectJob;
+import engine.objects.AbstractCharacter;
+import engine.objects.AbstractWorldObject;
+import engine.objects.Building;
+import engine.objects.Item;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+public class DurabilityEffectModifier extends AbstractEffectModifier {
+
+	public DurabilityEffectModifier(ResultSet rs) throws SQLException {
+		super(rs);
+	}
+
+	@Override
+	protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) {
+
+	}
+
+	@Override
+	public void applyBonus(AbstractCharacter ac, int trains) {
+
+	}
+
+	@Override
+	public void applyBonus(Item item, int trains) {}
+	@Override
+	public void applyBonus(Building building, int trains) {}
+}
diff --git a/src/engine/powers/effectmodifiers/ExclusiveDamageCapEffectModifier.java b/src/engine/powers/effectmodifiers/ExclusiveDamageCapEffectModifier.java
new file mode 100644
index 00000000..0a652cef
--- /dev/null
+++ b/src/engine/powers/effectmodifiers/ExclusiveDamageCapEffectModifier.java
@@ -0,0 +1,45 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.powers.effectmodifiers;
+
+import engine.jobs.AbstractEffectJob;
+import engine.objects.*;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.HashSet;
+
+public class ExclusiveDamageCapEffectModifier extends AbstractEffectModifier {
+
+	public ExclusiveDamageCapEffectModifier(ResultSet rs) throws SQLException {
+		super(rs);
+	}
+
+	@Override
+	protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) {
+
+	}
+
+	@Override
+	public void applyBonus(AbstractCharacter ac, int trains) {
+		PlayerBonuses bonus = ac.getBonuses();
+		if (bonus == null)
+			return;
+		if (bonus.getList(this.modType) == null)
+			bonus.setList(this.modType, new HashSet<>());
+		bonus.getList(this.modType).add(this.sourceType);
+		
+	}
+
+	@Override
+	public void applyBonus(Item item, int trains) {}
+	@Override
+	public void applyBonus(Building building, int trains) {}
+}
diff --git a/src/engine/powers/effectmodifiers/FadeEffectModifier.java b/src/engine/powers/effectmodifiers/FadeEffectModifier.java
new file mode 100644
index 00000000..2e12d645
--- /dev/null
+++ b/src/engine/powers/effectmodifiers/FadeEffectModifier.java
@@ -0,0 +1,41 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.powers.effectmodifiers;
+
+import engine.jobs.AbstractEffectJob;
+import engine.objects.AbstractCharacter;
+import engine.objects.AbstractWorldObject;
+import engine.objects.Building;
+import engine.objects.Item;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+public class FadeEffectModifier extends AbstractEffectModifier {
+
+	public FadeEffectModifier(ResultSet rs) throws SQLException {
+		super(rs);
+	}
+
+	@Override
+	protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) {
+
+	}
+
+	@Override
+	public void applyBonus(AbstractCharacter ac, int trains) {
+
+	}
+
+	@Override
+	public void applyBonus(Item item, int trains) {}
+	@Override
+	public void applyBonus(Building building, int trains) {}
+}
diff --git a/src/engine/powers/effectmodifiers/FlyEffectModifier.java b/src/engine/powers/effectmodifiers/FlyEffectModifier.java
new file mode 100644
index 00000000..3e16ca7c
--- /dev/null
+++ b/src/engine/powers/effectmodifiers/FlyEffectModifier.java
@@ -0,0 +1,39 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.powers.effectmodifiers;
+
+import engine.jobs.AbstractEffectJob;
+import engine.objects.*;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+public class FlyEffectModifier extends AbstractEffectModifier {
+
+	public FlyEffectModifier(ResultSet rs) throws SQLException {
+		super(rs);
+	}
+
+	@Override
+	protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) {
+
+	}
+
+	@Override
+	public void applyBonus(AbstractCharacter ac, int trains) {
+		PlayerBonuses bonus = ac.getBonuses();
+		bonus.setBool(this.modType,this.sourceType,true);
+	}
+
+	@Override
+	public void applyBonus(Item item, int trains) {}
+	@Override
+	public void applyBonus(Building building, int trains) {}
+}
diff --git a/src/engine/powers/effectmodifiers/HealthEffectModifier.java b/src/engine/powers/effectmodifiers/HealthEffectModifier.java
new file mode 100644
index 00000000..97ef9462
--- /dev/null
+++ b/src/engine/powers/effectmodifiers/HealthEffectModifier.java
@@ -0,0 +1,331 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.powers.effectmodifiers;
+
+import engine.Enum.DamageType;
+import engine.Enum.GameObjectType;
+import engine.Enum.ModType;
+import engine.Enum.SourceType;
+import engine.gameManager.ChatManager;
+import engine.jobs.AbstractEffectJob;
+import engine.jobs.DamageOverTimeJob;
+import engine.net.AbstractNetMsg;
+import engine.net.DispatchMessage;
+import engine.net.client.msg.ModifyHealthKillMsg;
+import engine.net.client.msg.ModifyHealthMsg;
+import engine.objects.*;
+import org.pmw.tinylog.Logger;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.concurrent.ThreadLocalRandom;
+
+public class HealthEffectModifier extends AbstractEffectModifier {
+
+	private DamageType damageType;
+
+	public HealthEffectModifier(ResultSet rs) throws SQLException {
+		super(rs);
+		String damageTypeDB = rs.getString("type");
+		try {
+			this.damageType = DamageType.valueOf(damageTypeDB);
+		} catch (IllegalArgumentException e) {
+			Logger.error("DamageType could not be loaded from database. " + "UUID = " + this.UUID
+					+ " value received = '" + damageTypeDB + '\'', e);
+		}
+	}
+
+	@Override
+	protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) {
+		if (awo == null) {
+			Logger.error("_applyEffectModifier(): NULL AWO passed in.");
+			return;
+		}
+
+		if (effect == null) {
+			Logger.error( "_applyEffectModifier(): NULL AbstractEffectJob passed in.");
+			return;
+		}
+
+		float modAmount = 0f;
+
+		// Modify health by percent
+		if (this.percentMod != 0f) {
+
+			//high level mobs/players should not be %damaged/healed.
+			if (awo.getHealthMax() > 25000f && (this.percentMod < 0f || this.percentMod > 5f))
+				return;
+
+			float mod = 1f;
+			if (this.useRampAdd)
+				mod = (this.percentMod + (this.ramp * trains)) / 100;
+			else
+				mod = (this.percentMod * (1 + (this.ramp * trains))) / 100;
+			modAmount = mod * awo.getHealthMax();
+			if (AbstractWorldObject.IsAbstractCharacter(awo)) {
+				if (((AbstractCharacter)awo).isSit())
+					modAmount *= 2.5f;
+			}
+
+			//debug for spell damage and atr
+			if (source.getDebug(16) && source.getObjectType().equals(GameObjectType.PlayerCharacter)) {
+				PlayerCharacter pc = (PlayerCharacter) source;
+				String smsg = "Percent Damage: " + mod * 100 + '%';
+				ChatManager.chatSystemInfo(pc, smsg);
+			}
+		}
+
+		// Modify health by min/max amount
+		else if (this.minMod != 0f || this.maxMod != 0f) {
+			float min = this.minMod;
+			float max = this.maxMod;
+			if (this.ramp > 0f) {
+				float mod = this.ramp * trains;
+				if (this.useRampAdd) {
+					min += mod;
+					max += mod;
+				} else {
+					min *= (1 + mod);
+					max *= (1 + mod);
+				}
+			}
+			if (source.getObjectType().equals(GameObjectType.PlayerCharacter)) {
+				PlayerCharacter pc = (PlayerCharacter) source;
+
+				float focus;
+				CharacterSkill skill = pc.getSkills().get(effect.getPower().getSkillName());
+				if (skill == null)
+					focus = CharacterSkill.getQuickMastery(pc, effect.getPower().getSkillName());
+				else
+					focus = skill.getModifiedAmount();
+				//TODO clean up old formulas once new one is verified
+				//				min *= (0.5 + 0.0075 * pc.getStatIntCurrent() + 0.011 * pc.getStatSpiCurrent() + 0.0196 * focus);
+				//				max *= (0.62 + 0.0192 * pc.getStatIntCurrent() + 0.00415 * pc.getStatSpiCurrent() + 0.015 * focus);
+				float intt = (pc.getStatIntCurrent() >= 1) ? (float)pc.getStatIntCurrent() : 1f;
+				float spi = (pc.getStatSpiCurrent() >= 1) ? (float)pc.getStatSpiCurrent() : 1f;
+				//				min *= (intt * 0.0045 + 0.055 * (float)Math.sqrt(intt - 0.5) + spi * 0.006 + 0.07 * (float)Math.sqrt(spi - 0.5) + 0.02 * (int)focus);
+				//				max *= (intt * 0.0117 + 0.13 * (float)Math.sqrt(intt - 0.5) + spi * 0.0024 + (float)Math.sqrt(spi - 0.5) * 0.021 + 0.015 * (int)focus);
+				min = HealthEffectModifier.getMinDamage(min, intt, spi, focus);
+				max = HealthEffectModifier.getMaxDamage(max, intt, spi, focus);
+
+				//debug for spell damage and atr
+				if (pc.getDebug(16)) {
+					String smsg = "Damage: " + (int)Math.abs(min) + " - " + (int)Math.abs(max);
+					ChatManager.chatSystemInfo(pc, smsg);
+				}
+			}else if (source.getObjectType() == GameObjectType.Mob){
+				Mob pc = (Mob) source;
+
+				float focus;
+				CharacterSkill skill = pc.getSkills().get(effect.getPower().getSkillName());
+				if (skill == null)
+					focus = CharacterSkill.getQuickMastery(pc, effect.getPower().getSkillName());
+				else
+					focus = skill.getModifiedAmount();
+				//TODO clean up old formulas once new one is verified
+				//				min *= (0.5 + 0.0075 * pc.getStatIntCurrent() + 0.011 * pc.getStatSpiCurrent() + 0.0196 * focus);
+				//				max *= (0.62 + 0.0192 * pc.getStatIntCurrent() + 0.00415 * pc.getStatSpiCurrent() + 0.015 * focus);
+				float intt = (pc.getStatIntCurrent() >= 1) ? (float)pc.getStatIntCurrent() : 1f;
+
+				if (pc.isPlayerGuard())
+					intt = 200;
+				float spi = (pc.getStatSpiCurrent() >= 1) ? (float)pc.getStatSpiCurrent() : 1f;
+
+				if (pc.isPlayerGuard())
+					spi = 200;
+				//				min *= (intt * 0.0045 + 0.055 * (float)Math.sqrt(intt - 0.5) + spi * 0.006 + 0.07 * (float)Math.sqrt(spi - 0.5) + 0.02 * (int)focus);
+				//				max *= (intt * 0.0117 + 0.13 * (float)Math.sqrt(intt - 0.5) + spi * 0.0024 + (float)Math.sqrt(spi - 0.5) * 0.021 + 0.015 * (int)focus);
+				min = HealthEffectModifier.getMinDamage(min, intt, spi, focus);
+				max = HealthEffectModifier.getMaxDamage(max, intt, spi, focus);
+
+				//debug for spell damage and atr
+				//				if (pc.getDebug(16)) {
+				//					String smsg = "Damage: " + (int)Math.abs(min) + " - " + (int)Math.abs(max);
+				//					ChatManager.chatSystemInfo(pc, smsg);
+				//				}
+			}
+			modAmount = calculateDamage(source, min, max, awo, trains);
+			PlayerBonuses bonus = source.getBonuses();
+
+			// Apply any power effect modifiers (such as stances)
+			if (bonus != null)
+				modAmount *= (1 + (bonus.getFloatPercentAll(ModType.PowerDamageModifier, SourceType.None)));
+		}
+		if (modAmount == 0f)
+			return;
+		if (AbstractWorldObject.IsAbstractCharacter(awo)) {
+			AbstractCharacter ac = (AbstractCharacter) awo;
+
+			if (!ac.isAlive())
+				return;
+
+			int powerID = 0, effectID = 0;
+			String powerName = "";
+			if (effect.getPower() != null) {
+				powerID = effect.getPower().getToken();
+				powerName = effect.getPower().getName();
+			} else {
+				Logger.error("Power has returned null! Damage will fail to register! (" + (ac.getCurrentHitpoints()>0?"Alive)":"Dead)"));
+			}
+
+			if (effect.getEffect() != null) {
+				effectID = effect.getEffect().getToken();
+			} else {
+				Logger.error("Effect has returned null! Damage will fail to register! (" + (ac.getCurrentHitpoints()>0?"Alive)":"Dead)"));
+			}
+
+			//see if target is immune to heals
+			if (modAmount > 0f) {
+				boolean skipImmune = false;
+				// first tick of HoT going thru SM was removed in a later patch
+				/*if (effect.getAction().getPowerAction() instanceof DirectDamagePowerAction) {
+					ArrayList<ActionsBase> actions = effect.getPower().getActions();
+					for (ActionsBase ab : actions) {
+						AbstractPowerAction apa = ab.getPowerAction();
+						if (apa instanceof DamageOverTimePowerAction)
+							skipImmune = true;
+					}
+				}*/
+
+				PlayerBonuses bonus = ac.getBonuses();
+				if (!skipImmune && bonus.getFloat(ModType.BlackMantle, SourceType.Heal) >= trains) {
+					ModifyHealthMsg mhm = new ModifyHealthMsg(source, ac, 0f, 0f, 0f, powerID, powerName, trains, effectID);
+					mhm.setUnknown03(5); //set target is immune
+					DispatchMessage.sendToAllInRange(ac, mhm);
+					return;
+				}
+			}
+			float mod = 0;
+
+			//Modify health
+
+			mod = ac.modifyHealth(modAmount, source, false);
+
+			float cur = awo.getCurrentHitpoints();
+			float maxAmount = awo.getHealthMax() - cur;
+
+			AbstractNetMsg mhm = null;
+			if (modAmount < 0 && cur < 0 && mod != 0)
+				mhm = new ModifyHealthKillMsg(source, ac, modAmount, 0f, 0f, powerID, powerName, trains, effectID);
+			else
+				mhm = new ModifyHealthMsg(source, ac, modAmount, 0f, 0f, powerID, powerName, trains, effectID);
+
+			if (effect instanceof DamageOverTimeJob) {
+				if (mhm instanceof ModifyHealthMsg)
+					((ModifyHealthMsg)mhm).setOmitFromChat(1);
+				else if (mhm instanceof ModifyHealthKillMsg)
+					((ModifyHealthKillMsg)mhm).setUnknown02(1);
+			}
+
+			//send the damage
+
+			DispatchMessage.sendToAllInRange(ac, mhm);
+
+			//			//send corpse if this kills a mob
+			//			//TODO fix the someone misses blurb.
+			//			if(awo instanceof Mob && awo.getHealth() <= 0) {
+			//				CombatMessageMsg cmm = new CombatMessageMsg(null, 0, awo, 15);
+			//				try {
+			//					DispatchMessage.sendToAllInRange(ac, cmm);
+			//				} catch (MsgSendException e) {
+			//					Logger.error("MobCorpseSendError", e);
+			//				}
+			//			}
+		} else if (awo.getObjectType().equals(GameObjectType.Building)) {
+
+			Building b = (Building) awo;
+
+			if (modAmount < 0 && (!b.isVulnerable()))
+				return; //can't damage invul building
+
+			int powerID = 0, effectID = 0;
+			String powerName = "";
+			if (effect.getPower() != null) {
+				powerID = effect.getPower().getToken();
+				powerName = effect.getPower().getName();
+			} else
+				Logger.error("Power has returned null! Damage will fail to register! (" + (b.getRank() == -1 ? "Standing)" : "Destroyed)"));
+
+			if (effect.getEffect() != null) {
+				effectID = effect.getEffect().getToken();
+			} else
+				Logger.error("Effect has returned null! Damage will fail to register! (" + (b.getRank() == -1 ? "Standing)" : "Destroyed)"));
+
+			float mod = b.modifyHealth(modAmount, source);
+			ModifyHealthMsg mhm = new ModifyHealthMsg(source, b, modAmount, 0f, 0f, powerID, powerName, trains, effectID);
+
+			if (effect instanceof DamageOverTimeJob)
+				mhm.setOmitFromChat(1);
+
+			//send the damage
+
+			DispatchMessage.sendToAllInRange(b, mhm);
+
+		}
+	}
+
+	private float calculateDamage(AbstractCharacter source, float minDamage, float maxDamage, AbstractWorldObject awo, int trains) {
+
+		// get range between min and max
+		float range = maxDamage - minDamage;
+
+		// Damage is calculated twice to average a more central point
+		float damage = ThreadLocalRandom.current().nextFloat() * range;
+		damage = (damage + (ThreadLocalRandom.current().nextFloat() * range)) / 2;
+
+		// put it back between min and max
+		damage += minDamage;
+
+		Resists resists = null;
+		// get resists
+		if (AbstractWorldObject.IsAbstractCharacter(awo)) {
+			AbstractCharacter ac = (AbstractCharacter) awo;
+			resists = ac.getResists();
+		} else if (awo.getObjectType().equals(GameObjectType.Building))
+			resists = ((Building) awo).getResists();
+
+		// calculate resists in if any
+		if (resists != null) {
+			if (AbstractWorldObject.IsAbstractCharacter(awo))
+				damage = resists.getResistedDamage(source, (AbstractCharacter) awo, damageType, damage * -1, trains) * -1;
+			else
+				damage = resists.getResistedDamage(source, null, damageType, damage * -1, trains) * -1;
+		}
+
+		if (AbstractWorldObject.IsAbstractCharacter(awo)) {
+			AbstractCharacter ac = (AbstractCharacter) awo;
+			if (ac.isSit())
+				damage *= 2.5f; // increase damage if sitting
+		}
+
+		return damage;
+	}
+
+	@Override
+	public void applyBonus(AbstractCharacter ac, int trains) {
+
+	}
+
+	@Override
+	public void applyBonus(Item item, int trains) {}
+	@Override
+	public void applyBonus(Building building, int trains) {}
+
+	public static float getMinDamage(float baseMin, float intelligence, float spirit, float focus) {
+		float min = baseMin * (((float)Math.pow(intelligence, 0.75f) * 0.0311f) + (0.02f * (int)focus) + ((float)Math.pow(spirit, 0.75f) * 0.0416f));
+		return (float)((int)(min + 0.5f)); //round to nearest whole number
+	}
+
+	public static float getMaxDamage(float baseMax, float intelligence, float spirit, float focus) {
+		float max = baseMax * (((float)Math.pow(intelligence, 0.75f) * 0.0785f) + (0.015f * (int)focus) + ((float)Math.pow(spirit, 0.75f) * 0.0157f));
+		return (float)((int)(max + 0.5f)); //round to nearest whole number
+	}
+
+}
diff --git a/src/engine/powers/effectmodifiers/HealthFullEffectModifier.java b/src/engine/powers/effectmodifiers/HealthFullEffectModifier.java
new file mode 100644
index 00000000..6025f670
--- /dev/null
+++ b/src/engine/powers/effectmodifiers/HealthFullEffectModifier.java
@@ -0,0 +1,53 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.powers.effectmodifiers;
+
+import engine.jobs.AbstractEffectJob;
+import engine.objects.*;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+public class HealthFullEffectModifier extends AbstractEffectModifier {
+
+	public HealthFullEffectModifier(ResultSet rs) throws SQLException {
+		super(rs);
+	}
+
+	@Override
+	protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) {
+
+	}
+
+	@Override
+	public void applyBonus(AbstractCharacter ac, int trains) {
+		Float amount = 0f;
+		PlayerBonuses bonus = ac.getBonuses();
+		if (this.percentMod != 0f) { //Stat Percent Modifiers
+			if (this.useRampAdd)
+				amount = this.percentMod + (this.ramp * trains);
+			else
+				amount = this.percentMod * (1 + (this.ramp * trains));
+			amount = amount/100;
+			bonus.addFloat(this, amount);
+		} else { //Stat Modifiers
+			if (this.useRampAdd)
+				amount = this.minMod + (this.ramp * trains);
+			else
+				amount = this.minMod * (1 + (this.ramp * trains));
+			bonus.addFloat(this, amount);
+		}
+	}
+
+	@Override
+	public void applyBonus(Item item, int trains) {}
+	@Override
+	public void applyBonus(Building building, int trains) {}
+}
diff --git a/src/engine/powers/effectmodifiers/HealthRecoverRateEffectModifier.java b/src/engine/powers/effectmodifiers/HealthRecoverRateEffectModifier.java
new file mode 100644
index 00000000..2eb38cdf
--- /dev/null
+++ b/src/engine/powers/effectmodifiers/HealthRecoverRateEffectModifier.java
@@ -0,0 +1,47 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.powers.effectmodifiers;
+
+import engine.jobs.AbstractEffectJob;
+import engine.objects.*;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+public class HealthRecoverRateEffectModifier extends AbstractEffectModifier {
+
+	public HealthRecoverRateEffectModifier(ResultSet rs) throws SQLException {
+		super(rs);
+	}
+
+	@Override
+	protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) {
+
+	}
+
+	@Override
+	public void applyBonus(AbstractCharacter ac, int trains) {
+		
+		ac.update();
+		Float amount = 0f;
+		PlayerBonuses bonus = ac.getBonuses();
+		if (this.useRampAdd)
+			amount = this.percentMod + (this.ramp * trains);
+		else
+			amount = this.percentMod * (1 + (this.ramp * trains));
+		amount = amount/100;
+		bonus.multRegen(this.modType, amount); //positive regen modifiers
+	}
+
+	@Override
+	public void applyBonus(Item item, int trains) {}
+	@Override
+	public void applyBonus(Building building, int trains) {}
+}
diff --git a/src/engine/powers/effectmodifiers/IgnoreDamageCapEffectModifier.java b/src/engine/powers/effectmodifiers/IgnoreDamageCapEffectModifier.java
new file mode 100644
index 00000000..6b2ffbaf
--- /dev/null
+++ b/src/engine/powers/effectmodifiers/IgnoreDamageCapEffectModifier.java
@@ -0,0 +1,46 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.powers.effectmodifiers;
+
+import engine.jobs.AbstractEffectJob;
+import engine.objects.*;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.HashSet;
+
+public class IgnoreDamageCapEffectModifier extends AbstractEffectModifier {
+
+	public IgnoreDamageCapEffectModifier(ResultSet rs) throws SQLException {
+		super(rs);
+	}
+
+	@Override
+	protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) {
+
+	}
+
+	@Override
+	public void applyBonus(AbstractCharacter ac, int trains) {
+		PlayerBonuses bonus = ac.getBonuses();
+		if (bonus == null)
+			return;
+		
+		if (bonus.getList(this.modType) == null)
+			bonus.setList(this.modType, new HashSet<>());
+		bonus.getList(this.modType).add(this.sourceType);
+		
+	}
+
+	@Override
+	public void applyBonus(Item item, int trains) {}
+	@Override
+	public void applyBonus(Building building, int trains) {}
+}
diff --git a/src/engine/powers/effectmodifiers/IgnorePassiveDefenseEffectModifier.java b/src/engine/powers/effectmodifiers/IgnorePassiveDefenseEffectModifier.java
new file mode 100644
index 00000000..3b02a302
--- /dev/null
+++ b/src/engine/powers/effectmodifiers/IgnorePassiveDefenseEffectModifier.java
@@ -0,0 +1,39 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.powers.effectmodifiers;
+
+import engine.jobs.AbstractEffectJob;
+import engine.objects.*;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+public class IgnorePassiveDefenseEffectModifier extends AbstractEffectModifier {
+
+	public IgnorePassiveDefenseEffectModifier(ResultSet rs) throws SQLException {
+		super(rs);
+	}
+
+	@Override
+	protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) {
+
+	}
+
+	@Override
+	public void applyBonus(AbstractCharacter ac, int trains) {
+		PlayerBonuses bonus = ac.getBonuses();
+		bonus.setBool(this.modType,this.sourceType,true);
+	}
+
+	@Override
+	public void applyBonus(Item item, int trains) {}
+	@Override
+	public void applyBonus(Building building, int trains) {}
+}
diff --git a/src/engine/powers/effectmodifiers/ImmuneToAttackEffectModifier.java b/src/engine/powers/effectmodifiers/ImmuneToAttackEffectModifier.java
new file mode 100644
index 00000000..35876f37
--- /dev/null
+++ b/src/engine/powers/effectmodifiers/ImmuneToAttackEffectModifier.java
@@ -0,0 +1,40 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.powers.effectmodifiers;
+
+import engine.jobs.AbstractEffectJob;
+import engine.objects.*;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+public class ImmuneToAttackEffectModifier extends AbstractEffectModifier {
+
+	public ImmuneToAttackEffectModifier(ResultSet rs) throws SQLException {
+		super(rs);
+	}
+
+	@Override
+	protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) {
+
+	}
+
+	@Override
+	public void applyBonus(AbstractCharacter ac, int trains) {
+
+		PlayerBonuses bonus = ac.getBonuses();
+		bonus.setBool(this.modType,this.sourceType,true);
+	}
+
+	@Override
+	public void applyBonus(Item item, int trains) {}
+	@Override
+	public void applyBonus(Building building, int trains) {}
+}
diff --git a/src/engine/powers/effectmodifiers/ImmuneToEffectModifier.java b/src/engine/powers/effectmodifiers/ImmuneToEffectModifier.java
new file mode 100644
index 00000000..da21abec
--- /dev/null
+++ b/src/engine/powers/effectmodifiers/ImmuneToEffectModifier.java
@@ -0,0 +1,39 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.powers.effectmodifiers;
+
+import engine.jobs.AbstractEffectJob;
+import engine.objects.*;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+public class ImmuneToEffectModifier extends AbstractEffectModifier {
+
+	public ImmuneToEffectModifier(ResultSet rs) throws SQLException {
+		super(rs);
+	}
+
+	@Override
+	protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) {
+
+	}
+
+	@Override
+	public void applyBonus(AbstractCharacter ac, int trains) {
+		PlayerBonuses bonus = ac.getBonuses();
+		bonus.setBool(this.modType,this.sourceType,true);
+	}
+
+	@Override
+	public void applyBonus(Item item, int trains) {}
+	@Override
+	public void applyBonus(Building building, int trains) {}
+}
diff --git a/src/engine/powers/effectmodifiers/ImmuneToPowersEffectModifier.java b/src/engine/powers/effectmodifiers/ImmuneToPowersEffectModifier.java
new file mode 100644
index 00000000..67b4225d
--- /dev/null
+++ b/src/engine/powers/effectmodifiers/ImmuneToPowersEffectModifier.java
@@ -0,0 +1,40 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.powers.effectmodifiers;
+
+import engine.jobs.AbstractEffectJob;
+import engine.objects.*;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+public class ImmuneToPowersEffectModifier extends AbstractEffectModifier {
+
+	public ImmuneToPowersEffectModifier(ResultSet rs) throws SQLException {
+		super(rs);
+	}
+
+	@Override
+	protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) {
+
+	}
+
+	@Override
+	public void applyBonus(AbstractCharacter ac, int trains) {
+		
+		PlayerBonuses bonus = ac.getBonuses();
+		bonus.setBool(this.modType,this.sourceType,true);
+	}
+
+	@Override
+	public void applyBonus(Item item, int trains) {}
+	@Override
+	public void applyBonus(Building building, int trains) {}
+}
diff --git a/src/engine/powers/effectmodifiers/InvisibleEffectModifier.java b/src/engine/powers/effectmodifiers/InvisibleEffectModifier.java
new file mode 100644
index 00000000..c9aba42a
--- /dev/null
+++ b/src/engine/powers/effectmodifiers/InvisibleEffectModifier.java
@@ -0,0 +1,82 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.powers.effectmodifiers;
+
+import engine.Enum;
+import engine.gameManager.SessionManager;
+import engine.jobs.AbstractEffectJob;
+import engine.net.client.ClientConnection;
+import engine.objects.*;
+import engine.powers.ActionsBase;
+import engine.powers.PowersBase;
+import org.pmw.tinylog.Logger;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+public class InvisibleEffectModifier extends AbstractEffectModifier {
+
+	public InvisibleEffectModifier(ResultSet rs) throws SQLException {
+		super(rs);
+	}
+
+	@Override
+	protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) {
+
+		if (awo.getObjectType().equals(Enum.GameObjectType.PlayerCharacter)) {
+			PlayerCharacter pc = (PlayerCharacter) awo;
+
+			if (effect == null)
+				return;
+
+			PowersBase pb = effect.getPower();
+			if (pb == null)
+				return;
+
+			ActionsBase ab = effect.getAction();
+
+			if (ab == null)
+				return;
+
+			//send invis message to everyone around.
+			ClientConnection origin = SessionManager.getClientConnection(pc);
+			if (origin == null)
+				return;
+
+			ab.getDurationInSeconds(trains);
+
+			pc.setHidden(trains);
+
+			pc.setTimeStampNow("Invis");
+
+		}
+		else {
+			Logger.error( "Cannot go invis on a non player.");
+		}
+	}
+
+	@Override
+	public void applyBonus(AbstractCharacter ac, int trains) {
+		if (ac == null)
+			return;
+		PlayerBonuses bonus = ac.getBonuses();
+		if (bonus != null)
+			bonus.updateIfHigher(this, (float)trains);
+
+		//remove pets
+		if (ac.getObjectType().equals(Enum.GameObjectType.PlayerCharacter))
+			((PlayerCharacter)ac).dismissPet();
+	}
+
+	@Override
+	public void applyBonus(Item item, int trains) {}
+	@Override
+	public void applyBonus(Building building, int trains) {}
+}
diff --git a/src/engine/powers/effectmodifiers/ItemNameEffectModifier.java b/src/engine/powers/effectmodifiers/ItemNameEffectModifier.java
new file mode 100644
index 00000000..16b91db3
--- /dev/null
+++ b/src/engine/powers/effectmodifiers/ItemNameEffectModifier.java
@@ -0,0 +1,103 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.powers.effectmodifiers;
+
+import engine.jobs.AbstractEffectJob;
+import engine.objects.AbstractCharacter;
+import engine.objects.AbstractWorldObject;
+import engine.objects.Building;
+import engine.objects.Item;
+import engine.powers.EffectsBase;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+public class ItemNameEffectModifier extends AbstractEffectModifier {
+
+	String name = "";
+
+	public ItemNameEffectModifier(ResultSet rs) throws SQLException {
+		super(rs);
+
+		//We're going to add effect names to a lookup map for ./makeitem
+		int ID = rs.getInt("ID");
+		switch (ID) { //don't add these ID's to the name list. They're duplicates
+			case 4259: return;
+			case 4210: return;
+			case 4: return;
+			case 97: return;
+			case 610: return;
+			case 4442: return;
+			case 5106: return;
+			case 4637: return;
+			case 2271: return;
+			case 587: return;
+			case 600: return;
+			case 3191: return;
+			case 3589: return;
+			case 3950: return;
+			case 3499: return;
+			case 4925: return;
+			case 15: return;
+			case 5101: return;
+			case 2418: return;
+			case 183: return;
+			case 373: return;
+			case 1893: return;
+			case 3127: return;
+			case 1232: return;
+			case 4522: return;
+			case 4817: return;
+			case 2833: return;
+			case 4469: return;
+			case 2122: return;
+			case 3057: return;
+			case 3070: return;
+			case 191: return;
+			case 3117: return;
+			case 3702: return;
+			case 1619: return;
+			case 2584: return;
+			case 414: return;
+			case 2078: return;
+			case 4844: return;
+			case 2275: return;
+		}
+
+		String namePre = rs.getString("string1");
+		String nameSuf = rs.getString("string2");
+		String n = (namePre.isEmpty()) ? nameSuf : namePre;
+		this.name = n;
+		n = n.toLowerCase();
+		n = n.replace(" ", "_");
+		String IDString = rs.getString("IDString");
+		IDString = IDString.substring(0, IDString.length() - 1);
+		EffectsBase.addItemEffectsByName(n, IDString);
+	}
+
+	public String getName() {
+		return this.name;
+	}
+
+	@Override
+	protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) {
+
+	}
+
+	@Override
+	public void applyBonus(AbstractCharacter ac, int trains) {
+
+	}
+
+	@Override
+	public void applyBonus(Item item, int trains) {}
+	@Override
+	public void applyBonus(Building building, int trains) {}
+}
diff --git a/src/engine/powers/effectmodifiers/ManaEffectModifier.java b/src/engine/powers/effectmodifiers/ManaEffectModifier.java
new file mode 100644
index 00000000..94e20793
--- /dev/null
+++ b/src/engine/powers/effectmodifiers/ManaEffectModifier.java
@@ -0,0 +1,232 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+
+
+
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.powers.effectmodifiers;
+
+import engine.Enum;
+import engine.Enum.DamageType;
+import engine.Enum.ModType;
+import engine.Enum.SourceType;
+import engine.gameManager.ChatManager;
+import engine.jobs.AbstractEffectJob;
+import engine.jobs.DamageOverTimeJob;
+import engine.net.DispatchMessage;
+import engine.net.client.msg.ModifyHealthMsg;
+import engine.objects.*;
+import engine.powers.ActionsBase;
+import engine.powers.poweractions.AbstractPowerAction;
+import engine.powers.poweractions.DamageOverTimePowerAction;
+import engine.powers.poweractions.DirectDamagePowerAction;
+import org.pmw.tinylog.Logger;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.concurrent.ThreadLocalRandom;
+
+public class ManaEffectModifier extends AbstractEffectModifier {
+
+	private DamageType damageType;
+
+	public ManaEffectModifier(ResultSet rs) throws SQLException {
+		super(rs);
+		String damageTypeDB = rs.getString("type");
+		try {
+			this.damageType = DamageType.valueOf(damageTypeDB);
+		} catch (IllegalArgumentException e) {
+			Logger.error("DamageType could not be loaded from database. " + "UUID = " + this.UUID
+					+ " value received = '" + damageTypeDB + '\'', e);
+		}
+	}
+
+	@Override
+	protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) {
+		if (awo == null) {
+			Logger.error( "_applyEffectModifier(): NULL AWO passed in.");
+			return;
+		}
+
+		if (effect == null) {
+			Logger.error( "_applyEffectModifier(): NULL AbstractEffectJob passed in.");
+			return;
+		}
+
+		if (!AbstractWorldObject.IsAbstractCharacter(awo))
+			return;
+		AbstractCharacter awoac = (AbstractCharacter) awo;
+
+		float modAmount = 0f;
+
+		// Modify Mana by percent
+		if (this.percentMod != 0f) {
+
+			float mod = 1f;
+			if (this.useRampAdd)
+				mod = (this.percentMod + (this.ramp * trains)) / 100;
+			else
+				mod = (this.percentMod * (1 + (this.ramp * trains))) / 100;
+			modAmount = mod * awoac.getManaMax();
+
+			if (awoac.isSit())
+				modAmount *= 2.5f;
+
+			//debug for spell damage and atr
+			if (source.getDebug(16) && source.getObjectType().equals(Enum.GameObjectType.PlayerCharacter)) {
+
+				PlayerCharacter pc = (PlayerCharacter) source;
+				String smsg = "Percent Damage: " + mod * 100 + '%';
+
+				ChatManager.chatSystemInfo(pc, smsg);
+			}
+		}
+
+		// Modify health by min/max amount
+		else if (this.minMod != 0f || this.maxMod != 0f) {
+			float min = this.minMod;
+			float max = this.maxMod;
+			if (this.ramp > 0f) {
+				float mod = this.ramp * trains;
+				if (this.useRampAdd) {
+					min += mod;
+					max += mod;
+				} else {
+					min *= (1 + mod);
+					max *= (1 + mod);
+				}
+			}
+			if (source.getObjectType().equals(Enum.GameObjectType.PlayerCharacter)) {
+				PlayerCharacter pc = (PlayerCharacter) source;
+
+				float focus;
+				CharacterSkill skill = pc.getSkills().get(effect.getPower().getSkillName());
+				if (skill == null)
+					focus = CharacterSkill.getQuickMastery(pc, effect.getPower().getSkillName());
+				else
+					focus = skill.getModifiedAmount();
+				//TODO clean up old formulas once new one is verified
+				//				min *= (0.5 + 0.0075 * pc.getStatIntCurrent() + 0.011 * pc.getStatSpiCurrent() + 0.0196 * focus);
+				//				max *= (0.62 + 0.0192 * pc.getStatIntCurrent() + 0.00415 * pc.getStatSpiCurrent() + 0.015 * focus);
+				float intt = (pc.getStatIntCurrent() >= 1) ? (float)pc.getStatIntCurrent() : 1f;
+				float spi = (pc.getStatSpiCurrent() >= 1) ? (float)pc.getStatSpiCurrent() : 1f;
+				//				min *= (intt * 0.0045 + 0.055 * (float)Math.sqrt(intt - 0.5) + spi * 0.006 + 0.07 * (float)Math.sqrt(spi - 0.5) + 0.02 * (int)focus);
+				//				max *= (intt * 0.0117 + 0.13 * (float)Math.sqrt(intt - 0.5) + spi * 0.0024 + (float)Math.sqrt(spi - 0.5) * 0.021 + 0.015 * (int)focus);
+				min = HealthEffectModifier.getMinDamage(min, intt, spi, focus);
+				max = HealthEffectModifier.getMaxDamage(max, intt, spi, focus);
+
+				//debug for spell damage and atr
+				if (pc.getDebug(16)) {
+					String smsg = "Damage: " + (int)Math.abs(min) + " - " + (int)Math.abs(max);
+					ChatManager.chatSystemInfo(pc, smsg);
+				}
+			}
+			modAmount = calculateDamage(source, awoac, min, max, awo, trains);
+			PlayerBonuses bonus = source.getBonuses();
+
+			// Apply any power effect modifiers (such as stances)
+			if (bonus != null)
+				modAmount *=  (1 + bonus.getFloatPercentAll(ModType.PowerDamageModifier, SourceType.None));
+		}
+		if (modAmount == 0f)
+			return;
+		if (AbstractWorldObject.IsAbstractCharacter(awo)) {
+			AbstractCharacter ac = (AbstractCharacter) awo;
+			int powerID = 0, effectID = 0;
+			String powerName = "";
+			if (effect.getPower() != null) {
+				powerID = effect.getPower().getToken();
+				powerName = effect.getPower().getName();
+			}
+			if (effect.getEffect() != null) {
+				effectID = effect.getEffect().getToken();
+			}
+
+			//see if target is immune to heals
+			if (modAmount > 0f) {
+				boolean skipImmune = false;
+				if (effect.getAction().getPowerAction() instanceof DirectDamagePowerAction) {
+					ArrayList<ActionsBase> actions = effect.getPower().getActions();
+					for (ActionsBase ab : actions) {
+						AbstractPowerAction apa = ab.getPowerAction();
+						if (apa instanceof DamageOverTimePowerAction)
+							skipImmune = true;
+					}
+				}
+				PlayerBonuses bonus = ac.getBonuses();
+				if (!skipImmune && bonus.getFloat(ModType.BlackMantle, SourceType.Heal) >= trains) {
+					ModifyHealthMsg mhm = new ModifyHealthMsg(source, ac, 0f, 0f, 0f, powerID, powerName, trains, effectID);
+					mhm.setUnknown03(5); //set target is immune
+					DispatchMessage.sendToAllInRange(ac, mhm);
+					return;
+				}
+			}
+
+			ac.modifyMana(modAmount, source);
+
+			ModifyHealthMsg mhm = new ModifyHealthMsg(source, ac, 0f, modAmount, 0f, powerID, powerName, trains,
+					effectID);
+			if (effect instanceof DamageOverTimeJob)
+				mhm.setOmitFromChat(1);
+			DispatchMessage.sendToAllInRange(ac, mhm);
+		}
+	}
+
+	private float calculateDamage(AbstractCharacter source, AbstractCharacter target, float minDamage, float maxDamage, AbstractWorldObject awo, int trains) {
+		// get range between min and max
+		float range = maxDamage - minDamage;
+
+		// Damage is calculated twice to average a more central point
+		float damage = ThreadLocalRandom.current().nextFloat() * range;
+		damage = (damage + (ThreadLocalRandom.current().nextFloat() * range)) / 2;
+
+		// put it back between min and max
+		damage += minDamage;
+
+		Resists resists = null;
+		// get resists
+		if (AbstractWorldObject.IsAbstractCharacter(awo)) {
+			AbstractCharacter ac = (AbstractCharacter) awo;
+			resists = ac.getResists();
+		} else if (awo.getObjectType().equals(Enum.GameObjectType.Building))
+			resists = ((Building) awo).getResists();
+
+		// calculate resists in if any
+		if (resists != null)
+			damage = resists.getResistedDamage(source, target, damageType, damage * -1, trains) * -1;
+
+		if (AbstractWorldObject.IsAbstractCharacter(awo)) {
+			AbstractCharacter ac = (AbstractCharacter) awo;
+			if (ac.isSit())
+				damage *= 2.5f; // increase damage if sitting
+		}
+
+		return damage;
+	}
+
+	@Override
+	public void applyBonus(AbstractCharacter ac, int trains) {
+
+	}
+
+	@Override
+	public void applyBonus(Item item, int trains) {}
+	@Override
+	public void applyBonus(Building building, int trains) {}
+}
diff --git a/src/engine/powers/effectmodifiers/ManaFullEffectModifier.java b/src/engine/powers/effectmodifiers/ManaFullEffectModifier.java
new file mode 100644
index 00000000..ba637c39
--- /dev/null
+++ b/src/engine/powers/effectmodifiers/ManaFullEffectModifier.java
@@ -0,0 +1,53 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.powers.effectmodifiers;
+
+import engine.jobs.AbstractEffectJob;
+import engine.objects.*;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+public class ManaFullEffectModifier extends AbstractEffectModifier {
+
+	public ManaFullEffectModifier(ResultSet rs) throws SQLException {
+		super(rs);
+	}
+
+	@Override
+	protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) {
+
+	}
+
+	@Override
+	public void applyBonus(AbstractCharacter ac, int trains) {
+		Float amount = 0f;
+		PlayerBonuses bonus = ac.getBonuses();
+		if (this.percentMod != 0f) { //Stat Percent Modifiers
+			if (this.useRampAdd)
+				amount = this.percentMod + (this.ramp * trains);
+			else
+				amount = this.percentMod * (1 + (this.ramp * trains));
+			amount = amount/100;
+			bonus.addFloat(this, amount);
+		} else { //Stat Modifiers
+			if (this.useRampAdd)
+				amount = this.minMod + (this.ramp * trains);
+			else
+				amount = this.minMod * (1 + (this.ramp * trains));
+			bonus.addFloat(this, amount);
+		}
+	}
+
+	@Override
+	public void applyBonus(Item item, int trains) {}
+	@Override
+	public void applyBonus(Building building, int trains) {}
+}
diff --git a/src/engine/powers/effectmodifiers/ManaRecoverRateEffectModifier.java b/src/engine/powers/effectmodifiers/ManaRecoverRateEffectModifier.java
new file mode 100644
index 00000000..64886767
--- /dev/null
+++ b/src/engine/powers/effectmodifiers/ManaRecoverRateEffectModifier.java
@@ -0,0 +1,45 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.powers.effectmodifiers;
+
+import engine.jobs.AbstractEffectJob;
+import engine.objects.*;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+public class ManaRecoverRateEffectModifier extends AbstractEffectModifier {
+
+	public ManaRecoverRateEffectModifier(ResultSet rs) throws SQLException {
+		super(rs);
+	}
+
+	@Override
+	protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) {
+
+	}
+
+	@Override
+	public void applyBonus(AbstractCharacter ac, int trains) {
+		Float amount = 0f;
+		PlayerBonuses bonus = ac.getBonuses();
+		if (this.useRampAdd)
+			amount = this.percentMod + (this.ramp * trains);
+		else
+			amount = this.percentMod * (1 + (this.ramp * trains));
+		amount = amount/100;
+		bonus.multRegen(this.modType, amount); //positive regen modifiers
+	}
+
+	@Override
+	public void applyBonus(Item item, int trains) {}
+	@Override
+	public void applyBonus(Building building, int trains) {}
+}
diff --git a/src/engine/powers/effectmodifiers/MaxDamageEffectModifier.java b/src/engine/powers/effectmodifiers/MaxDamageEffectModifier.java
new file mode 100644
index 00000000..e52045f2
--- /dev/null
+++ b/src/engine/powers/effectmodifiers/MaxDamageEffectModifier.java
@@ -0,0 +1,61 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.powers.effectmodifiers;
+
+import engine.jobs.AbstractEffectJob;
+import engine.objects.AbstractCharacter;
+import engine.objects.AbstractWorldObject;
+import engine.objects.Building;
+import engine.objects.Item;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+public class MaxDamageEffectModifier extends AbstractEffectModifier {
+
+	public MaxDamageEffectModifier(ResultSet rs) throws SQLException {
+		super(rs);
+	}
+
+	@Override
+	protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) {
+
+	}
+
+	@Override
+	public void applyBonus(AbstractCharacter ac, int trains) {
+
+	}
+
+	@Override
+	public void applyBonus(Item item, int trains) {
+		if (item == null)
+			return;
+		String key; float amount = 0f;
+		if (this.percentMod != 0f) {
+			if (this.useRampAdd)
+				amount = (this.percentMod + (this.ramp * trains)) / 100f;
+			else
+				amount = (this.percentMod * (1 + (this.ramp * trains))) / 100f;
+			amount = amount/100;
+			key = "max.percent";
+		} else {
+			if (this.useRampAdd)
+				amount = this.minMod + (this.ramp * trains);
+			else
+				amount = this.minMod * (1 + (this.ramp * trains));
+			key = "max";
+		}
+		item.addBonus(this, amount);
+	}
+
+	@Override
+	public void applyBonus(Building building, int trains) {}
+}
diff --git a/src/engine/powers/effectmodifiers/MeleeDamageEffectModifier.java b/src/engine/powers/effectmodifiers/MeleeDamageEffectModifier.java
new file mode 100644
index 00000000..f822aba4
--- /dev/null
+++ b/src/engine/powers/effectmodifiers/MeleeDamageEffectModifier.java
@@ -0,0 +1,53 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.powers.effectmodifiers;
+
+import engine.jobs.AbstractEffectJob;
+import engine.objects.*;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+public class MeleeDamageEffectModifier extends AbstractEffectModifier {
+
+	public MeleeDamageEffectModifier(ResultSet rs) throws SQLException {
+		super(rs);
+	}
+
+	@Override
+	protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) {
+
+	}
+
+	@Override
+	public void applyBonus(AbstractCharacter ac, int trains) {
+		Float amount = 0f;
+		PlayerBonuses bonus = ac.getBonuses();
+		if (this.percentMod != 0f) { //Stat Percent Modifiers
+			if (this.useRampAdd)
+				amount = this.percentMod + (this.ramp * trains);
+			else
+				amount = this.percentMod * (1 + (this.ramp * trains));
+			amount = amount/100;
+			bonus.addFloat(this, amount);
+		} else { //Stat Modifiers
+			if (this.useRampAdd)
+				amount = this.minMod + (this.ramp * trains);
+			else
+				amount = this.minMod * (1 + (this.ramp * trains));
+			bonus.addFloat(this, amount);
+		}
+	}
+
+	@Override
+	public void applyBonus(Item item, int trains) {}
+	@Override
+	public void applyBonus(Building building, int trains) {}
+}
diff --git a/src/engine/powers/effectmodifiers/MinDamageEffectModifier.java b/src/engine/powers/effectmodifiers/MinDamageEffectModifier.java
new file mode 100644
index 00000000..90e73fe0
--- /dev/null
+++ b/src/engine/powers/effectmodifiers/MinDamageEffectModifier.java
@@ -0,0 +1,61 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.powers.effectmodifiers;
+
+import engine.jobs.AbstractEffectJob;
+import engine.objects.AbstractCharacter;
+import engine.objects.AbstractWorldObject;
+import engine.objects.Building;
+import engine.objects.Item;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+public class MinDamageEffectModifier extends AbstractEffectModifier {
+
+	public MinDamageEffectModifier(ResultSet rs) throws SQLException {
+		super(rs);
+	}
+
+	@Override
+	protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) {
+
+	}
+
+	@Override
+	public void applyBonus(AbstractCharacter ac, int trains) {
+
+	}
+
+	@Override
+	public void applyBonus(Item item, int trains) {
+		if (item == null)
+			return;
+		String key; float amount = 0f;
+		if (this.percentMod != 0f) {
+			if (this.useRampAdd)
+				amount = (this.percentMod + (this.ramp * trains)) / 100f;
+			else
+				amount = (this.percentMod * (1 + (this.ramp * trains))) / 100f;
+			amount = amount/100;
+			key = "min.percent";
+		} else {
+			if (this.useRampAdd)
+				amount = this.minMod + (this.ramp * trains);
+			else
+				amount = this.minMod * (1 + (this.ramp * trains));
+			key = "min";
+		}
+	item.addBonus(this, amount);
+	}
+
+	@Override
+	public void applyBonus(Building building, int trains) {}
+}
diff --git a/src/engine/powers/effectmodifiers/NoModEffectModifier.java b/src/engine/powers/effectmodifiers/NoModEffectModifier.java
new file mode 100644
index 00000000..f5716e03
--- /dev/null
+++ b/src/engine/powers/effectmodifiers/NoModEffectModifier.java
@@ -0,0 +1,53 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.powers.effectmodifiers;
+
+import engine.Enum.GameObjectType;
+import engine.jobs.AbstractEffectJob;
+import engine.objects.*;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+public class NoModEffectModifier extends AbstractEffectModifier {
+
+	public NoModEffectModifier(ResultSet rs) throws SQLException {
+		super(rs);
+	}
+
+	@Override
+	protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) {
+		//TODO check if anything needs removed.
+	}
+
+	@Override
+	public void applyBonus(AbstractCharacter ac, int trains) {
+		PlayerBonuses bonus = ac.getBonuses();
+		bonus.setBool(this.modType,this.sourceType,true);
+		
+		switch (this.sourceType){
+		case Fly:
+			if (!ac.getObjectType().equals(GameObjectType.PlayerCharacter))
+				return;
+			PlayerCharacter flyer = (PlayerCharacter)ac;
+			
+			if (flyer.getAltitude() > 0)
+				flyer.update();
+			PlayerCharacter.GroundPlayer(flyer);
+			break;
+			
+		}
+	}
+
+	@Override
+	public void applyBonus(Item item, int trains) {}
+	@Override
+	public void applyBonus(Building building, int trains) {}
+}
diff --git a/src/engine/powers/effectmodifiers/OCVEffectModifier.java b/src/engine/powers/effectmodifiers/OCVEffectModifier.java
new file mode 100644
index 00000000..5c91affe
--- /dev/null
+++ b/src/engine/powers/effectmodifiers/OCVEffectModifier.java
@@ -0,0 +1,53 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.powers.effectmodifiers;
+
+import engine.jobs.AbstractEffectJob;
+import engine.objects.*;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+public class OCVEffectModifier extends AbstractEffectModifier {
+
+	public OCVEffectModifier(ResultSet rs) throws SQLException {
+		super(rs);
+	}
+
+	@Override
+	protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) {
+
+	}
+
+	@Override
+	public void applyBonus(AbstractCharacter ac, int trains) {
+		Float amount = 0f;
+		PlayerBonuses bonus = ac.getBonuses();
+		if (this.percentMod != 0f) { //Stat Percent Modifiers
+			if (this.useRampAdd)
+				amount = this.percentMod + (this.ramp * trains);
+			else
+				amount = this.percentMod * (1 + (this.ramp * trains));
+			amount = amount/100;
+			bonus.addFloat(this, amount);
+		} else { //Stat Modifiers
+			if (this.useRampAdd)
+				amount = this.minMod + (this.ramp * trains);
+			else
+				amount = this.minMod * (1 + (this.ramp * trains));
+			bonus.addFloat(this, amount);
+		}
+	}
+
+	@Override
+	public void applyBonus(Item item, int trains) {}
+	@Override
+	public void applyBonus(Building building, int trains) {}
+}
diff --git a/src/engine/powers/effectmodifiers/ParryEffectModifier.java b/src/engine/powers/effectmodifiers/ParryEffectModifier.java
new file mode 100644
index 00000000..6a0f0f27
--- /dev/null
+++ b/src/engine/powers/effectmodifiers/ParryEffectModifier.java
@@ -0,0 +1,53 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.powers.effectmodifiers;
+
+import engine.jobs.AbstractEffectJob;
+import engine.objects.*;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+public class ParryEffectModifier extends AbstractEffectModifier {
+
+	public ParryEffectModifier(ResultSet rs) throws SQLException {
+		super(rs);
+	}
+
+	@Override
+	protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) {
+
+	}
+
+	@Override
+	public void applyBonus(AbstractCharacter ac, int trains) {
+		Float amount = 0f;
+		PlayerBonuses bonus = ac.getBonuses();
+		if (this.percentMod != 0f) { //Stat Percent Modifiers
+			if (this.useRampAdd)
+				amount = this.percentMod + (this.ramp * trains);
+			else
+				amount = this.percentMod * (1 + (this.ramp * trains));
+			amount = amount/100;
+			bonus.addFloat(this, amount);
+		} else { //Stat Modifiers
+			if (this.useRampAdd)
+				amount = this.minMod + (this.ramp * trains);
+			else
+				amount = this.minMod * (1 + (this.ramp * trains));
+			bonus.addFloat(this, amount);
+		}
+	}
+
+	@Override
+	public void applyBonus(Item item, int trains) {}
+	@Override
+	public void applyBonus(Building building, int trains) {}
+}
diff --git a/src/engine/powers/effectmodifiers/PassiveDefenseEffectModifier.java b/src/engine/powers/effectmodifiers/PassiveDefenseEffectModifier.java
new file mode 100644
index 00000000..9789c4c4
--- /dev/null
+++ b/src/engine/powers/effectmodifiers/PassiveDefenseEffectModifier.java
@@ -0,0 +1,53 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.powers.effectmodifiers;
+
+import engine.jobs.AbstractEffectJob;
+import engine.objects.*;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+public class PassiveDefenseEffectModifier extends AbstractEffectModifier {
+
+	public PassiveDefenseEffectModifier(ResultSet rs) throws SQLException {
+		super(rs);
+	}
+
+	@Override
+	protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) {
+
+	}
+
+	@Override
+	public void applyBonus(AbstractCharacter ac, int trains) {
+		Float amount = 0f;
+		PlayerBonuses bonus = ac.getBonuses();
+		if (this.percentMod != 0f) { //Stat Percent Modifiers
+			if (this.useRampAdd)
+				amount = this.percentMod + (this.ramp * trains);
+			else
+				amount = this.percentMod * (1 + (this.ramp * trains));
+			amount = amount/100;
+			bonus.addFloat(this, amount);
+		} else { //Stat Modifiers
+			if (this.useRampAdd)
+				amount = this.minMod + (this.ramp * trains);
+			else
+				amount = this.minMod * (1 + (this.ramp * trains));
+			bonus.addFloat(this, amount);
+		}
+	}
+
+	@Override
+	public void applyBonus(Item item, int trains) {}
+	@Override
+	public void applyBonus(Building building, int trains) {}
+}
diff --git a/src/engine/powers/effectmodifiers/PowerCostEffectModifier.java b/src/engine/powers/effectmodifiers/PowerCostEffectModifier.java
new file mode 100644
index 00000000..3a4c54d9
--- /dev/null
+++ b/src/engine/powers/effectmodifiers/PowerCostEffectModifier.java
@@ -0,0 +1,53 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.powers.effectmodifiers;
+
+import engine.jobs.AbstractEffectJob;
+import engine.objects.*;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+public class PowerCostEffectModifier extends AbstractEffectModifier {
+
+	public PowerCostEffectModifier(ResultSet rs) throws SQLException {
+		super(rs);
+	}
+
+	@Override
+	protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) {
+
+	}
+
+	@Override
+	public void applyBonus(AbstractCharacter ac, int trains) {
+		Float amount = 0f;
+		PlayerBonuses bonus = ac.getBonuses();
+		if (this.percentMod != 0f) { //Stat Percent Modifiers
+			if (this.useRampAdd)
+				amount = this.percentMod + (this.ramp * trains);
+			else
+				amount = this.percentMod * (1 + (this.ramp * trains));
+			amount = amount/100;
+			bonus.addFloat(this, amount);
+		} else { //Stat Modifiers
+			if (this.useRampAdd)
+				amount = this.minMod + (this.ramp * trains);
+			else
+				amount = this.minMod * (1 + (this.ramp * trains));
+			bonus.addFloat(this, amount);
+		}
+	}
+
+	@Override
+	public void applyBonus(Item item, int trains) {}
+	@Override
+	public void applyBonus(Building building, int trains) {}
+}
diff --git a/src/engine/powers/effectmodifiers/PowerCostHealthEffectModifier.java b/src/engine/powers/effectmodifiers/PowerCostHealthEffectModifier.java
new file mode 100644
index 00000000..e6006a3a
--- /dev/null
+++ b/src/engine/powers/effectmodifiers/PowerCostHealthEffectModifier.java
@@ -0,0 +1,41 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.powers.effectmodifiers;
+
+import engine.jobs.AbstractEffectJob;
+import engine.objects.AbstractCharacter;
+import engine.objects.AbstractWorldObject;
+import engine.objects.Building;
+import engine.objects.Item;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+public class PowerCostHealthEffectModifier extends AbstractEffectModifier {
+
+	public PowerCostHealthEffectModifier(ResultSet rs) throws SQLException {
+		super(rs);
+	}
+
+	@Override
+	protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) {
+
+	}
+
+	@Override
+	public void applyBonus(AbstractCharacter ac, int trains) {
+
+	}
+
+	@Override
+	public void applyBonus(Item item, int trains) {}
+	@Override
+	public void applyBonus(Building building, int trains) {}
+}
diff --git a/src/engine/powers/effectmodifiers/PowerDamageEffectModifier.java b/src/engine/powers/effectmodifiers/PowerDamageEffectModifier.java
new file mode 100644
index 00000000..238e3fbd
--- /dev/null
+++ b/src/engine/powers/effectmodifiers/PowerDamageEffectModifier.java
@@ -0,0 +1,53 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.powers.effectmodifiers;
+
+import engine.jobs.AbstractEffectJob;
+import engine.objects.*;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+public class PowerDamageEffectModifier extends AbstractEffectModifier {
+
+	public PowerDamageEffectModifier(ResultSet rs) throws SQLException {
+		super(rs);
+	}
+
+	@Override
+	protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) {
+
+	}
+
+	@Override
+	public void applyBonus(AbstractCharacter ac, int trains) {
+		Float amount = 0f;
+		PlayerBonuses bonus = ac.getBonuses();
+		if (this.percentMod != 0f) { //Stat Percent Modifiers
+			if (this.useRampAdd)
+				amount = this.percentMod + (this.ramp * trains);
+			else
+				amount = this.percentMod * (1 + (this.ramp * trains));
+			amount = amount/100;
+			bonus.addFloat(this, amount);
+		} else { //Stat Modifiers
+			if (this.useRampAdd)
+				amount = this.minMod + (this.ramp * trains);
+			else
+				amount = this.minMod * (1 + (this.ramp * trains));
+			bonus.addFloat(this, amount);
+		}
+	}
+
+	@Override
+	public void applyBonus(Item item, int trains) {}
+	@Override
+	public void applyBonus(Building building, int trains) {}
+}
diff --git a/src/engine/powers/effectmodifiers/ProtectionFromEffectModifier.java b/src/engine/powers/effectmodifiers/ProtectionFromEffectModifier.java
new file mode 100644
index 00000000..f329bbf4
--- /dev/null
+++ b/src/engine/powers/effectmodifiers/ProtectionFromEffectModifier.java
@@ -0,0 +1,42 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.powers.effectmodifiers;
+
+import engine.jobs.AbstractEffectJob;
+import engine.objects.*;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+public class ProtectionFromEffectModifier extends AbstractEffectModifier {
+
+	public ProtectionFromEffectModifier(ResultSet rs) throws SQLException {
+		super(rs);
+	}
+
+	@Override
+	protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) {
+
+	}
+
+	@Override
+	public void applyBonus(AbstractCharacter ac, int trains) {
+		PlayerBonuses bonus = ac.getBonuses();
+		if (bonus == null)
+			return;
+		bonus.setFloat(this, trains);
+	//	bonus.setBool(this, true);
+	}
+
+	@Override
+	public void applyBonus(Item item, int trains) {}
+	@Override
+	public void applyBonus(Building building, int trains) {}
+}
diff --git a/src/engine/powers/effectmodifiers/ResistanceEffectModifier.java b/src/engine/powers/effectmodifiers/ResistanceEffectModifier.java
new file mode 100644
index 00000000..e4286e24
--- /dev/null
+++ b/src/engine/powers/effectmodifiers/ResistanceEffectModifier.java
@@ -0,0 +1,53 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.powers.effectmodifiers;
+
+import engine.jobs.AbstractEffectJob;
+import engine.objects.*;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+public class ResistanceEffectModifier extends AbstractEffectModifier {
+
+	public ResistanceEffectModifier(ResultSet rs) throws SQLException {
+		super(rs);
+	}
+
+	@Override
+	protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) {
+
+	}
+
+	@Override
+	public void applyBonus(AbstractCharacter ac, int trains) {
+		Float amount = 0f;
+		PlayerBonuses bonus = ac.getBonuses();
+		if (this.percentMod != 0f) { //Stat Percent Modifiers
+			if (this.useRampAdd)
+				amount = this.percentMod + (this.ramp * trains);
+			else
+				amount = this.percentMod * (1 + (this.ramp * trains));
+			amount = amount/100;
+			bonus.addFloat(this, amount);
+		} else { //Stat Modifiers
+			if (this.useRampAdd)
+				amount = this.minMod + (this.ramp * trains);
+			else
+				amount = this.minMod * (1 + (this.ramp * trains));
+			bonus.addFloat(this, amount);
+		}
+	}
+
+	@Override
+	public void applyBonus(Item item, int trains) {}
+	@Override
+	public void applyBonus(Building building, int trains) {}
+}
diff --git a/src/engine/powers/effectmodifiers/ScaleHeightEffectModifier.java b/src/engine/powers/effectmodifiers/ScaleHeightEffectModifier.java
new file mode 100644
index 00000000..6cd022a4
--- /dev/null
+++ b/src/engine/powers/effectmodifiers/ScaleHeightEffectModifier.java
@@ -0,0 +1,41 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.powers.effectmodifiers;
+
+import engine.jobs.AbstractEffectJob;
+import engine.objects.AbstractCharacter;
+import engine.objects.AbstractWorldObject;
+import engine.objects.Building;
+import engine.objects.Item;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+public class ScaleHeightEffectModifier extends AbstractEffectModifier {
+
+	public ScaleHeightEffectModifier(ResultSet rs) throws SQLException {
+		super(rs);
+	}
+
+	@Override
+	protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) {
+
+	}
+
+	@Override
+	public void applyBonus(AbstractCharacter ac, int trains) {
+
+	}
+
+	@Override
+	public void applyBonus(Item item, int trains) {}
+	@Override
+	public void applyBonus(Building building, int trains) {}
+}
diff --git a/src/engine/powers/effectmodifiers/ScaleWidthEffectModifier.java b/src/engine/powers/effectmodifiers/ScaleWidthEffectModifier.java
new file mode 100644
index 00000000..00e77e68
--- /dev/null
+++ b/src/engine/powers/effectmodifiers/ScaleWidthEffectModifier.java
@@ -0,0 +1,41 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.powers.effectmodifiers;
+
+import engine.jobs.AbstractEffectJob;
+import engine.objects.AbstractCharacter;
+import engine.objects.AbstractWorldObject;
+import engine.objects.Building;
+import engine.objects.Item;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+public class ScaleWidthEffectModifier extends AbstractEffectModifier {
+
+	public ScaleWidthEffectModifier(ResultSet rs) throws SQLException {
+		super(rs);
+	}
+
+	@Override
+	protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) {
+
+	}
+
+	@Override
+	public void applyBonus(AbstractCharacter ac, int trains) {
+
+	}
+
+	@Override
+	public void applyBonus(Item item, int trains) {}
+	@Override
+	public void applyBonus(Building building, int trains) {}
+}
diff --git a/src/engine/powers/effectmodifiers/ScanRangeEffectModifier.java b/src/engine/powers/effectmodifiers/ScanRangeEffectModifier.java
new file mode 100644
index 00000000..968f2e3e
--- /dev/null
+++ b/src/engine/powers/effectmodifiers/ScanRangeEffectModifier.java
@@ -0,0 +1,53 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.powers.effectmodifiers;
+
+import engine.jobs.AbstractEffectJob;
+import engine.objects.*;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+public class ScanRangeEffectModifier extends AbstractEffectModifier {
+
+	public ScanRangeEffectModifier(ResultSet rs) throws SQLException {
+		super(rs);
+	}
+
+	@Override
+	protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) {
+
+	}
+
+	@Override
+	public void applyBonus(AbstractCharacter ac, int trains) {
+		Float amount = 0f;
+		PlayerBonuses bonus = ac.getBonuses();
+		if (this.percentMod != 0f) { //Stat Percent Modifiers
+			if (this.useRampAdd)
+				amount = this.percentMod + (this.ramp * trains);
+			else
+				amount = this.percentMod * (1 + (this.ramp * trains));
+			amount = amount/100;
+			bonus.addFloat(this, amount);
+		} else { //Stat Modifiers
+			if (this.useRampAdd)
+				amount = this.minMod + (this.ramp * trains);
+			else
+				amount = this.minMod * (1 + (this.ramp * trains));
+			bonus.addFloat(this, amount);
+		}
+	}
+
+	@Override
+	public void applyBonus(Item item, int trains) {}
+	@Override
+	public void applyBonus(Building building, int trains) {}
+}
diff --git a/src/engine/powers/effectmodifiers/SeeInvisibleEffectModifier.java b/src/engine/powers/effectmodifiers/SeeInvisibleEffectModifier.java
new file mode 100644
index 00000000..94ab63d9
--- /dev/null
+++ b/src/engine/powers/effectmodifiers/SeeInvisibleEffectModifier.java
@@ -0,0 +1,42 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.powers.effectmodifiers;
+
+import engine.jobs.AbstractEffectJob;
+import engine.objects.*;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+public class SeeInvisibleEffectModifier extends AbstractEffectModifier {
+
+	public SeeInvisibleEffectModifier(ResultSet rs) throws SQLException {
+		super(rs);
+	}
+
+	@Override
+	protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) {
+
+	}
+
+	@Override
+	public void applyBonus(AbstractCharacter ac, int trains) {
+		if (ac == null)
+			return;
+		PlayerBonuses bonus = ac.getBonuses();
+		if (bonus != null)
+			bonus.updateIfHigher(this, (float)trains);
+	}
+
+	@Override
+	public void applyBonus(Item item, int trains) {}
+	@Override
+	public void applyBonus(Building building, int trains) {}
+}
diff --git a/src/engine/powers/effectmodifiers/SilencedEffectModifier.java b/src/engine/powers/effectmodifiers/SilencedEffectModifier.java
new file mode 100644
index 00000000..75764cc1
--- /dev/null
+++ b/src/engine/powers/effectmodifiers/SilencedEffectModifier.java
@@ -0,0 +1,40 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.powers.effectmodifiers;
+
+import engine.jobs.AbstractEffectJob;
+import engine.objects.*;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+public class SilencedEffectModifier extends AbstractEffectModifier {
+
+	public SilencedEffectModifier(ResultSet rs) throws SQLException {
+		super(rs);
+	}
+
+	@Override
+	protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) {
+
+	}
+
+	@Override
+	public void applyBonus(AbstractCharacter ac, int trains) {
+		PlayerBonuses bonus = ac.getBonuses();
+		bonus.setBool(this.modType,this.sourceType, true);
+	
+	}
+
+	@Override
+	public void applyBonus(Item item, int trains) {}
+	@Override
+	public void applyBonus(Building building, int trains) {}
+}
diff --git a/src/engine/powers/effectmodifiers/SkillEffectModifier.java b/src/engine/powers/effectmodifiers/SkillEffectModifier.java
new file mode 100644
index 00000000..d49dd6f3
--- /dev/null
+++ b/src/engine/powers/effectmodifiers/SkillEffectModifier.java
@@ -0,0 +1,53 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.powers.effectmodifiers;
+
+import engine.jobs.AbstractEffectJob;
+import engine.objects.*;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+public class SkillEffectModifier extends AbstractEffectModifier {
+
+	public SkillEffectModifier(ResultSet rs) throws SQLException {
+		super(rs);
+	}
+
+	@Override
+	protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) {
+
+	}
+
+	@Override
+	public void applyBonus(AbstractCharacter ac, int trains) {
+		Float amount = 0f;
+		PlayerBonuses bonus = ac.getBonuses();
+		if (this.percentMod != 0f) { //Stat Percent Modifiers
+			if (this.useRampAdd)
+				amount = this.percentMod + (this.ramp * trains);
+			else
+				amount = this.percentMod * (1 + (this.ramp * trains));
+			amount = amount/100;
+			bonus.addFloat(this, amount);
+		} else { //Stat Modifiers
+			if (this.useRampAdd)
+				amount = this.minMod + (this.ramp * trains);
+			else
+				amount = this.minMod * (1 + (this.ramp * trains));
+			bonus.addFloat(this, amount);
+		}
+	}
+
+	@Override
+	public void applyBonus(Item item, int trains) {}
+	@Override
+	public void applyBonus(Building building, int trains) {}
+}
diff --git a/src/engine/powers/effectmodifiers/SlayEffectModifier.java b/src/engine/powers/effectmodifiers/SlayEffectModifier.java
new file mode 100644
index 00000000..c4608eee
--- /dev/null
+++ b/src/engine/powers/effectmodifiers/SlayEffectModifier.java
@@ -0,0 +1,53 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.powers.effectmodifiers;
+
+import engine.jobs.AbstractEffectJob;
+import engine.objects.*;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+public class SlayEffectModifier extends AbstractEffectModifier {
+
+	public SlayEffectModifier(ResultSet rs) throws SQLException {
+		super(rs);
+	}
+
+	@Override
+	protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) {
+
+	}
+
+	@Override
+	public void applyBonus(AbstractCharacter ac, int trains) {
+		Float amount = 0f;
+		PlayerBonuses bonus = ac.getBonuses();
+		if (this.percentMod != 0f) { //Stat Percent Modifiers
+			if (this.useRampAdd)
+				amount = this.percentMod + (this.ramp * trains);
+			else
+				amount = this.percentMod * (1 + (this.ramp * trains));
+			amount = amount/100;
+			bonus.addFloat(this, amount);
+		} else { //Stat Modifiers
+			if (this.useRampAdd)
+				amount = this.minMod + (this.ramp * trains);
+			else
+				amount = this.minMod * (1 + (this.ramp * trains));
+			bonus.addFloat(this, amount);
+		}
+	}
+
+	@Override
+	public void applyBonus(Item item, int trains) {}
+	@Override
+	public void applyBonus(Building building, int trains) {}
+}
diff --git a/src/engine/powers/effectmodifiers/SpeedEffectModifier.java b/src/engine/powers/effectmodifiers/SpeedEffectModifier.java
new file mode 100644
index 00000000..9df8a13b
--- /dev/null
+++ b/src/engine/powers/effectmodifiers/SpeedEffectModifier.java
@@ -0,0 +1,53 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.powers.effectmodifiers;
+
+import engine.jobs.AbstractEffectJob;
+import engine.objects.*;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+public class SpeedEffectModifier extends AbstractEffectModifier {
+
+	public SpeedEffectModifier(ResultSet rs) throws SQLException {
+		super(rs);
+	}
+
+	@Override
+	protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) {
+		//Logger.error(this.getSimpleClassName(), "Speed applied with " + trains + " trains");
+	}
+
+	@Override
+	public void applyBonus(AbstractCharacter ac, int trains) {
+		Float amount = 0f;
+		PlayerBonuses bonus = ac.getBonuses();
+		if (this.percentMod != 0f) { //Stat Percent Modifiers
+			if (this.useRampAdd)
+				amount = this.percentMod + (this.ramp * trains);
+			else
+				amount = this.percentMod * (1 + (this.ramp * trains));
+			amount = amount/100;
+			bonus.addFloat(this, amount);
+		} else { //Stat Modifiers
+			if (this.useRampAdd)
+				amount = this.minMod + (this.ramp * trains);
+			else
+				amount = this.minMod * (1 + (this.ramp * trains));
+			bonus.addFloat(this, amount);
+		}
+	}
+
+	@Override
+	public void applyBonus(Item item, int trains) {}
+	@Override
+	public void applyBonus(Building building, int trains) {}
+}
diff --git a/src/engine/powers/effectmodifiers/SpireBlockEffectModifier.java b/src/engine/powers/effectmodifiers/SpireBlockEffectModifier.java
new file mode 100644
index 00000000..26dfb368
--- /dev/null
+++ b/src/engine/powers/effectmodifiers/SpireBlockEffectModifier.java
@@ -0,0 +1,39 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.powers.effectmodifiers;
+
+import engine.jobs.AbstractEffectJob;
+import engine.objects.*;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+public class SpireBlockEffectModifier extends AbstractEffectModifier {
+
+	public SpireBlockEffectModifier(ResultSet rs) throws SQLException {
+		super(rs);
+	}
+
+	@Override
+	protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) {
+
+	}
+
+	@Override
+	public void applyBonus(AbstractCharacter ac, int trains) {
+		PlayerBonuses bonus = ac.getBonuses();
+		bonus.setBool(this.modType,this.sourceType, true);
+	}
+
+	@Override
+	public void applyBonus(Item item, int trains) {}
+	@Override
+	public void applyBonus(Building building, int trains) {}
+}
diff --git a/src/engine/powers/effectmodifiers/StaminaEffectModifier.java b/src/engine/powers/effectmodifiers/StaminaEffectModifier.java
new file mode 100644
index 00000000..a15d2653
--- /dev/null
+++ b/src/engine/powers/effectmodifiers/StaminaEffectModifier.java
@@ -0,0 +1,230 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+
+
+
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.powers.effectmodifiers;
+
+import engine.Enum;
+import engine.Enum.DamageType;
+import engine.Enum.ModType;
+import engine.Enum.SourceType;
+import engine.gameManager.ChatManager;
+import engine.jobs.AbstractEffectJob;
+import engine.jobs.DamageOverTimeJob;
+import engine.net.DispatchMessage;
+import engine.net.client.msg.ModifyHealthMsg;
+import engine.objects.*;
+import engine.powers.ActionsBase;
+import engine.powers.poweractions.AbstractPowerAction;
+import engine.powers.poweractions.DamageOverTimePowerAction;
+import engine.powers.poweractions.DirectDamagePowerAction;
+import org.pmw.tinylog.Logger;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.concurrent.ThreadLocalRandom;
+
+public class StaminaEffectModifier extends AbstractEffectModifier {
+
+	private DamageType damageType;
+
+	public StaminaEffectModifier(ResultSet rs) throws SQLException {
+		super(rs);
+		String damageTypeDB = rs.getString("type");
+		try {
+			this.damageType = DamageType.valueOf(damageTypeDB);
+		} catch (IllegalArgumentException e) {
+			Logger.error("DamageType could not be loaded from database. " + "UUID = " + this.UUID
+					+ " value received = '" + damageTypeDB + '\'', e);
+		}
+	}
+
+	@Override
+	protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) {
+		if (awo == null) {
+			Logger.error( "_applyEffectModifier(): NULL AWO passed in.");
+			return;
+		}
+
+		if (effect == null) {
+			Logger.error( "_applyEffectModifier(): NULL AbstractEffectJob passed in.");
+			return;
+		}
+
+		if (!AbstractWorldObject.IsAbstractCharacter(awo))
+			return;
+		AbstractCharacter awoac = (AbstractCharacter) awo;
+
+		float modAmount = 0f;
+
+		// Modify Stamina by percent
+		if (this.percentMod != 0f) {
+			float mod = 1f;
+			if (this.useRampAdd)
+				mod = (this.percentMod + (this.ramp * trains)) / 100;
+			else
+				mod = (this.percentMod * (1 + (this.ramp * trains))) / 100;
+			modAmount = mod * awoac.getStaminaMax();
+			if (awoac.isSit())
+				modAmount *= 2.5f;
+
+			//debug for spell damage and atr
+			if (source.getDebug(16) && source.getObjectType().equals(Enum.GameObjectType.PlayerCharacter)) {
+				PlayerCharacter pc = (PlayerCharacter) source;
+				String smsg = "Percent Damage: " + mod * 100 + '%';
+				ChatManager.chatSystemInfo(pc, smsg);
+			}
+		}
+
+		// Modify Stamina by min/max amount
+		else if (this.minMod != 0f || this.maxMod != 0f) {
+			float min = this.minMod;
+			float max = this.maxMod;
+			if (this.ramp > 0f) {
+				float mod = this.ramp * trains;
+				if (this.useRampAdd) {
+					min += mod;
+					max += mod;
+				} else {
+					min *= (1 + mod);
+					max *= (1 + mod);
+				}
+			}
+			if (source.getObjectType().equals(Enum.GameObjectType.PlayerCharacter)) {
+				PlayerCharacter pc = (PlayerCharacter) source;
+
+				float focus;
+				CharacterSkill skill = pc.getSkills().get(effect.getPower().getSkillName());
+				if (skill == null)
+					focus = CharacterSkill.getQuickMastery(pc, effect.getPower().getSkillName());
+				else
+					focus = skill.getModifiedAmount();
+				//TODO clean up old formulas once new one is verified
+				//				min *= (0.5 + 0.0075 * pc.getStatIntCurrent() + 0.011 * pc.getStatSpiCurrent() + 0.0196 * focus);
+				//				max *= (0.62 + 0.0192 * pc.getStatIntCurrent() + 0.00415 * pc.getStatSpiCurrent() + 0.015 * focus);
+				float intt = (pc.getStatIntCurrent() >= 1) ? (float)pc.getStatIntCurrent() : 1f;
+				float spi = (pc.getStatSpiCurrent() >= 1) ? (float)pc.getStatSpiCurrent() : 1f;
+				//				min *= (intt * 0.0045 + 0.055 * (float)Math.sqrt(intt - 0.5) + spi * 0.006 + 0.07 * (float)Math.sqrt(spi - 0.5) + 0.02 * (int)focus);
+				//				max *= (intt * 0.0117 + 0.13 * (float)Math.sqrt(intt - 0.5) + spi * 0.0024 + (float)Math.sqrt(spi - 0.5) * 0.021 + 0.015 * (int)focus);
+				min = HealthEffectModifier.getMinDamage(min, intt, spi, focus);
+				max = HealthEffectModifier.getMaxDamage(max, intt, spi, focus);
+
+				//debug for spell damage and atr
+				if (pc.getDebug(16)) {
+					String smsg = "Damage: " + (int)Math.abs(min) + " - " + (int)Math.abs(max);
+					ChatManager.chatSystemInfo(pc, smsg);
+				}
+			}
+			modAmount = calculateDamage(source, awoac, min, max, awo, trains);
+			PlayerBonuses bonus = source.getBonuses();
+
+			// Apply any power effect modifiers (such as stances)
+			if (bonus != null)
+				modAmount *= (1 + (bonus.getFloatPercentAll(ModType.PowerDamageModifier, SourceType.None)));
+		}
+		if (modAmount == 0f)
+			return;
+		if (AbstractWorldObject.IsAbstractCharacter(awo)) {
+			AbstractCharacter ac = (AbstractCharacter) awo;
+			int powerID = 0, effectID = 0;
+			String powerName = "";
+			if (effect.getPower() != null) {
+				powerID = effect.getPower().getToken();
+				powerName = effect.getPower().getName();
+			}
+			if (effect.getEffect() != null) {
+				effectID = effect.getEffect().getToken();
+			}
+
+			//see if target is immune to heals
+			if (modAmount > 0f) {
+				boolean skipImmune = false;
+				if (effect.getAction().getPowerAction() instanceof DirectDamagePowerAction) {
+					ArrayList<ActionsBase> actions = effect.getPower().getActions();
+					for (ActionsBase ab : actions) {
+						AbstractPowerAction apa = ab.getPowerAction();
+						if (apa instanceof DamageOverTimePowerAction)
+							skipImmune = true;
+					}
+				}
+				PlayerBonuses bonus = ac.getBonuses();
+				if (!skipImmune && bonus.getFloat(ModType.BlackMantle, SourceType.Heal) >= trains) {
+					ModifyHealthMsg mhm = new ModifyHealthMsg(source, ac, 0f, 0f, 0f, powerID, powerName, trains, effectID);
+					mhm.setUnknown03(5); //set target is immune
+					DispatchMessage.sendToAllInRange(ac, mhm);
+
+					return;
+				}
+			}
+
+			ac.modifyStamina(modAmount, source);
+
+			ModifyHealthMsg mhm = new ModifyHealthMsg(source, ac, 0f, 0f, modAmount, powerID, powerName, trains,
+					effectID);
+			if (effect instanceof DamageOverTimeJob)
+				mhm.setOmitFromChat(1);
+			DispatchMessage.sendToAllInRange(ac, mhm);
+		}
+	}
+
+	private float calculateDamage(AbstractCharacter source, AbstractCharacter target, float minDamage, float maxDamage, AbstractWorldObject awo, int trains) {
+
+		// get range between min and max
+		float range = maxDamage - minDamage;
+
+		// Damage is calculated twice to average a more central point
+		float damage = ThreadLocalRandom.current().nextFloat() * range;
+		damage = (damage + (ThreadLocalRandom.current().nextFloat() * range)) / 2;
+
+		// put it back between min and max
+		damage += minDamage;
+
+		Resists resists = null;
+		// get resists
+		if (AbstractWorldObject.IsAbstractCharacter(awo)) {
+			AbstractCharacter ac = (AbstractCharacter) awo;
+			resists = ac.getResists();
+		} else if (awo.getObjectType().equals(Enum.GameObjectType.Building))
+			resists = ((Building) awo).getResists();
+
+		// calculate resists in if any
+		if (resists != null)
+			damage = resists.getResistedDamage(source, target, damageType, damage * -1, trains) * -1;
+
+		if (AbstractWorldObject.IsAbstractCharacter(awo)) {
+			AbstractCharacter ac = (AbstractCharacter) awo;
+			if (ac.isSit())
+				damage *= 2.5f; // increase damage if sitting
+		}
+
+		return damage;
+	}
+
+	@Override
+	public void applyBonus(AbstractCharacter ac, int trains) {
+
+	}
+
+	@Override
+	public void applyBonus(Item item, int trains) {}
+	@Override
+	public void applyBonus(Building building, int trains) {}
+}
diff --git a/src/engine/powers/effectmodifiers/StaminaFullEffectModifier.java b/src/engine/powers/effectmodifiers/StaminaFullEffectModifier.java
new file mode 100644
index 00000000..1f2ac056
--- /dev/null
+++ b/src/engine/powers/effectmodifiers/StaminaFullEffectModifier.java
@@ -0,0 +1,53 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.powers.effectmodifiers;
+
+import engine.jobs.AbstractEffectJob;
+import engine.objects.*;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+public class StaminaFullEffectModifier extends AbstractEffectModifier {
+
+	public StaminaFullEffectModifier(ResultSet rs) throws SQLException {
+		super(rs);
+	}
+
+	@Override
+	protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) {
+
+	}
+
+	@Override
+	public void applyBonus(AbstractCharacter ac, int trains) {
+		Float amount = 0f;
+		PlayerBonuses bonus = ac.getBonuses();
+		if (this.percentMod != 0f) { //Stat Percent Modifiers
+			if (this.useRampAdd)
+				amount = this.percentMod + (this.ramp * trains);
+			else
+				amount = this.percentMod * (1 + (this.ramp * trains));
+			amount = amount/100;
+			bonus.addFloat(this, amount);
+		} else { //Stat Modifiers
+			if (this.useRampAdd)
+				amount = this.minMod + (this.ramp * trains);
+			else
+				amount = this.minMod * (1 + (this.ramp * trains));
+			bonus.addFloat(this, amount);
+		}
+	}
+
+	@Override
+	public void applyBonus(Item item, int trains) {}
+	@Override
+	public void applyBonus(Building building, int trains) {}
+}
diff --git a/src/engine/powers/effectmodifiers/StaminaRecoverRateEffectModifier.java b/src/engine/powers/effectmodifiers/StaminaRecoverRateEffectModifier.java
new file mode 100644
index 00000000..11a79d0f
--- /dev/null
+++ b/src/engine/powers/effectmodifiers/StaminaRecoverRateEffectModifier.java
@@ -0,0 +1,47 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.powers.effectmodifiers;
+
+import engine.jobs.AbstractEffectJob;
+import engine.objects.*;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+public class StaminaRecoverRateEffectModifier extends AbstractEffectModifier {
+
+	public StaminaRecoverRateEffectModifier(ResultSet rs) throws SQLException {
+		super(rs);
+	}
+
+	@Override
+	protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) {
+
+	}
+
+	@Override
+	public void applyBonus(AbstractCharacter ac, int trains) {
+		Float amount = 0f;
+		PlayerBonuses bonus = ac.getBonuses();
+		if (this.useRampAdd)
+			amount = this.percentMod + (this.ramp * trains);
+		else
+			amount = this.percentMod * (1 + (this.ramp * trains));
+		
+		//Erection is this right?
+		amount = amount/100;
+		bonus.multRegen(this.modType, amount); //positive regen modifiers
+	}
+
+	@Override
+	public void applyBonus(Item item, int trains) {}
+	@Override
+	public void applyBonus(Building building, int trains) {}
+}
diff --git a/src/engine/powers/effectmodifiers/StunnedEffectModifier.java b/src/engine/powers/effectmodifiers/StunnedEffectModifier.java
new file mode 100644
index 00000000..e1e0918a
--- /dev/null
+++ b/src/engine/powers/effectmodifiers/StunnedEffectModifier.java
@@ -0,0 +1,47 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.powers.effectmodifiers;
+
+import engine.Enum.GameObjectType;
+import engine.jobs.AbstractEffectJob;
+import engine.objects.*;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+public class StunnedEffectModifier extends AbstractEffectModifier {
+
+	public StunnedEffectModifier(ResultSet rs) throws SQLException {
+		super(rs);
+	}
+
+	@Override
+	protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) {
+
+	}
+
+	@Override
+	public void applyBonus(AbstractCharacter ac, int trains) {
+		if (ac.getObjectType() == GameObjectType.Mob) {
+			Mob mob = (Mob) ac;
+		}
+
+		PlayerBonuses bonus = ac.getBonuses();
+		bonus.setBool(this.modType,this.sourceType, true);
+		ac.cancelOnStun();	
+		ac.setIsCasting(false);
+		ac.stopMovement(ac.getLoc());
+	}
+
+	@Override
+	public void applyBonus(Item item, int trains) {}
+	@Override
+	public void applyBonus(Building building, int trains) {}
+}
diff --git a/src/engine/powers/effectmodifiers/ValueEffectModifier.java b/src/engine/powers/effectmodifiers/ValueEffectModifier.java
new file mode 100644
index 00000000..359d1a93
--- /dev/null
+++ b/src/engine/powers/effectmodifiers/ValueEffectModifier.java
@@ -0,0 +1,41 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.powers.effectmodifiers;
+
+import engine.jobs.AbstractEffectJob;
+import engine.objects.AbstractCharacter;
+import engine.objects.AbstractWorldObject;
+import engine.objects.Building;
+import engine.objects.Item;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+public class ValueEffectModifier extends AbstractEffectModifier {
+
+	public ValueEffectModifier(ResultSet rs) throws SQLException {
+		super(rs);
+	}
+
+	@Override
+	protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) {
+
+	}
+
+	@Override
+	public void applyBonus(AbstractCharacter ac, int trains) {
+
+	}
+
+	@Override
+	public void applyBonus(Item item, int trains) {}
+	@Override
+	public void applyBonus(Building building, int trains) {}
+}
diff --git a/src/engine/powers/effectmodifiers/WeaponProcEffectModifier.java b/src/engine/powers/effectmodifiers/WeaponProcEffectModifier.java
new file mode 100644
index 00000000..90258c58
--- /dev/null
+++ b/src/engine/powers/effectmodifiers/WeaponProcEffectModifier.java
@@ -0,0 +1,47 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.powers.effectmodifiers;
+
+import engine.gameManager.PowersManager;
+import engine.jobs.AbstractEffectJob;
+import engine.math.Vector3fImmutable;
+import engine.objects.AbstractCharacter;
+import engine.objects.AbstractWorldObject;
+import engine.objects.Building;
+import engine.objects.Item;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+public class WeaponProcEffectModifier extends AbstractEffectModifier {
+
+	public WeaponProcEffectModifier(ResultSet rs) throws SQLException {
+		super(rs);
+	}
+
+	@Override
+	protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) {
+
+	}
+
+	@Override
+	public void applyBonus(AbstractCharacter ac, int trains) {
+
+	}
+
+	@Override
+	public void applyBonus(Item item, int trains) {}
+	@Override
+	public void applyBonus(Building building, int trains) {}
+
+	public void applyProc(AbstractCharacter ac, AbstractWorldObject target) {
+		PowersManager.applyPower(ac, target, Vector3fImmutable.ZERO, this.string1, (int)this.percentMod, false);
+	}
+}
diff --git a/src/engine/powers/effectmodifiers/WeaponRangeEffectModifier.java b/src/engine/powers/effectmodifiers/WeaponRangeEffectModifier.java
new file mode 100644
index 00000000..cc08ed6b
--- /dev/null
+++ b/src/engine/powers/effectmodifiers/WeaponRangeEffectModifier.java
@@ -0,0 +1,54 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.powers.effectmodifiers;
+
+import engine.jobs.AbstractEffectJob;
+import engine.objects.*;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+public class WeaponRangeEffectModifier extends AbstractEffectModifier {
+
+	public WeaponRangeEffectModifier(ResultSet rs) throws SQLException {
+		super(rs);
+	}
+
+	@Override
+	protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) {
+
+	}
+
+	@Override
+	public void applyBonus(AbstractCharacter ac, int trains) {
+		Float amount = 0f;
+		PlayerBonuses bonus = ac.getBonuses();
+		if (this.percentMod != 0f) { //Stat Percent Modifiers
+			if (this.useRampAdd)
+				amount = this.percentMod + (this.ramp * trains);
+			else
+				amount = this.percentMod * (1 + (this.ramp * trains));
+			amount = amount/100;
+			bonus.addFloat(this, amount);
+		} else { //Stat Modifiers
+			if (this.useRampAdd)
+				amount = this.minMod + (this.ramp * trains);
+			else
+				amount = this.minMod * (1 + (this.ramp * trains));
+			bonus.addFloat(this, amount);
+		}
+
+	}
+
+	@Override
+	public void applyBonus(Item item, int trains) {}
+	@Override
+	public void applyBonus(Building building, int trains) {}
+}
diff --git a/src/engine/powers/effectmodifiers/WeaponSpeedEffectModifier.java b/src/engine/powers/effectmodifiers/WeaponSpeedEffectModifier.java
new file mode 100644
index 00000000..b47d8642
--- /dev/null
+++ b/src/engine/powers/effectmodifiers/WeaponSpeedEffectModifier.java
@@ -0,0 +1,48 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.powers.effectmodifiers;
+
+import engine.jobs.AbstractEffectJob;
+import engine.objects.AbstractCharacter;
+import engine.objects.AbstractWorldObject;
+import engine.objects.Building;
+import engine.objects.Item;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+public class WeaponSpeedEffectModifier extends AbstractEffectModifier {
+
+	public WeaponSpeedEffectModifier(ResultSet rs) throws SQLException {
+		super(rs);
+	}
+
+	@Override
+	protected void _applyEffectModifier(AbstractCharacter source, AbstractWorldObject awo, int trains, AbstractEffectJob effect) {
+
+	}
+
+	@Override
+	public void applyBonus(AbstractCharacter ac, int trains) {}
+
+	@Override
+	public void applyBonus(Item item, int trains) {
+		Float amount = 0f;
+		if (this.useRampAdd)
+			amount = this.percentMod + (this.ramp * trains);
+		else
+			amount = this.percentMod * (1 + (this.ramp * trains));
+		amount = amount/100;
+			item.addBonus(this, amount);
+	}
+
+	@Override
+	public void applyBonus(Building building, int trains) {}
+}
diff --git a/src/engine/powers/poweractions/AbstractPowerAction.java b/src/engine/powers/poweractions/AbstractPowerAction.java
new file mode 100644
index 00000000..f7b1914f
--- /dev/null
+++ b/src/engine/powers/poweractions/AbstractPowerAction.java
@@ -0,0 +1,274 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.powers.poweractions;
+
+import engine.gameManager.DbManager;
+import engine.gameManager.PowersManager;
+import engine.math.Vector3fImmutable;
+import engine.objects.AbstractCharacter;
+import engine.objects.AbstractWorldObject;
+import engine.objects.Item;
+import engine.objects.PreparedStatementShared;
+import engine.powers.ActionsBase;
+import engine.powers.EffectsBase;
+import engine.powers.PowersBase;
+import org.pmw.tinylog.Logger;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.HashMap;
+
+
+public abstract class AbstractPowerAction {
+
+	protected PowersBase parent;
+	protected int UUID;
+	protected String IDString;
+	protected String type;
+	protected boolean isAggressive;
+	protected long validItemFlags;
+
+	/**
+	 * No Table ID Constructor
+	 */
+	public AbstractPowerAction() {
+
+	}
+
+	/**
+	 * ResultSet Constructor
+	 */
+	public AbstractPowerAction(ResultSet rs) throws SQLException {
+
+		this.UUID = rs.getInt("ID");
+		this.IDString = rs.getString("IDString");
+		this.type = rs.getString("type");
+		int flags = rs.getInt("flags");
+		this.isAggressive = ((flags & 128) != 0) ? true : false;
+	}
+
+	public AbstractPowerAction( int uUID, String iDString, String type, boolean isAggressive,
+			long validItemFlags) {
+		super();
+		UUID = uUID;
+		IDString = iDString;
+		this.type = type;
+		this.isAggressive = false;
+	}
+
+	public static void getAllPowerActions(HashMap<String, AbstractPowerAction> powerActions, HashMap<Integer, AbstractPowerAction> powerActionsByID, HashMap<String, EffectsBase> effects) {
+		PreparedStatementShared ps = null;
+		try {
+			ps = new PreparedStatementShared("SELECT * FROM static_power_poweraction");
+			ResultSet rs = ps.executeQuery();
+			String IDString, type;
+			while (rs.next()) {
+				AbstractPowerAction apa;
+				type = rs.getString("type");
+				IDString = rs.getString("IDString");
+				int token = DbManager.hasher.SBStringHash(IDString);
+				//cache token, used for applying effects.
+				PowersManager.ActionTokenByIDString.put(IDString, token);
+				if (type.equals("ApplyEffect"))
+					apa = new ApplyEffectPowerAction(rs, effects);
+				else if (type.equals("ApplyEffects"))
+					apa = new ApplyEffectsPowerAction(rs, effects);
+				else if (type.equals("DeferredPower"))
+					apa = new DeferredPowerPowerAction(rs, effects);
+				else if (type.equals("DamageOverTime"))
+					apa = new DamageOverTimePowerAction(rs, effects);
+				else if (type.equals("Peek"))
+					apa = new PeekPowerAction(rs);
+				else if (type.equals("Charm"))
+					apa = new CharmPowerAction(rs);
+				else if (type.equals("Fear"))
+					apa = new FearPowerAction(rs);
+				else if (type.equals("Confusion"))
+					apa = new ConfusionPowerAction(rs);
+				else if (type.equals("RemoveEffect"))
+					apa = new RemoveEffectPowerAction(rs);
+				else if (type.equals("Track"))
+					apa = new TrackPowerAction(rs, effects);
+				else if (type.equals("DirectDamage"))
+					apa = new DirectDamagePowerAction(rs, effects);
+				else if (type.equals("Transform"))
+					apa = new TransformPowerAction(rs, effects);
+				else if (type.equals("CreateMob"))
+					apa = new CreateMobPowerAction(rs);
+				else if (type.equals("Invis"))
+					apa = new InvisPowerAction(rs, effects);
+				else if (type.equals("ClearNearbyAggro"))
+					apa = new ClearNearbyAggroPowerAction(rs);
+				else if (type.equals("MobRecall"))
+					apa = new MobRecallPowerAction(rs);
+				else if (type.equals("SetItemFlag"))
+					apa = new SetItemFlagPowerAction(rs);
+				else if (type.equals("SimpleDamage"))
+					apa = new SimpleDamagePowerAction(rs);
+				else if (type.equals("TransferStatOT"))
+					apa = new TransferStatOTPowerAction(rs, effects);
+				else if (type.equals("TransferStat"))
+					apa = new TransferStatPowerAction(rs, effects);
+				else if (type.equals("Teleport"))
+					apa = new TeleportPowerAction(rs);
+				else if (type.equals("TreeChoke"))
+					apa = new TreeChokePowerAction(rs);
+				else if (type.equals("Block"))
+					apa = new BlockPowerAction(rs);
+				else if (type.equals("Resurrect"))
+					apa = new ResurrectPowerAction(rs);
+				else if (type.equals("ClearAggro"))
+					apa = new ClearAggroPowerAction(rs);
+				else if (type.equals("ClaimMine"))
+					apa = new ClaimMinePowerAction(rs);
+				else if (type.equals("Recall"))
+					apa = new RecallPowerAction(rs);
+				else if (type.equals("SpireDisable"))
+					apa = new SpireDisablePowerAction(rs);
+				else if (type.equals("Steal"))
+					apa = new StealPowerAction(rs);
+				else if (type.equals("Summon"))
+					apa = new SummonPowerAction(rs);
+				else if (type.equals("RunegateTeleport"))
+					apa = new RunegateTeleportPowerAction(rs);
+				else if (type.equals("RunegateTeleport"))
+					apa = new RunegateTeleportPowerAction(rs);
+				else if (type.equals("OpenGate"))
+					apa = new OpenGatePowerAction(rs);
+				else {
+					Logger.error("valid type not found for poweraction of ID" + rs.getInt("ID"));
+					continue;
+				}
+				powerActions.put(IDString, apa);
+				powerActionsByID.put(apa.UUID, apa);
+				apa.validItemFlags = 0;
+			}
+			rs.close();
+		} catch (Exception e) {
+			Logger.error( e.toString());
+		} finally {
+			ps.release();
+		}
+		
+		
+		
+		//Add OpenGatePowerAction
+		AbstractPowerAction openGateAction = new OpenGatePowerAction(5000, "OPENGATE", "OpenGate", false, 0);
+		powerActions.put("OPENGATE", openGateAction);
+		powerActionsByID.put(openGateAction.UUID, openGateAction);
+	}
+
+	public void startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc, int numTrains, ActionsBase ab, PowersBase pb) {
+		this._startAction(source, awo, targetLoc, numTrains, ab, pb);
+	}
+
+	public void startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc, int numTrains, ActionsBase ab, PowersBase pb, int duration) {
+		this._startAction(source, awo, targetLoc, numTrains, ab, pb,duration);
+	}
+
+	protected abstract void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc, int numTrains, ActionsBase ab, PowersBase pb);
+
+	protected abstract void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc, int numTrains, ActionsBase ab, PowersBase pb, int duration);
+
+	public void handleChant(AbstractCharacter source, AbstractWorldObject target, Vector3fImmutable targetLoc, int numTrains, ActionsBase ab, PowersBase pb) {
+		this._handleChant(source, target, targetLoc, numTrains, ab, pb);
+	}
+
+	protected abstract void _handleChant(AbstractCharacter source, AbstractWorldObject target, Vector3fImmutable targetLoc, int numTrains, ActionsBase ab, PowersBase pb);
+
+	public int getUUID() {
+		return this.UUID;
+	}
+
+	public String getIDString() {
+		return this.IDString;
+	}
+
+	//	public String getMessageType() {
+	//		return this.type;
+	//	}
+
+	public boolean isAggressive() {
+		return this.isAggressive;
+	}
+
+	public PowersBase getParent() {
+		return this.parent;
+	}
+
+	public void setParent(PowersBase value) {
+		this.parent = value;
+	}
+
+	public void applyEffectForItem(Item item, int trains) {
+		if (this instanceof ApplyEffectPowerAction)
+			((ApplyEffectPowerAction)this)._applyEffectForItem(item, trains);
+		if (this instanceof ApplyEffectsPowerAction)
+			((ApplyEffectsPowerAction)this)._applyEffectForItem(item, trains);
+	}
+	public void applyBakedInStatsForItem(Item item, int trains) {
+		if (this instanceof ApplyEffectPowerAction)
+			((ApplyEffectPowerAction)this)._applyBakedInStatsForItem(item, trains);
+		if (this instanceof ApplyEffectsPowerAction)
+			((ApplyEffectsPowerAction)this)._applyBakedInStatsForItem(item, trains);
+	}
+
+	public EffectsBase getEffectsBase() {
+		if (this instanceof ApplyEffectPowerAction)
+			return ((ApplyEffectPowerAction)this).getEffect();
+		else if (this instanceof ApplyEffectsPowerAction)
+			return ((ApplyEffectsPowerAction)this).getEffect();
+		return null;
+	}
+
+	public EffectsBase getEffectsBase2() {
+		if (this instanceof ApplyEffectsPowerAction)
+			return ((ApplyEffectsPowerAction)this).getEffect2();
+		return null;
+	}
+
+	public static void loadValidItemFlags(HashMap<String, AbstractPowerAction> powerActions) {
+		PreparedStatementShared ps = null;
+		try {
+			ps = new PreparedStatementShared("SELECT * FROM `static_power_effect_allowed_item`");
+			ResultSet rs = ps.executeQuery();
+			String IDS;
+			long flags;
+			while (rs.next()) {
+				AbstractPowerAction apa;
+				flags = rs.getLong("flags");
+				IDS = rs.getString("IDString");
+				if (powerActions.containsKey(IDS)) {
+					apa = powerActions.get(IDS);
+					apa.validItemFlags = flags;
+				} else {
+					Logger.error("Unable to find PowerAction " + IDS);
+					continue;
+				}
+			}
+			rs.close();
+		} catch (Exception e) {
+			Logger.error(e.toString());
+		} finally {
+			ps.release();
+		}
+
+	}
+
+	//These functions verify a powerAction is valid for an item type
+	public long getValidItemFlags() {
+		return this.validItemFlags;
+	}
+
+	public String getType() {
+		return type;
+	}
+
+}
diff --git a/src/engine/powers/poweractions/ApplyEffectPowerAction.java b/src/engine/powers/poweractions/ApplyEffectPowerAction.java
new file mode 100644
index 00000000..95c5ae2f
--- /dev/null
+++ b/src/engine/powers/poweractions/ApplyEffectPowerAction.java
@@ -0,0 +1,265 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+
+
+
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.powers.poweractions;
+
+import engine.Enum.GameObjectType;
+import engine.Enum.ModType;
+import engine.Enum.SourceType;
+import engine.ai.MobileFSM;
+import engine.gameManager.ChatManager;
+import engine.jobs.ChantJob;
+import engine.jobs.DeferredPowerJob;
+import engine.jobs.FinishEffectTimeJob;
+import engine.math.Vector3fImmutable;
+import engine.net.DispatchMessage;
+import engine.net.client.msg.chat.ChatSystemMsg;
+import engine.objects.*;
+import engine.powers.ActionsBase;
+import engine.powers.EffectsBase;
+import engine.powers.PowersBase;
+import org.pmw.tinylog.Logger;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.HashMap;
+
+public class ApplyEffectPowerAction extends AbstractPowerAction {
+
+	private String effectID;
+	private EffectsBase effect;
+	private String effectParentID;
+	private EffectsBase effectParent;
+
+	public ApplyEffectPowerAction(ResultSet rs, HashMap<String, EffectsBase> effects) throws SQLException {
+		super(rs);
+		this.effectParentID = rs.getString("IDString");
+		this.effectParent = effects.get(this.effectParentID);
+		this.effectID = rs.getString("effectID");
+		this.effect = effects.get(this.effectID);
+	}
+
+	public ApplyEffectPowerAction(ResultSet rs, EffectsBase effect) throws SQLException {
+		super(rs);
+
+		this.effectID = rs.getString("effectID");
+		this.effect = effect;
+	}
+
+	public String getEffectID() {
+		return this.effectID;
+	}
+
+	public EffectsBase getEffect() {
+		return this.effect;
+	}
+
+	@Override
+	protected void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc, int trains, ActionsBase ab, PowersBase pb) {
+		if (this.effect == null || pb == null || ab == null) {
+			//TODO log error here
+			return;
+		}
+
+		//add schedule job to end it if needed and add effect to pc
+		int duration = 0;
+		//		if (pb.isChant())
+		//			duration = (int)pb.getChantDuration() * 1000;
+		//		else
+		duration = ab.getDuration(trains);
+		String stackType = ab.getStackType();
+		if (stackType.equals("WeaponMove")) {
+			DeferredPowerJob eff = new DeferredPowerJob(source, awo, stackType, trains, ab, pb, this.effect, this);
+			if (stackType.equals("IgnoreStack"))
+				awo.addEffect(Integer.toString(ab.getUUID()), 10000, eff, this.effect, trains);
+			else
+				awo.addEffect(stackType, 10000, eff, this.effect, trains);
+			if (awo.getObjectType().equals(GameObjectType.PlayerCharacter))
+				((PlayerCharacter)awo).setWeaponPower(eff);
+			this.effect.startEffect(source, awo, trains, eff);
+		} else {
+			FinishEffectTimeJob eff = new FinishEffectTimeJob(source, awo, stackType, trains, ab, pb, effect);
+			
+			if (blockInvul(pb, source, awo)) {
+				this.effect.endEffect(source, awo, trains, pb, eff);
+				return;
+			}
+			
+//			 Effect lastEff = awo.getEffects().get(eff.getStackType());
+//				
+//				if (lastEff != null && lastEff.getPowerToken() == eff.getPowerToken())
+//					lastEff.cancelJob(true);
+
+			if (duration > 0) {
+				if (stackType.equals("IgnoreStack"))
+					awo.addEffect(Integer.toString(ab.getUUID()), duration, eff, effect, trains);
+				else
+					awo.addEffect(stackType, duration, eff, effect, trains);
+			} else
+				awo.applyAllBonuses();
+			//			//TODO if chant, start cycle
+			//			if (pb.isChant() && source.equals(awo)) {
+			//				ChantJob cj = new ChantJob(source, awo, stackType, trains, ab, pb, effect, eff);
+			//				source.setLastChant((int)(pb.getChantDuration()-2) * 1000, cj);
+			//				eff.setChant(true);
+			//			}
+
+			if (this.effectID.equals("TAUNT")){
+
+				if (awo != null && awo.getObjectType() == GameObjectType.Mob){
+					MobileFSM.setAggro((Mob)awo,source.getObjectUUID());
+					ChatSystemMsg msg = ChatManager.CombatInfo(source, awo);
+					DispatchMessage.sendToAllInRange(source, msg);
+				}
+			}
+			this.effect.startEffect(source, awo, trains, eff);
+		}
+	}
+
+	protected void _applyEffectForItem(Item item, int trains) {
+		if (item == null || this.effect == null)
+			return;
+		item.addEffectNoTimer(Integer.toString(this.effect.getUUID()), this.effect, trains,false);
+		item.addEffectNoTimer(Integer.toString(this.effectParent.getUUID()), this.effectParent, trains,false);
+	}
+	protected void _applyBakedInStatsForItem(Item item, int trains) {
+		if (item == null)
+			return;
+		if (this.effect == null){
+			Logger.error( "Unknown Token: EffectBase ID " + this.effectID + '.');
+			return;
+		}
+
+		if (this.effectParent == null){
+			Logger.error("Unknown Token: EffectBase ID " + this.effectParentID + '.');
+			return;
+		}
+		Effect eff = item.addEffectNoTimer(Integer.toString(this.effect.getUUID()), this.effect, trains,false);
+		Effect eff3 = item.addEffectNoTimer(Integer.toString(this.effectParent.getUUID()), this.effectParent, trains,false);
+		if (eff != null  && eff3 != null){
+			eff3.setBakedInStat(true);
+			item.getEffectNames().add(this.effect.getIDString());
+			item.getEffectNames().add(this.effectParent.getIDString());
+		}
+	}
+
+	@Override
+	protected void _handleChant(AbstractCharacter source, AbstractWorldObject target, Vector3fImmutable targetLoc, int trains, ActionsBase ab, PowersBase pb) {
+		if (source != null) {
+			PlayerBonuses bonuses = source.getBonuses();
+			
+			if (bonuses == null)
+				return;
+			
+			boolean noSilence = bonuses.getBool(ModType.Silenced, SourceType.None);
+			
+			if (noSilence)
+				return;
+
+		}
+		String stackType = ab.stackType;
+		stackType = stackType.equals("IgnoreStack") ? Integer.toString(ab.getUUID()) : stackType;
+
+		FinishEffectTimeJob eff = new FinishEffectTimeJob(source, target, stackType, trains, ab, pb, effect);
+		ChantJob cj = new ChantJob(source, target, stackType, trains, ab, pb, effect, eff);
+		//handle invul
+		if(pb.getUUID() != 334)
+			source.setLastChant((int)(pb.getChantDuration()) * 1000, cj);
+		else
+			source.setLastChant((int)(pb.getChantDuration()) * 1000, cj);
+		eff.setChant(true);
+	}
+
+	private static boolean blockInvul(PowersBase pb, AbstractCharacter source, AbstractWorldObject awo) {
+		if (awo == null || pb == null || source == null)
+			return false;
+
+		if (source.getObjectUUID() == awo.getObjectUUID())
+			return false;
+
+		if (!AbstractWorldObject.IsAbstractCharacter(awo))
+			return false;
+
+		AbstractCharacter ac = (AbstractCharacter) awo;
+
+
+		return false;
+	}
+
+	@Override
+	protected void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc,
+			int trains, ActionsBase ab, PowersBase pb, int duration) {
+		if (this.effect == null || pb == null || ab == null) {
+			//TODO log error here
+			return;
+		}
+
+		//add schedule job to end it if needed and add effect to pc
+		//		if (pb.isChant())
+		//			duration = (int)pb.getChantDuration() * 1000;
+		//		else
+
+		String stackType = ab.getStackType();
+		if (stackType.equals("WeaponMove")) {
+			DeferredPowerJob eff = new DeferredPowerJob(source, awo, stackType, trains, ab, pb, this.effect, this);
+			if (stackType.equals("IgnoreStack"))
+				awo.addEffect(Integer.toString(ab.getUUID()), 10000, eff, this.effect, trains);
+			else
+				awo.addEffect(stackType, 10000, eff, this.effect, trains);
+			if (awo.getObjectType().equals(GameObjectType.PlayerCharacter))
+				((PlayerCharacter)awo).setWeaponPower(eff);
+			this.effect.startEffect(source, awo, trains, eff);
+		} else {
+			FinishEffectTimeJob eff = new FinishEffectTimeJob(source, awo, stackType, trains, ab, pb, effect);
+
+			if (blockInvul(pb, source, awo)) {
+				this.effect.endEffect(source, awo, trains, pb, eff);
+				return;
+			}
+
+			if (duration > 0) {
+				if (stackType.equals("IgnoreStack"))
+					awo.addEffect(Integer.toString(ab.getUUID()), duration, eff, effect, trains);
+				else
+					awo.addEffect(stackType, duration, eff, effect, trains);
+			} else
+				awo.applyAllBonuses();
+			//			//TODO if chant, start cycle
+			//			if (pb.isChant() && source.equals(awo)) {
+			//				ChantJob cj = new ChantJob(source, awo, stackType, trains, ab, pb, effect, eff);
+			//				source.setLastChant((int)(pb.getChantDuration()-2) * 1000, cj);
+			//				eff.setChant(true);
+			//			}
+
+			if (this.effectID.equals("TAUNT")){
+
+				if (awo != null && awo.getObjectType() == GameObjectType.Mob){
+					MobileFSM.setAggro((Mob)awo,source.getObjectUUID());
+					ChatSystemMsg msg = ChatManager.CombatInfo(source, awo);
+					DispatchMessage.sendToAllInRange(source, msg);
+				}
+			}
+			this.effect.startEffect(source, awo, trains, eff);
+		}
+
+	}
+
+}
diff --git a/src/engine/powers/poweractions/ApplyEffectsPowerAction.java b/src/engine/powers/poweractions/ApplyEffectsPowerAction.java
new file mode 100644
index 00000000..901b34c6
--- /dev/null
+++ b/src/engine/powers/poweractions/ApplyEffectsPowerAction.java
@@ -0,0 +1,124 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.powers.poweractions;
+
+import engine.math.Vector3fImmutable;
+import engine.objects.AbstractCharacter;
+import engine.objects.AbstractWorldObject;
+import engine.objects.Effect;
+import engine.objects.Item;
+import engine.powers.ActionsBase;
+import engine.powers.EffectsBase;
+import engine.powers.PowersBase;
+import org.pmw.tinylog.Logger;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.HashMap;
+
+
+public class ApplyEffectsPowerAction extends AbstractPowerAction {
+	private String IDString;
+	private String effectID;
+	private String effectID2;
+	private EffectsBase effect;
+	private EffectsBase effect2;
+	private EffectsBase effectParent;
+
+	public ApplyEffectsPowerAction(ResultSet rs, HashMap<String, EffectsBase> effects) throws SQLException {
+		super(rs);
+		this.IDString = rs.getString("IDString");
+		this.effectID = rs.getString("effectID");
+		this.effectID2 = rs.getString("effectID2");
+		this.effect = effects.get(this.effectID);
+		this.effect2 = effects.get(this.effectID2);
+		this.effectParent = effects.get(this.IDString);
+	
+	}
+
+	public String getEffectID() {
+		return this.effectID;
+	}
+
+	public String getEffectID2() {
+		return this.effectID2;
+	}
+
+	public EffectsBase getEffect() {
+		return this.effect;
+	}
+
+	public EffectsBase getEffect2() {
+		return this.effect2;
+	}
+
+	@Override
+	protected void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc, int trains, ActionsBase ab, PowersBase pb) {
+
+	}
+
+	protected void _applyEffectForItem(Item item, int trains) {
+		if (item == null || this.effect == null)
+			return;
+		item.addEffectNoTimer(Integer.toString(this.effect.getUUID()), this.effect, trains,false);
+		if (this.effect2 != null)
+		item.addEffectNoTimer(Integer.toString(this.effect2.getUUID()), this.effect2, trains,false);
+		item.addEffectNoTimer(Integer.toString(this.effectParent.getUUID()), this.effectParent, trains,false);
+	}
+	protected void _applyBakedInStatsForItem(Item item, int trains) {
+
+		if (item == null)
+			return;
+
+		if (this.effect == null){
+			Logger.error( "Unknown Token: EffectBase ID " + this.effectID + '.');
+			return;
+		}
+
+		if (this.effect2 == null){
+			Logger.error( "Unknown Token: EffectBase ID " + this.effectID2 + '.');
+			return;
+		}
+
+		if (this.effectParent == null){
+			Logger.error( "Unknown Token: EffectBase ID " + this.IDString + '.');
+			return;
+		}
+
+		Effect eff = item.addEffectNoTimer(Integer.toString(this.effect.getUUID()), this.effect, trains,false);
+		Effect eff2 = item.addEffectNoTimer(Integer.toString(this.effect2.getUUID()), this.effect2, trains,false);
+		Effect eff3 = item.addEffectNoTimer(Integer.toString(this.effectParent.getUUID()), this.effectParent, trains,false);
+
+		if (eff != null && eff2 != null && eff3 != null){
+			eff3.setBakedInStat(true);
+			item.getEffectNames().add(this.effect.getIDString());
+			item.getEffectNames().add(this.effect2.getIDString());
+			item.getEffectNames().add(this.effectParent.getIDString());
+		}
+	}
+	@Override
+	protected void _handleChant(AbstractCharacter source, AbstractWorldObject target, Vector3fImmutable targetLoc, int trains, ActionsBase ab, PowersBase pb) {
+	}
+
+	public String getIDString() {
+		return IDString;
+	}
+
+	public void setIDString(String iDString) {
+		IDString = iDString;
+	}
+
+	@Override
+	protected void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc,
+			int numTrains, ActionsBase ab, PowersBase pb, int duration) {
+		// TODO Auto-generated method stub
+
+	}
+}
diff --git a/src/engine/powers/poweractions/BlockPowerAction.java b/src/engine/powers/poweractions/BlockPowerAction.java
new file mode 100644
index 00000000..db395299
--- /dev/null
+++ b/src/engine/powers/poweractions/BlockPowerAction.java
@@ -0,0 +1,43 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.powers.poweractions;
+
+import engine.math.Vector3fImmutable;
+import engine.objects.AbstractCharacter;
+import engine.objects.AbstractWorldObject;
+import engine.powers.ActionsBase;
+import engine.powers.PowersBase;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+
+public class BlockPowerAction extends AbstractPowerAction {
+
+	public BlockPowerAction(ResultSet rs) throws SQLException {
+		super(rs);
+	}
+
+	@Override
+	protected void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc, int trains, ActionsBase ab, PowersBase pb) {
+
+	}
+
+	@Override
+	protected void _handleChant(AbstractCharacter source, AbstractWorldObject target, Vector3fImmutable targetLoc, int trains, ActionsBase ab, PowersBase pb) {
+	}
+
+	@Override
+	protected void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc,
+			int numTrains, ActionsBase ab, PowersBase pb, int duration) {
+		// TODO Auto-generated method stub
+
+	}
+}
diff --git a/src/engine/powers/poweractions/CharmPowerAction.java b/src/engine/powers/poweractions/CharmPowerAction.java
new file mode 100644
index 00000000..4d3b1cde
--- /dev/null
+++ b/src/engine/powers/poweractions/CharmPowerAction.java
@@ -0,0 +1,73 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.powers.poweractions;
+
+import engine.Enum;
+import engine.math.Vector3fImmutable;
+import engine.net.client.ClientConnection;
+import engine.objects.AbstractCharacter;
+import engine.objects.AbstractWorldObject;
+import engine.objects.Mob;
+import engine.objects.PlayerCharacter;
+import engine.powers.ActionsBase;
+import engine.powers.PowersBase;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+
+public class CharmPowerAction extends AbstractPowerAction {
+
+	private int levelCap;
+	private int levelCapRamp;
+
+	public CharmPowerAction(ResultSet rs) throws SQLException {
+		super(rs);
+		this.levelCap = rs.getInt("levelCap");
+		this.levelCapRamp = rs.getInt("levelCapRamp");
+	}
+
+	@Override
+	protected void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc, int trains, ActionsBase ab, PowersBase pb) {
+
+		if (source == null || awo == null || !(awo.getObjectType().equals(Enum.GameObjectType.Mob)) || !(source.getObjectType().equals(Enum.GameObjectType.PlayerCharacter)))
+			return;
+
+		PlayerCharacter owner = (PlayerCharacter) source;
+		ClientConnection origin = owner.getClientConnection();
+
+		if (origin == null)
+			return;
+
+		//verify is mob, not pet or guard
+		Mob mob = (Mob) awo;
+		if (!mob.isMob())
+			return;
+
+		//make sure mob isn't too high level
+		int cap = this.levelCap + (this.levelCapRamp * trains);
+		if (mob.getLevel() > cap && pb.getToken() != 1577464266)
+			return;
+
+		//turn mob into pet.
+		owner.commandSiegeMinion(mob);
+	}
+
+	@Override
+	protected void _handleChant(AbstractCharacter source, AbstractWorldObject target, Vector3fImmutable targetLoc, int trains, ActionsBase ab, PowersBase pb) {
+	}
+
+	@Override
+	protected void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc,
+			int numTrains, ActionsBase ab, PowersBase pb, int duration) {
+		// TODO Auto-generated method stub
+
+	}
+}
diff --git a/src/engine/powers/poweractions/ClaimMinePowerAction.java b/src/engine/powers/poweractions/ClaimMinePowerAction.java
new file mode 100644
index 00000000..76b3cd4c
--- /dev/null
+++ b/src/engine/powers/poweractions/ClaimMinePowerAction.java
@@ -0,0 +1,61 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.powers.poweractions;
+
+import engine.Enum;
+import engine.math.Vector3fImmutable;
+import engine.objects.*;
+import engine.powers.ActionsBase;
+import engine.powers.PowersBase;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+
+public class ClaimMinePowerAction extends AbstractPowerAction {
+
+	public ClaimMinePowerAction(ResultSet rs) throws SQLException {
+		super(rs);
+	}
+
+	@Override
+	protected void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc, int trains, ActionsBase ab, PowersBase pb) {
+		if (source == null || awo == null)
+			return;
+
+		if (!(source.getObjectType().equals(Enum.GameObjectType.PlayerCharacter)))
+			return;
+
+		if (!(awo.getObjectType().equals(Enum.GameObjectType.Building)))
+			return;
+
+		Building b = (Building)awo;
+
+		if (b.getRank() > 0)
+			return;
+
+		Mine m = Mine.getMineFromTower(b.getObjectUUID());
+		if (m == null)
+			return;
+
+		m.claimMine((PlayerCharacter) source);
+	}
+
+	@Override
+	protected void _handleChant(AbstractCharacter source, AbstractWorldObject target, Vector3fImmutable targetLoc, int trains, ActionsBase ab, PowersBase pb) {
+	}
+
+	@Override
+	protected void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc,
+			int numTrains, ActionsBase ab, PowersBase pb, int duration) {
+		// TODO Auto-generated method stub
+
+	}
+}
diff --git a/src/engine/powers/poweractions/ClearAggroPowerAction.java b/src/engine/powers/poweractions/ClearAggroPowerAction.java
new file mode 100644
index 00000000..4ac761e6
--- /dev/null
+++ b/src/engine/powers/poweractions/ClearAggroPowerAction.java
@@ -0,0 +1,52 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.powers.poweractions;
+
+import engine.Enum.GameObjectType;
+import engine.ai.MobileFSM.STATE;
+import engine.math.Vector3fImmutable;
+import engine.objects.AbstractCharacter;
+import engine.objects.AbstractWorldObject;
+import engine.objects.Mob;
+import engine.powers.ActionsBase;
+import engine.powers.PowersBase;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+
+public class ClearAggroPowerAction extends AbstractPowerAction {
+
+	public ClearAggroPowerAction(ResultSet rs) throws SQLException {
+		super(rs);
+	}
+
+	@Override
+	protected void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc, int trains, ActionsBase ab, PowersBase pb) {
+		if (awo != null && awo.getObjectType() == GameObjectType.Mob){
+			((Mob)awo).setNoAggro(true);
+			((Mob)awo).setState(STATE.Patrol);
+		}
+
+
+
+	}
+
+	@Override
+	protected void _handleChant(AbstractCharacter source, AbstractWorldObject target, Vector3fImmutable targetLoc, int trains, ActionsBase ab, PowersBase pb) {
+	}
+
+	@Override
+	protected void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc,
+			int numTrains, ActionsBase ab, PowersBase pb, int duration) {
+		// TODO Auto-generated method stub
+
+	}
+}
diff --git a/src/engine/powers/poweractions/ClearNearbyAggroPowerAction.java b/src/engine/powers/poweractions/ClearNearbyAggroPowerAction.java
new file mode 100644
index 00000000..a32dd972
--- /dev/null
+++ b/src/engine/powers/poweractions/ClearNearbyAggroPowerAction.java
@@ -0,0 +1,49 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.powers.poweractions;
+
+import engine.Enum.GameObjectType;
+import engine.ai.MobileFSM.STATE;
+import engine.math.Vector3fImmutable;
+import engine.objects.AbstractCharacter;
+import engine.objects.AbstractWorldObject;
+import engine.objects.Mob;
+import engine.powers.ActionsBase;
+import engine.powers.PowersBase;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+
+public class ClearNearbyAggroPowerAction extends AbstractPowerAction {
+
+	public ClearNearbyAggroPowerAction(ResultSet rs) throws SQLException {
+		super(rs);
+	}
+
+	@Override
+	protected void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc, int trains, ActionsBase ab, PowersBase pb) {
+		if (source.getObjectType() == GameObjectType.Mob){
+			((Mob)source).setState(STATE.Patrol);
+		}
+
+	}
+
+	@Override
+	protected void _handleChant(AbstractCharacter source, AbstractWorldObject target, Vector3fImmutable targetLoc, int trains, ActionsBase ab, PowersBase pb) {
+	}
+
+	@Override
+	protected void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc,
+			int numTrains, ActionsBase ab, PowersBase pb, int duration) {
+		// TODO Auto-generated method stub
+
+	}
+}
diff --git a/src/engine/powers/poweractions/ConfusionPowerAction.java b/src/engine/powers/poweractions/ConfusionPowerAction.java
new file mode 100644
index 00000000..1c2e1843
--- /dev/null
+++ b/src/engine/powers/poweractions/ConfusionPowerAction.java
@@ -0,0 +1,43 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.powers.poweractions;
+
+import engine.math.Vector3fImmutable;
+import engine.objects.AbstractCharacter;
+import engine.objects.AbstractWorldObject;
+import engine.powers.ActionsBase;
+import engine.powers.PowersBase;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+
+public class ConfusionPowerAction extends AbstractPowerAction {
+
+	public ConfusionPowerAction(ResultSet rs) throws SQLException {
+		super(rs);
+	}
+
+	@Override
+	protected void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc, int trains, ActionsBase ab, PowersBase pb) {
+
+	}
+
+	@Override
+	protected void _handleChant(AbstractCharacter source, AbstractWorldObject target, Vector3fImmutable targetLoc, int trains, ActionsBase ab, PowersBase pb) {
+	}
+
+	@Override
+	protected void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc,
+			int numTrains, ActionsBase ab, PowersBase pb, int duration) {
+		// TODO Auto-generated method stub
+
+	}
+}
diff --git a/src/engine/powers/poweractions/CreateMobPowerAction.java b/src/engine/powers/poweractions/CreateMobPowerAction.java
new file mode 100644
index 00000000..de78b27d
--- /dev/null
+++ b/src/engine/powers/poweractions/CreateMobPowerAction.java
@@ -0,0 +1,175 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.powers.poweractions;
+
+import engine.Enum;
+import engine.InterestManagement.WorldGrid;
+import engine.ai.MobileFSM.STATE;
+import engine.gameManager.DbManager;
+import engine.gameManager.ZoneManager;
+import engine.math.Vector3fImmutable;
+import engine.net.Dispatch;
+import engine.net.DispatchMessage;
+import engine.net.client.ClientConnection;
+import engine.net.client.msg.PetMsg;
+import engine.objects.*;
+import engine.powers.ActionsBase;
+import engine.powers.PowersBase;
+import org.pmw.tinylog.Logger;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+
+public class CreateMobPowerAction extends AbstractPowerAction {
+
+	private int mobID;
+	private int mobLevel;
+
+	public CreateMobPowerAction(ResultSet rs) throws SQLException {
+		super(rs);
+
+		this.mobID = rs.getInt("mobID");
+		this.mobLevel = rs.getInt("mobLevel");
+	}
+
+	public int getMobID() {
+		return this.mobID;
+	}
+
+	public int getMobLevel() {
+		return this.mobLevel;
+	}
+
+	@Override
+	protected void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc, int trains, ActionsBase ab, PowersBase pb) {
+
+		if (source == null || !(source.getObjectType().equals(Enum.GameObjectType.PlayerCharacter)))
+			return;
+
+		PlayerCharacter owner = (PlayerCharacter) source;
+		Mob currentPet = owner.getPet();
+		Zone seaFloor = ZoneManager.getSeaFloor();
+		Guild guild = Guild.getErrantGuild();
+		ClientConnection origin = owner.getClientConnection();
+
+		if (seaFloor == null || guild == null || origin == null)
+			return;
+
+		MobBase mobbase = MobBase.getMobBase(mobID);
+
+		if (mobbase == null) {
+			Logger.error("Attempt to summon pet with null mobbase: " + mobID);
+			return;
+		}
+
+		if (mobbase.isNecroPet() && owner.inSafeZone())
+			return;
+
+		//create Pet
+		Mob pet = Mob.createPet(mobID, guild, seaFloor, owner, (short)mobLevel);
+
+
+		if(pet.getMobBaseID() == 12021 || pet.getMobBaseID() == 12022) { //is a necro pet
+			if(currentPet!= null && !currentPet.isNecroPet() && !currentPet.isSiege()) {
+				DbManager.removeFromCache(currentPet);
+				WorldGrid.RemoveWorldObject(currentPet);
+				currentPet.setState(STATE.Disabled);
+				currentPet.setCombatTarget(null);
+
+				if (currentPet.getParentZone() != null)
+					currentPet.getParentZone().zoneMobSet.remove(currentPet);
+
+				currentPet.getPlayerAgroMap().clear();
+
+				try {
+					currentPet.clearEffects();
+				}catch(Exception e){
+					Logger.error(e.getMessage());
+				}
+
+				//currentPet.disableIntelligence();
+			}else if (currentPet != null && currentPet.isSiege()){
+				currentPet.setMob();
+				currentPet.setOwner(null);
+				currentPet.setCombatTarget(null);
+
+				if (currentPet.isAlive())
+					WorldGrid.updateObject(currentPet);
+			}
+			//remove 10th pet
+			
+		
+			owner.spawnNecroPet(pet);
+
+		}
+		else { //is not a necro pet
+			if(currentPet != null) {
+				if(!currentPet.isNecroPet() && !currentPet.isSiege()) {
+					DbManager.removeFromCache(currentPet);
+					currentPet.setCombatTarget(null);
+					currentPet.setState(STATE.Disabled);
+
+					currentPet.setOwner(null);
+					WorldGrid.RemoveWorldObject(currentPet);
+
+					currentPet.getParentZone().zoneMobSet.remove(currentPet);
+					currentPet.getPlayerAgroMap().clear();
+					currentPet.clearEffects();
+					//currentPet.disableIntelligence();
+				}
+				else {
+					if (currentPet.isSiege()){
+						currentPet.setMob();
+						currentPet.setOwner(null);
+						currentPet.setCombatTarget(null);
+
+						if (currentPet.isAlive())
+							WorldGrid.updateObject(currentPet);
+					}
+					
+				}
+				PlayerCharacter.auditNecroPets(owner);
+				PlayerCharacter.resetNecroPets(owner);
+			}
+		}
+		/*		if(owner.getPet() != null) {
+			if(owner.getPet().getMobBaseID() != 12021 && owner.getPet().getMobBaseID() != 12022) {
+				//if not a necro pet, remove pet
+				WorldGrid.removeWorldObject(owner.getPet());
+				owner.getPet().disableIntelligence();
+				Mob.removePet(owner.getPet().getUUID());
+				owner.setPet(null);
+		}
+		else {
+			//if it is a necro pet, add it to the line and set as mob
+			owner.getPet().setMob();
+		}
+	}*/
+
+		//	if (mobID == 12021 || mobID == 12022) //Necro Pets
+		//	pet.setPet(owner, true);
+		owner.setPet(pet);
+		PetMsg pm = new PetMsg(5, pet);
+		Dispatch dispatch = Dispatch.borrow(owner, pm);
+		DispatchMessage.dispatchMsgDispatch(dispatch, engine.Enum.DispatchChannel.SECONDARY);
+	}
+
+	@Override
+	protected void _handleChant(AbstractCharacter source, AbstractWorldObject target, Vector3fImmutable targetLoc, int trains, ActionsBase ab, PowersBase pb) {
+	}
+
+	@Override
+	protected void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc,
+			int numTrains, ActionsBase ab, PowersBase pb, int duration) {
+		// TODO Auto-generated method stub
+
+	}
+}
diff --git a/src/engine/powers/poweractions/DamageOverTimePowerAction.java b/src/engine/powers/poweractions/DamageOverTimePowerAction.java
new file mode 100644
index 00000000..033f9b2d
--- /dev/null
+++ b/src/engine/powers/poweractions/DamageOverTimePowerAction.java
@@ -0,0 +1,85 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.powers.poweractions;
+
+import engine.jobs.DamageOverTimeJob;
+import engine.math.Vector3fImmutable;
+import engine.objects.AbstractCharacter;
+import engine.objects.AbstractWorldObject;
+import engine.powers.ActionsBase;
+import engine.powers.EffectsBase;
+import engine.powers.PowersBase;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.HashMap;
+
+
+public class DamageOverTimePowerAction extends AbstractPowerAction {
+
+	private String effectID;
+	private int numIterations;
+	private EffectsBase effect;
+
+	public DamageOverTimePowerAction(ResultSet rs, HashMap<String, EffectsBase> effects) throws SQLException {
+		super(rs);
+
+		this.effectID = rs.getString("effectID");
+		this.numIterations = rs.getInt("numIterations");
+		this.effect = effects.get(this.effectID);
+	}
+
+	public String getEffectID() {
+		return this.effectID;
+	}
+
+	public int getNumIterations() {
+		return this.numIterations;
+	}
+
+	public EffectsBase getEffect() {
+		return this.effect;
+	}
+
+	@Override
+	protected void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc, int trains, ActionsBase ab, PowersBase pb) {
+		if (this.effect == null || pb == null || ab == null) {
+			//TODO log error here
+			return;
+		}
+		//add schedule job to end it if needed and add effect to pc
+		int duration = ab.getDuration(trains);
+		String stackType = ab.getStackType();
+		DamageOverTimeJob eff = new DamageOverTimeJob(source, awo, stackType, trains, ab, pb, this.effect, this);
+		int tick = eff.getTickLength();
+		if (duration > 0) {
+			if (stackType.equals("IgnoreStack"))
+				awo.addEffect(Integer.toString(ab.getUUID()), eff.getTickLength(), eff, this.effect, trains);
+			else
+				awo.addEffect(stackType, tick, eff, this.effect, trains);
+		}
+
+		//start effect icon for client. Skip applying dot until first iteration.
+		eff.setSkipApplyEffect(true);
+		this.effect.startEffect(source, awo, trains, eff);
+		eff.setSkipApplyEffect(false);
+	}
+
+	@Override
+	protected void _handleChant(AbstractCharacter source, AbstractWorldObject target, Vector3fImmutable targetLoc, int trains, ActionsBase ab, PowersBase pb) {
+	}
+
+	@Override
+	protected void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc,
+			int numTrains, ActionsBase ab, PowersBase pb, int duration) {
+		// TODO Auto-generated method stub
+
+	}
+}
diff --git a/src/engine/powers/poweractions/DeferredPowerPowerAction.java b/src/engine/powers/poweractions/DeferredPowerPowerAction.java
new file mode 100644
index 00000000..d6cdabb9
--- /dev/null
+++ b/src/engine/powers/poweractions/DeferredPowerPowerAction.java
@@ -0,0 +1,95 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.powers.poweractions;
+
+import engine.jobs.DeferredPowerJob;
+import engine.math.Vector3fImmutable;
+import engine.objects.AbstractCharacter;
+import engine.objects.AbstractWorldObject;
+import engine.objects.Mob;
+import engine.objects.PlayerCharacter;
+import engine.powers.ActionsBase;
+import engine.powers.EffectsBase;
+import engine.powers.PowersBase;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.HashMap;
+
+
+public class DeferredPowerPowerAction extends AbstractPowerAction {
+
+	private String effectID;
+	private String deferedPowerID;
+	private EffectsBase effect;
+	//	private EffectsBase deferedPower;
+
+	public DeferredPowerPowerAction(ResultSet rs, HashMap<String, EffectsBase> effects) throws SQLException {
+		super(rs);
+
+		this.effectID = rs.getString("effectID");
+		this.deferedPowerID = rs.getString("deferredPowerID");
+		this.effect = effects.get(this.effectID);
+	}
+
+	public String getEffectID() {
+		return this.effectID;
+	}
+
+	public String getDeferredPowerID() {
+		return this.deferedPowerID;
+	}
+
+	public EffectsBase getEffect() {
+		return this.effect;
+	}
+
+	@Override
+	protected void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc, int trains, ActionsBase ab, PowersBase pb) {
+		if (this.effect == null || pb == null || ab == null) {
+			//TODO log error here
+			return;
+		}
+
+		//add schedule job to end it if needed and add effect to pc
+
+		String stackType = ab.getStackType();
+		DeferredPowerJob eff = new DeferredPowerJob(source, awo, stackType, trains, ab, pb, this.effect, this);
+		if (stackType.equals("IgnoreStack"))
+			awo.addEffect(Integer.toString(ab.getUUID()), 10000, eff, this.effect, trains);
+		else
+			awo.addEffect(stackType, 10000, eff, this.effect, trains);
+
+		switch (awo.getObjectType()){
+		case PlayerCharacter:
+			((PlayerCharacter)awo).setWeaponPower(eff);
+			break;
+		case Mob:
+			((Mob)awo).setWeaponPower(eff);
+			break;
+		default:
+			break;
+		}
+
+
+		this.effect.startEffect(source, awo, trains, eff);
+	}
+
+	@Override
+	protected void _handleChant(AbstractCharacter source, AbstractWorldObject target, Vector3fImmutable targetLoc, int trains, ActionsBase ab, PowersBase pb) {
+	}
+
+	@Override
+	protected void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc,
+			int numTrains, ActionsBase ab, PowersBase pb, int duration) {
+		// TODO Auto-generated method stub
+
+	}
+}
diff --git a/src/engine/powers/poweractions/DirectDamagePowerAction.java b/src/engine/powers/poweractions/DirectDamagePowerAction.java
new file mode 100644
index 00000000..955ea6a9
--- /dev/null
+++ b/src/engine/powers/poweractions/DirectDamagePowerAction.java
@@ -0,0 +1,93 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.powers.poweractions;
+
+import engine.jobs.FinishEffectTimeJob;
+import engine.jobs.PersistentAoeJob;
+import engine.math.Vector3fImmutable;
+import engine.objects.AbstractCharacter;
+import engine.objects.AbstractWorldObject;
+import engine.powers.ActionsBase;
+import engine.powers.EffectsBase;
+import engine.powers.PowersBase;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.HashMap;
+
+
+public class DirectDamagePowerAction extends AbstractPowerAction {
+
+	private String effectID;
+	private EffectsBase effect;
+
+	public DirectDamagePowerAction(ResultSet rs, HashMap<String, EffectsBase> effects) throws SQLException {
+		super(rs);
+
+		this.effectID = rs.getString("effectID");
+		this.effect = effects.get(this.effectID);
+	}
+
+	public String getEffectID() {
+		return this.effectID;
+	}
+
+	public EffectsBase getEffect() {
+		return this.effect;
+	}
+
+	@Override
+	protected void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc, int trains, ActionsBase ab, PowersBase pb) {
+		if (this.effect == null || pb == null || ab == null) {
+			//TODO log error here
+			return;
+		}
+
+		//add schedule job to end it if needed and add effect to pc
+		int duration = ab.getDuration(trains);
+		String stackType = ab.getStackType();
+		FinishEffectTimeJob eff = new FinishEffectTimeJob(source, awo, stackType, trains, ab, pb, this.effect);
+		eff.setSkipSendEffect(true);	
+		if (duration > 0) {
+			if (stackType.equals("IgnoreStack"))
+				awo.addEffect(Integer.toString(ab.getUUID()), duration, eff, this.effect, trains);
+			else
+				awo.addEffect(stackType, duration, eff, this.effect, trains);
+		}
+
+		//		//if chant, start cycle
+		//		if (pb.isChant() && targetLoc.x != 0f && targetLoc.z != 0f) {
+		//			PersistentAoeJob paoe = new PersistentAoeJob(source, stackType, trains, ab, pb, effect, eff, targetLoc);
+		//			source.addPersistantAoe(stackType, (int)(pb.getChantDuration() * 1000), paoe, effect, trains);
+		//			eff.setChant(true);
+		//		}
+
+		this.effect.startEffect(source, awo, trains, eff);
+	}
+
+	@Override
+	protected void _handleChant(AbstractCharacter source, AbstractWorldObject target, Vector3fImmutable targetLoc, int trains, ActionsBase ab, PowersBase pb) {
+		String stackType = ab.getStackType();
+		stackType = stackType.equals("IgnoreStack") ? Integer.toString(ab.getUUID()) : stackType;
+		FinishEffectTimeJob eff = new FinishEffectTimeJob(source, target, stackType, trains, ab, pb, this.effect);
+		if (targetLoc.x != 0f && targetLoc.z != 0f) {
+			PersistentAoeJob paoe = new PersistentAoeJob(source,target, stackType, trains, ab, pb, effect, eff, targetLoc);
+			source.addPersistantAoe(stackType, (int)(pb.getChantDuration() * 1000), paoe, effect, trains);
+			eff.setChant(true);
+		}
+	}
+
+	@Override
+	protected void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc,
+			int numTrains, ActionsBase ab, PowersBase pb, int duration) {
+		// TODO Auto-generated method stub
+
+	}
+}
diff --git a/src/engine/powers/poweractions/FearPowerAction.java b/src/engine/powers/poweractions/FearPowerAction.java
new file mode 100644
index 00000000..23c41123
--- /dev/null
+++ b/src/engine/powers/poweractions/FearPowerAction.java
@@ -0,0 +1,76 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.powers.poweractions;
+
+import engine.Enum;
+import engine.job.JobScheduler;
+import engine.jobs.EndFearJob;
+import engine.math.Vector3fImmutable;
+import engine.net.client.ClientConnection;
+import engine.objects.AbstractCharacter;
+import engine.objects.AbstractWorldObject;
+import engine.objects.Mob;
+import engine.objects.PlayerCharacter;
+import engine.powers.ActionsBase;
+import engine.powers.PowersBase;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+public class FearPowerAction extends AbstractPowerAction {
+
+	private int levelCap;
+	private int levelCapRamp;
+
+	public FearPowerAction(ResultSet rs) throws SQLException {
+		super(rs);
+		this.levelCap = rs.getInt("levelCap");
+		this.levelCapRamp = rs.getInt("levelCapRamp");
+	}
+
+	@Override
+	protected void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc, int trains, ActionsBase ab, PowersBase pb) {
+		if (source == null || awo == null || !(awo.getObjectType().equals(Enum.GameObjectType.Mob)) || !(source.getObjectType().equals(Enum.GameObjectType.PlayerCharacter)))
+			return;
+
+		PlayerCharacter owner = (PlayerCharacter) source;
+		ClientConnection origin = owner.getClientConnection();
+		if (origin == null)
+			return;
+
+		//verify is mob, not pet or guard
+		Mob mob = (Mob) awo;
+		if (!mob.isMob())
+			return;
+
+		//make sure mob isn't too high level
+		int cap = this.levelCap + (this.levelCapRamp * trains);
+		if (mob.getLevel() > cap || mob.getLevel() > 79)
+			return;
+
+		//Apply fear to mob
+		int duration = 10 + ((int)(trains * 0.5));
+		String stackType = ab.getStackType();
+		EndFearJob efj = new EndFearJob(source, awo, stackType, trains, ab, pb, null);
+		((Mob)awo).setFearedObject(source);
+		JobScheduler.getInstance().scheduleJob(efj, duration * 1000);
+	}
+
+	@Override
+	protected void _handleChant(AbstractCharacter source, AbstractWorldObject target, Vector3fImmutable targetLoc, int trains, ActionsBase ab, PowersBase pb) {
+	}
+
+	@Override
+	protected void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc,
+			int numTrains, ActionsBase ab, PowersBase pb, int duration) {
+		// TODO Auto-generated method stub
+
+	}
+}
diff --git a/src/engine/powers/poweractions/InvisPowerAction.java b/src/engine/powers/poweractions/InvisPowerAction.java
new file mode 100644
index 00000000..cade11fc
--- /dev/null
+++ b/src/engine/powers/poweractions/InvisPowerAction.java
@@ -0,0 +1,78 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.powers.poweractions;
+
+import engine.jobs.FinishEffectTimeJob;
+import engine.math.Vector3fImmutable;
+import engine.objects.AbstractCharacter;
+import engine.objects.AbstractWorldObject;
+import engine.powers.ActionsBase;
+import engine.powers.EffectsBase;
+import engine.powers.PowersBase;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.HashMap;
+
+
+public class InvisPowerAction extends AbstractPowerAction {
+
+	private String effectID;
+	private EffectsBase effect;
+
+	public InvisPowerAction(ResultSet rs, HashMap<String, EffectsBase> effects) throws SQLException {
+		super(rs);
+
+		this.effectID = rs.getString("effectID");
+		this.effect = effects.get(this.effectID);
+	}
+
+	public String getEffectID() {
+		return this.effectID;
+	}
+
+	public EffectsBase getEffect() {
+		return this.effect;
+	}
+
+	@Override
+	protected void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc, int trains, ActionsBase ab, PowersBase pb) {
+		if (this.effect == null || pb == null || ab == null) {
+			//TODO log error here
+			return;
+		}
+
+		//		if (this.effect.ignoreMod())
+		//			trains = 50; //set above see invis for safe mode and csr-invis
+
+		//add schedule job to end it if needed and add effect to pc
+		int duration = ab.getDuration(trains);
+		String stackType = ab.getStackType();
+		FinishEffectTimeJob eff = new FinishEffectTimeJob(source, awo, stackType, trains, ab, pb, this.effect);
+		if (duration > 0) {
+			if (stackType.equals("IgnoreStack"))
+				awo.addEffect(Integer.toString(ab.getUUID()), duration, eff, this.effect, trains);
+			else
+				awo.addEffect(stackType, duration, eff, this.effect, trains);
+		}
+		this.effect.startEffect(source, awo, trains, eff);
+	}
+
+	@Override
+	protected void _handleChant(AbstractCharacter source, AbstractWorldObject target, Vector3fImmutable targetLoc, int trains, ActionsBase ab, PowersBase pb) {
+	}
+
+	@Override
+	protected void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc,
+			int numTrains, ActionsBase ab, PowersBase pb, int duration) {
+		// TODO Auto-generated method stub
+
+	}
+}
diff --git a/src/engine/powers/poweractions/MobRecallPowerAction.java b/src/engine/powers/poweractions/MobRecallPowerAction.java
new file mode 100644
index 00000000..af5a1250
--- /dev/null
+++ b/src/engine/powers/poweractions/MobRecallPowerAction.java
@@ -0,0 +1,60 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.powers.poweractions;
+
+import engine.Enum.GameObjectType;
+import engine.ai.MobileFSM;
+import engine.gameManager.MovementManager;
+import engine.math.Vector3fImmutable;
+import engine.objects.AbstractCharacter;
+import engine.objects.AbstractWorldObject;
+import engine.objects.Mob;
+import engine.powers.ActionsBase;
+import engine.powers.PowersBase;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+
+public class MobRecallPowerAction extends AbstractPowerAction {
+
+	public MobRecallPowerAction(ResultSet rs) throws SQLException {
+		super(rs);
+	}
+
+	@Override
+	protected void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc, int trains, ActionsBase ab, PowersBase pb) {
+		if (!AbstractWorldObject.IsAbstractCharacter(awo) || source == null)
+			return;
+		AbstractCharacter awoac = (AbstractCharacter)awo;
+
+		if (awo.getObjectType() != GameObjectType.Mob)
+			return;
+
+		
+		MovementManager.translocate(awoac,awoac.getBindLoc(), null);
+		if (awoac.getObjectType() == GameObjectType.Mob){
+			MobileFSM.setAwake((Mob)awoac,true);
+		}
+
+
+	}
+
+	@Override
+	protected void _handleChant(AbstractCharacter source, AbstractWorldObject target, Vector3fImmutable targetLoc, int trains, ActionsBase ab, PowersBase pb) {
+	}
+
+	@Override
+	protected void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc,
+			int numTrains, ActionsBase ab, PowersBase pb, int duration) {
+		// TODO Auto-generated method stub
+
+	}
+}
diff --git a/src/engine/powers/poweractions/OpenGatePowerAction.java b/src/engine/powers/poweractions/OpenGatePowerAction.java
new file mode 100644
index 00000000..84032829
--- /dev/null
+++ b/src/engine/powers/poweractions/OpenGatePowerAction.java
@@ -0,0 +1,130 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.powers.poweractions;
+
+import engine.Enum;
+import engine.Enum.BuildingGroup;
+import engine.Enum.GameObjectType;
+import engine.Enum.RunegateType;
+import engine.math.Vector3fImmutable;
+import engine.objects.AbstractCharacter;
+import engine.objects.AbstractWorldObject;
+import engine.objects.Building;
+import engine.objects.Runegate;
+import engine.powers.ActionsBase;
+import engine.powers.PowersBase;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+public class OpenGatePowerAction extends AbstractPowerAction {
+
+	/**
+	 * ResultSet Constructor
+	 */
+	public OpenGatePowerAction(ResultSet rs) throws SQLException {
+		super(rs);
+	}
+
+	public OpenGatePowerAction(int uUID, String iDString, String type, boolean isAggressive, long validItemFlags) {
+		super(uUID, iDString, type, isAggressive, validItemFlags);
+		// TODO Auto-generated constructor stub
+	}
+
+	@Override
+	protected void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc, int trains, ActionsBase ab, PowersBase pb) {
+
+		
+		if (awo.getObjectType().equals(GameObjectType.Building) == false)
+			return;
+		
+		Building targetBuilding = (Building) awo;
+		Runegate runeGate;
+		RunegateType runegateType;
+		RunegateType portalType;
+		int token;
+
+		// Sanity check.
+
+		if (source == null || awo == null || !(awo.getObjectType().equals(Enum.GameObjectType.Building)) || pb == null)
+			return;
+
+		// Make sure building has a blueprint
+
+		if (targetBuilding.getBlueprintUUID() == 0)
+			return;
+
+		// Make sure building is actually a runegate.
+
+		if (targetBuilding.getBlueprint().getBuildingGroup() != BuildingGroup.RUNEGATE)
+			return;
+
+		// Which portal was opened?
+
+		token = pb.getToken();
+		portalType = RunegateType.AIR;
+
+		switch (token) {
+		case 428937084: //Death Gate
+			portalType = RunegateType.OBLIV;
+			break;
+
+		case 429756284: //Chaos Gate
+			portalType = RunegateType.CHAOS;
+			break;
+
+		case 429723516: //Khar Gate
+			portalType = RunegateType.MERCHANT;
+			break;
+
+		case 429559676: //Spirit Gate
+			portalType = RunegateType.SPIRIT;
+			break;
+
+		case 429592444: //Water Gate
+			portalType = RunegateType.WATER;
+			break;
+
+		case 429428604: //Fire Gate
+			portalType = RunegateType.FIRE;
+			break;
+
+		case 429526908: //Air Gate
+			portalType = RunegateType.AIR;
+			break;
+
+		case 429625212: //Earth Gate
+			portalType = RunegateType.EARTH;
+			break;
+
+		default:
+		}
+
+		// Which runegate was clicked on?
+
+		runegateType = RunegateType.getGateTypeFromUUID(targetBuilding.getObjectUUID());
+		runeGate = Runegate.getRunegates()[runegateType.ordinal()];
+
+		runeGate.activatePortal(portalType);
+
+
+	}
+
+	@Override
+	protected void _handleChant(AbstractCharacter source, AbstractWorldObject target, Vector3fImmutable targetLoc, int trains, ActionsBase ab, PowersBase pb) {
+	}
+
+	@Override
+	protected void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc,
+			int numTrains, ActionsBase ab, PowersBase pb, int duration) {
+		// TODO Auto-generated method stub
+
+	}
+}
diff --git a/src/engine/powers/poweractions/PeekPowerAction.java b/src/engine/powers/poweractions/PeekPowerAction.java
new file mode 100644
index 00000000..289bc523
--- /dev/null
+++ b/src/engine/powers/poweractions/PeekPowerAction.java
@@ -0,0 +1,137 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.powers.poweractions;
+
+import engine.Enum;
+import engine.gameManager.ChatManager;
+import engine.math.Vector3fImmutable;
+import engine.net.Dispatch;
+import engine.net.DispatchMessage;
+import engine.net.client.msg.LootWindowResponseMsg;
+import engine.objects.*;
+import engine.powers.ActionsBase;
+import engine.powers.PowersBase;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.concurrent.ThreadLocalRandom;
+
+
+public class PeekPowerAction extends AbstractPowerAction {
+
+	public PeekPowerAction(ResultSet rs) throws SQLException {
+		super(rs);
+	}
+
+	@Override
+	protected void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc, int trains, ActionsBase ab, PowersBase pb) {
+
+		if (source == null || awo == null || !(source.getObjectType().equals(Enum.GameObjectType.PlayerCharacter)))
+			return;
+
+		PlayerCharacter pc = null;
+		if (source.getObjectType().equals(Enum.GameObjectType.PlayerCharacter))
+			pc = (PlayerCharacter) source;
+
+		AbstractCharacter target = null;
+		if (AbstractWorldObject.IsAbstractCharacter(awo))
+			target = (AbstractCharacter) awo;
+
+		//test probability of successful peek
+		boolean peekSuccess = peekSuccess(source, awo);
+		if (peekSuccess) {
+			ChatManager.chatPeekSteal(pc, target, null, true, peekDetect(source, awo), -1);
+		} else {
+			ChatManager.chatPeekSteal(pc, target, null, false, false, -1);
+			return;
+		}
+
+		LootWindowResponseMsg lwrm = null;
+
+		if (awo.getObjectType().equals(Enum.GameObjectType.PlayerCharacter)) {
+
+			PlayerCharacter tar = (PlayerCharacter)awo;
+
+			if (!tar.isAlive())
+				return;
+
+			lwrm = new LootWindowResponseMsg(tar.getObjectType().ordinal(), tar.getObjectUUID(), tar.getInventory(true));
+		} else if (awo.getObjectType().equals(Enum.GameObjectType.Mob)) {
+
+			Mob tar = (Mob) awo;
+
+			if (!tar.isAlive())
+				return;
+
+			lwrm = new LootWindowResponseMsg(tar.getObjectType().ordinal(), tar.getObjectUUID(), tar.getInventory(true));
+		}
+		if (lwrm == null)
+			return;
+
+		Dispatch dispatch = Dispatch.borrow(pc, lwrm);
+		DispatchMessage.dispatchMsgDispatch(dispatch, engine.Enum.DispatchChannel.SECONDARY);
+	}
+
+	@Override
+	protected void _handleChant(AbstractCharacter source, AbstractWorldObject target, Vector3fImmutable targetLoc, int trains, ActionsBase ab, PowersBase pb) {
+	}
+
+	protected static boolean peekSuccess(AbstractCharacter pc, AbstractWorldObject awo) {
+		if (pc == null || awo == null || !AbstractWorldObject.IsAbstractCharacter(awo) || pc.getPowers() == null)
+			return false;
+
+		int levelDif = pc.getLevel() - ((AbstractCharacter)awo).getLevel();
+
+		if (!pc.getPowers().containsKey(429494332))
+			return false;
+
+		CharacterPower cp = pc.getPowers().get(429494332);
+		int trains = cp.getTotalTrains();
+
+		float chance = 30 + (trains * 1.5f) + levelDif;
+		chance = (chance < 5f) ? 5f : chance;
+		chance = (chance > 95f) ? 95f : chance;
+
+		float roll = ThreadLocalRandom.current().nextFloat() * 100f;
+
+		return roll < chance;
+
+	}
+
+
+	protected static boolean peekDetect(AbstractCharacter pc, AbstractWorldObject awo) {
+		if (pc == null || awo == null || !AbstractWorldObject.IsAbstractCharacter(awo) || pc.getPowers() == null)
+			return false;
+
+		int levelDif = pc.getLevel() - ((AbstractCharacter)awo).getLevel();
+
+		if (!pc.getPowers().containsKey(429494332))
+			return false;
+
+		CharacterPower cp = pc.getPowers().get(429494332);
+		int trains = cp.getTotalTrains();
+
+		// check if peek is detected
+		float chance = 30 + (40-trains)*1.5f - levelDif;
+		chance = (chance < 5f) ? 5f : chance;
+		chance = (chance > 95f) ? 95f : chance;
+
+		float roll = ThreadLocalRandom.current().nextFloat() * 100f;
+		return roll < chance;
+
+	}
+
+	@Override
+	protected void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc,
+			int numTrains, ActionsBase ab, PowersBase pb, int duration) {
+		// TODO Auto-generated method stub
+
+	}
+}
diff --git a/src/engine/powers/poweractions/RecallPowerAction.java b/src/engine/powers/poweractions/RecallPowerAction.java
new file mode 100644
index 00000000..3462a363
--- /dev/null
+++ b/src/engine/powers/poweractions/RecallPowerAction.java
@@ -0,0 +1,88 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.powers.poweractions;
+
+import engine.Enum.GameObjectType;
+import engine.InterestManagement.WorldGrid;
+import engine.ai.MobileFSM;
+import engine.gameManager.MovementManager;
+import engine.math.Vector3fImmutable;
+import engine.net.Dispatch;
+import engine.net.DispatchMessage;
+import engine.net.client.ClientConnection;
+import engine.net.client.msg.PromptRecallMsg;
+import engine.objects.AbstractCharacter;
+import engine.objects.AbstractWorldObject;
+import engine.objects.Mob;
+import engine.objects.PlayerCharacter;
+import engine.powers.ActionsBase;
+import engine.powers.PowersBase;
+import engine.server.MBServerStatics;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+
+public class RecallPowerAction extends AbstractPowerAction {
+
+	public RecallPowerAction(ResultSet rs) throws SQLException {
+		super(rs);
+	}
+
+	@Override
+	protected void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc, int trains, ActionsBase ab, PowersBase pb) {
+		if (!AbstractWorldObject.IsAbstractCharacter(awo) || source == null)
+			return;
+		AbstractCharacter awoac = (AbstractCharacter)awo;
+
+		if (awo.getObjectType().equals(GameObjectType.PlayerCharacter)) {
+
+			PlayerCharacter pc = (PlayerCharacter) awo;
+
+			if (pc.hasBoon())
+				return;
+
+			ClientConnection cc = pc.getClientConnection();
+
+			if(source.getObjectUUID() != pc.getObjectUUID()) {
+				pc.setTimeStampNow("PromptRecall");
+				pc.setTimeStamp("LastRecallType",1); //recall to bind
+				PromptRecallMsg promptRecallMsgmsg = new PromptRecallMsg();
+				Dispatch dispatch = Dispatch.borrow(pc, promptRecallMsgmsg);
+				DispatchMessage.dispatchMsgDispatch(dispatch, engine.Enum.DispatchChannel.SECONDARY);
+
+			} else {
+				MovementManager.translocate(awoac, awoac.getBindLoc(), null);
+			}
+		} else {
+			Vector3fImmutable bindloc = awoac.getBindLoc();
+			if (bindloc.x == 0.0f || bindloc.y == 0.0f)
+				awoac.setBindLoc(MBServerStatics.startX, MBServerStatics.startY, MBServerStatics.startZ);
+			awoac.teleport(awoac.getBindLoc());
+			if (awoac.getObjectType() == GameObjectType.Mob){
+				MobileFSM.setAwake((Mob)awoac,true);
+				if (awoac.isAlive())
+					WorldGrid.updateObject(awoac);
+			}
+
+		}
+	}
+
+	@Override
+	protected void _handleChant(AbstractCharacter source, AbstractWorldObject target, Vector3fImmutable targetLoc, int trains, ActionsBase ab, PowersBase pb) {
+	}
+
+	@Override
+	protected void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc,
+			int numTrains, ActionsBase ab, PowersBase pb, int duration) {
+		// TODO Auto-generated method stub
+
+	}
+}
diff --git a/src/engine/powers/poweractions/RemoveEffectPowerAction.java b/src/engine/powers/poweractions/RemoveEffectPowerAction.java
new file mode 100644
index 00000000..d992ce81
--- /dev/null
+++ b/src/engine/powers/poweractions/RemoveEffectPowerAction.java
@@ -0,0 +1,62 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.powers.poweractions;
+
+import engine.Enum.EffectSourceType;
+import engine.math.Vector3fImmutable;
+import engine.objects.AbstractCharacter;
+import engine.objects.AbstractWorldObject;
+import engine.powers.ActionsBase;
+import engine.powers.PowersBase;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+
+public class RemoveEffectPowerAction extends AbstractPowerAction {
+
+	
+	public EffectSourceType sourceType;
+	private boolean removeAll;
+
+	public RemoveEffectPowerAction(ResultSet rs) throws SQLException {
+		super(rs);
+		 String effectTypeToRemove = rs.getString("effectSourceToRemove").replace("-", "").trim();
+		sourceType = EffectSourceType.GetEffectSourceType(effectTypeToRemove);
+		int flags = rs.getInt("flags");
+		this.removeAll = ((flags & 2) != 0) ? true : false;
+	}
+
+
+	@Override
+	protected void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc, int trains, ActionsBase ab, PowersBase pb) {
+		if (awo != null) {
+			
+			
+			if (this.removeAll)
+				awo.removeEffectBySource(this.sourceType, trains, true);
+			else
+				if (this.getIDString().equals("SNC-003A"))
+					trains = 40;
+			awo.removeEffectBySource(this.sourceType, trains, false);
+		}
+	}
+
+	@Override
+	protected void _handleChant(AbstractCharacter source, AbstractWorldObject target, Vector3fImmutable targetLoc, int trains, ActionsBase ab, PowersBase pb) {
+	}
+
+	@Override
+	protected void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc,
+			int numTrains, ActionsBase ab, PowersBase pb, int duration) {
+		// TODO Auto-generated method stub
+
+	}
+}
diff --git a/src/engine/powers/poweractions/ResurrectPowerAction.java b/src/engine/powers/poweractions/ResurrectPowerAction.java
new file mode 100644
index 00000000..85c04eab
--- /dev/null
+++ b/src/engine/powers/poweractions/ResurrectPowerAction.java
@@ -0,0 +1,43 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.powers.poweractions;
+
+import engine.math.Vector3fImmutable;
+import engine.objects.AbstractCharacter;
+import engine.objects.AbstractWorldObject;
+import engine.powers.ActionsBase;
+import engine.powers.PowersBase;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+
+public class ResurrectPowerAction extends AbstractPowerAction {
+
+	public ResurrectPowerAction(ResultSet rs) throws SQLException {
+		super(rs);
+	}
+
+	@Override
+	protected void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc, int trains, ActionsBase ab, PowersBase pb) {
+
+	}
+
+	@Override
+	protected void _handleChant(AbstractCharacter source, AbstractWorldObject target, Vector3fImmutable targetLoc, int trains, ActionsBase ab, PowersBase pb) {
+	}
+
+	@Override
+	protected void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc,
+			int numTrains, ActionsBase ab, PowersBase pb, int duration) {
+		// TODO Auto-generated method stub
+
+	}
+}
diff --git a/src/engine/powers/poweractions/RunegateTeleportPowerAction.java b/src/engine/powers/poweractions/RunegateTeleportPowerAction.java
new file mode 100644
index 00000000..559df3f9
--- /dev/null
+++ b/src/engine/powers/poweractions/RunegateTeleportPowerAction.java
@@ -0,0 +1,95 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.powers.poweractions;
+
+import engine.Enum;
+import engine.Enum.RunegateType;
+import engine.math.Vector3fImmutable;
+import engine.net.Dispatch;
+import engine.net.DispatchMessage;
+import engine.net.client.msg.PromptRecallMsg;
+import engine.objects.*;
+import engine.powers.ActionsBase;
+import engine.powers.PowersBase;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+import static engine.math.FastMath.sqr;
+import static engine.math.FastMath.sqrt;
+
+public class RunegateTeleportPowerAction extends AbstractPowerAction {
+
+	/**
+	 * ResultSet Constructor
+	 */
+	public RunegateTeleportPowerAction(ResultSet rs) throws SQLException {
+		super(rs);
+	}
+
+	@Override
+	protected void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc, int trains, ActionsBase ab, PowersBase pb) {
+
+		if (source == null || awo == null || !(awo .getObjectType().equals(Enum.GameObjectType.PlayerCharacter)))
+			return;
+
+		PlayerCharacter pc = (PlayerCharacter) awo;
+		float dist = 9999999999f;
+		Building rg = null;
+		Vector3fImmutable rgLoc;
+
+		for (Runegate runegate: Runegate.getRunegates()) {
+
+			if ((runegate.getGateType() == RunegateType.OBLIV) ||
+					(runegate.getGateType() == RunegateType.CHAOS))
+				continue;
+
+			for (Runegate thisGate : Runegate.getRunegates()) {
+
+				rgLoc = thisGate.getGateType().getGateBuilding().getLoc();
+
+				float distanceToRunegateSquared = source.getLoc().distanceSquared2D(rgLoc);
+
+				if (distanceToRunegateSquared < sqr(dist)) {
+					dist = sqrt(distanceToRunegateSquared);
+					rg = thisGate.getGateType().getGateBuilding();
+				}
+			}
+		}
+
+		if(source.getObjectUUID() != pc.getObjectUUID()) {
+			pc.setTimeStampNow("PromptRecall");
+			pc.setTimeStamp("LastRecallType",0); //recall to rg
+
+			if (rg != null) {
+				PromptRecallMsg promptRecallMsgmsg = new PromptRecallMsg();
+				Dispatch dispatch = Dispatch.borrow(pc, promptRecallMsgmsg);
+				DispatchMessage.dispatchMsgDispatch(dispatch, engine.Enum.DispatchChannel.SECONDARY);
+			}
+
+		} else {
+			if (rg != null) {
+				pc.teleport(rg.getLoc());
+				pc.setSafeMode();
+			}
+		}
+	}
+
+	@Override
+	protected void _handleChant(AbstractCharacter source, AbstractWorldObject target, Vector3fImmutable targetLoc, int trains, ActionsBase ab, PowersBase pb) {
+	}
+
+	@Override
+	protected void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc,
+			int numTrains, ActionsBase ab, PowersBase pb, int duration) {
+		// TODO Auto-generated method stub
+
+	}
+}
diff --git a/src/engine/powers/poweractions/SetItemFlagPowerAction.java b/src/engine/powers/poweractions/SetItemFlagPowerAction.java
new file mode 100644
index 00000000..e3571750
--- /dev/null
+++ b/src/engine/powers/poweractions/SetItemFlagPowerAction.java
@@ -0,0 +1,65 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.powers.poweractions;
+
+import engine.Enum;
+import engine.gameManager.DbManager;
+import engine.math.Vector3fImmutable;
+import engine.objects.AbstractCharacter;
+import engine.objects.AbstractWorldObject;
+import engine.objects.CharacterItemManager;
+import engine.objects.Item;
+import engine.powers.ActionsBase;
+import engine.powers.PowersBase;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+
+public class SetItemFlagPowerAction extends AbstractPowerAction {
+
+	public SetItemFlagPowerAction(ResultSet rs) throws SQLException {
+		super(rs);
+	}
+
+	@Override
+	protected void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc, int trains, ActionsBase ab, PowersBase pb) {
+
+		if (source == null || awo == null || !(awo .getObjectType().equals(Enum.GameObjectType.Item)))
+			return;
+
+		Item item = (Item) awo;
+
+		if (item.containerType != Enum.ItemContainerType.INVENTORY)
+			return; //Send an error here?
+
+		//until this is shown to do something else, just use it as item identify spell.
+		item.setIsID(true);
+
+		if (!DbManager.ItemQueries.UPDATE_FLAGS(item))
+			item.setIsID(false); //update failed, reset
+
+		//update inventory
+		CharacterItemManager cim =  source.getCharItemManager();
+		if (cim != null)
+			cim.updateInventory();
+	}
+
+	@Override
+	protected void _handleChant(AbstractCharacter source, AbstractWorldObject target, Vector3fImmutable targetLoc, int trains, ActionsBase ab, PowersBase pb) {
+	}
+
+	@Override
+	protected void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc,
+			int numTrains, ActionsBase ab, PowersBase pb, int duration) {
+		// TODO Auto-generated method stub
+
+	}
+}
diff --git a/src/engine/powers/poweractions/SimpleDamagePowerAction.java b/src/engine/powers/poweractions/SimpleDamagePowerAction.java
new file mode 100644
index 00000000..eb6a5b4d
--- /dev/null
+++ b/src/engine/powers/poweractions/SimpleDamagePowerAction.java
@@ -0,0 +1,51 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.powers.poweractions;
+
+import engine.math.Vector3fImmutable;
+import engine.objects.AbstractCharacter;
+import engine.objects.AbstractWorldObject;
+import engine.powers.ActionsBase;
+import engine.powers.PowersBase;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+
+public class SimpleDamagePowerAction extends AbstractPowerAction {
+
+	private int simpleDamage;
+
+	public SimpleDamagePowerAction(ResultSet rs) throws SQLException {
+		super(rs);
+
+		this.simpleDamage = rs.getInt("simpleDamage");
+	}
+
+	public int getSimpleDamage() {
+		return this.simpleDamage;
+	}
+
+	@Override
+	protected void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc, int trains, ActionsBase ab, PowersBase pb) {
+
+	}
+
+	@Override
+	protected void _handleChant(AbstractCharacter source, AbstractWorldObject target, Vector3fImmutable targetLoc, int trains, ActionsBase ab, PowersBase pb) {
+	}
+
+	@Override
+	protected void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc,
+			int numTrains, ActionsBase ab, PowersBase pb, int duration) {
+		// TODO Auto-generated method stub
+
+	}
+}
diff --git a/src/engine/powers/poweractions/SpireDisablePowerAction.java b/src/engine/powers/poweractions/SpireDisablePowerAction.java
new file mode 100644
index 00000000..7ad0bd30
--- /dev/null
+++ b/src/engine/powers/poweractions/SpireDisablePowerAction.java
@@ -0,0 +1,89 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.powers.poweractions;
+
+import engine.Enum.BuildingGroup;
+import engine.Enum.GameObjectType;
+import engine.gameManager.ChatManager;
+import engine.math.Vector3fImmutable;
+import engine.objects.AbstractCharacter;
+import engine.objects.AbstractWorldObject;
+import engine.objects.Building;
+import engine.objects.PlayerCharacter;
+import engine.powers.ActionsBase;
+import engine.powers.PowersBase;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+public class SpireDisablePowerAction extends AbstractPowerAction {
+
+	/**
+	 * ResultSet Constructor
+	 */
+	public SpireDisablePowerAction(ResultSet rs) throws SQLException {
+		super(rs);
+	}
+
+	@Override
+	protected void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc, int trains, ActionsBase ab, PowersBase pb) {
+		if (awo == null)
+			return;
+
+		if (source == null)
+			return;
+
+		PlayerCharacter pc = null;
+
+		if (source.getObjectType() == GameObjectType.PlayerCharacter)
+			pc = (PlayerCharacter)source;
+		else
+			return;
+
+		if (awo.getObjectType() != GameObjectType.Building)
+			return;
+
+
+		//Check if Building is Spire.
+
+		Building spire = (Building)awo;
+
+		if ((spire.getBlueprintUUID() == 0) ||
+				(spire.getBlueprint() != null && spire.getBlueprint().getBuildingGroup() != BuildingGroup.SPIRE)) {
+			ChatManager.chatSystemError((PlayerCharacter)source, "This Building is not a spire.");
+			return;
+		}
+
+		if (!spire.isSpireIsActive())
+			return;
+
+		spire.disableSpire(false);
+
+		if (trains > 20)
+			trains = 20;
+
+		long duration = trains * 4500 + 30000;
+		spire.setTimeStamp("DISABLED", System.currentTimeMillis() + duration);
+
+
+
+	}
+
+	@Override
+	protected void _handleChant(AbstractCharacter source, AbstractWorldObject target, Vector3fImmutable targetLoc, int trains, ActionsBase ab, PowersBase pb) {
+	}
+
+	@Override
+	protected void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc,
+			int numTrains, ActionsBase ab, PowersBase pb, int duration) {
+		// TODO Auto-generated method stub
+
+	}
+}
diff --git a/src/engine/powers/poweractions/StealPowerAction.java b/src/engine/powers/poweractions/StealPowerAction.java
new file mode 100644
index 00000000..2cdc56db
--- /dev/null
+++ b/src/engine/powers/poweractions/StealPowerAction.java
@@ -0,0 +1,201 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.powers.poweractions;
+
+import engine.Enum;
+import engine.Enum.ItemType;
+import engine.gameManager.ChatManager;
+import engine.gameManager.CombatManager;
+import engine.math.Vector3fImmutable;
+import engine.net.Dispatch;
+import engine.net.DispatchMessage;
+import engine.net.client.ClientConnection;
+import engine.net.client.msg.LootMsg;
+import engine.objects.*;
+import engine.powers.ActionsBase;
+import engine.powers.PowersBase;
+import engine.server.MBServerStatics;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.concurrent.ThreadLocalRandom;
+
+import static engine.math.FastMath.sqr;
+
+
+public class StealPowerAction extends AbstractPowerAction {
+
+	/**
+	 * ResultSet Constructor
+	 */
+	public StealPowerAction(ResultSet rs) throws SQLException {
+		super(rs);
+	}
+
+	@Override
+	protected void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc, int trains, ActionsBase ab, PowersBase pb) {
+
+		if (source == null || awo == null || !(source.getObjectType().equals(Enum.GameObjectType.PlayerCharacter)) || !(awo.getObjectType().equals(Enum.GameObjectType.Item)))
+			return;
+
+		PlayerCharacter sourcePlayer = (PlayerCharacter) source;
+
+		if (sourcePlayer.isSafeMode())
+			return;
+
+		if (!sourcePlayer.isAlive())
+			return;
+
+		//prevent stealing no steal mob loot
+		if (awo instanceof MobLoot && ((MobLoot)awo).noSteal())
+			return;
+
+		Item tar = (Item) awo;
+		AbstractWorldObject owner = (AbstractWorldObject) tar.getOwner();
+
+		if (owner == null)
+			return;
+
+
+		AbstractCharacter ownerAC = null;
+		if (AbstractWorldObject.IsAbstractCharacter(owner))
+			ownerAC = (AbstractCharacter) owner;
+
+			if (ownerAC != null)
+				if (ownerAC.getLoc().distanceSquared(sourcePlayer.getLoc()) > sqr(MBServerStatics.LOOT_RANGE))
+					return;
+
+		//only steal from players or mobs
+
+		if (owner.getObjectType().equals(Enum.GameObjectType.PlayerCharacter)) {
+
+			PlayerCharacter ownerPC = (PlayerCharacter)owner;
+
+			if (ownerPC.isSafeMode() || sourcePlayer.inSafeZone() || ownerPC.inSafeZone())
+				return;
+
+			if (ownerPC.getLoc().distanceSquared(sourcePlayer.getLoc()) > sqr(MBServerStatics.LOOT_RANGE))
+				return;
+
+			//dupe check, validate player has item
+			if (!tar.validForInventory(ownerPC.getClientConnection(), ownerPC, ownerPC.getCharItemManager()))//pc.getCharItemManager()))
+				return;
+
+			//mark thief and target as player aggressive
+			sourcePlayer.setLastPlayerAttackTime();
+			ownerPC.setLastPlayerAttackTime();
+
+			//Handle target attacking back if in combat and has no other target
+			CombatManager.handleRetaliate(ownerAC, sourcePlayer);
+
+		} else if (owner.getObjectType().equals(Enum.GameObjectType.Mob)) {
+			sourcePlayer.setLastMobAttackTime(); //mark thief as mob aggressive
+		} else
+			return;
+
+		ClientConnection origin = sourcePlayer.getClientConnection();
+
+		if (origin == null)
+			return;
+
+		int amount = getAmountToSteal(tar);
+
+		//test probability of steal success
+		if (!stealSuccess(sourcePlayer, owner)) {
+			ChatManager.chatPeekSteal(sourcePlayer, ownerAC, tar, false, false, -1);
+			return;
+		} else {
+			ChatManager.chatPeekSteal(sourcePlayer, ownerAC, tar, true, false, amount);
+			//TODO send steal failure success
+		}
+
+		//attempt transfer item
+		CharacterItemManager myCIM = sourcePlayer.getCharItemManager();
+		CharacterItemManager ownerCIM = ((AbstractCharacter)owner).getCharItemManager();
+		if (myCIM == null || ownerCIM == null)
+			return;
+
+		if (tar.getItemBase().getType().equals(ItemType.GOLD)) {
+			//stealing gold
+			if (!myCIM.transferGoldToMyInventory((AbstractCharacter)owner, amount))
+				return;
+		} else {
+			//stealing items
+			if (ownerCIM.lootItemFromMe(tar, sourcePlayer, origin, true, amount) == null)
+				return;
+		}
+
+		//send loot message to person stealing.
+		LootMsg lm = new LootMsg(source.getObjectType().ordinal(), source.getObjectUUID(), owner.getObjectType().ordinal(), owner.getObjectUUID(), tar);
+		Dispatch dispatch = Dispatch.borrow(sourcePlayer, lm);
+		DispatchMessage.dispatchMsgDispatch(dispatch, engine.Enum.DispatchChannel.SECONDARY);
+
+		//update thief's inventory
+		if (sourcePlayer.getCharItemManager() != null)
+			sourcePlayer.getCharItemManager().updateInventory();
+
+		//update victims inventory
+		if (owner.getObjectType().equals(Enum.GameObjectType.PlayerCharacter)) {
+			PlayerCharacter ownerPC = (PlayerCharacter) owner;
+
+			if (ownerPC.getCharItemManager() != null)
+				ownerPC.getCharItemManager().updateInventory();
+		}
+
+		//TODO if victim is trading, cancel trade window for both people involved in trade
+	}
+
+	@Override
+	protected void _handleChant(AbstractCharacter source, AbstractWorldObject target, Vector3fImmutable targetLoc, int trains, ActionsBase ab, PowersBase pb) {
+	}
+
+	protected static boolean stealSuccess(PlayerCharacter pc, AbstractWorldObject awo) {
+		if (pc == null || awo == null || !AbstractWorldObject.IsAbstractCharacter(awo) || pc.getPowers() == null)
+			return false;
+
+		int levelDif = pc.getLevel() - ((AbstractCharacter)awo).getLevel();
+
+		if (!pc.getPowers().containsKey(429396028))
+			return false;
+
+		CharacterPower cp = pc.getPowers().get(429396028);
+		int trains = cp.getTotalTrains();
+
+		float chance = 20 + (trains * 1.5f) + levelDif;
+		chance = (chance < 5f) ? 5f : chance;
+		chance = (chance > 85f) ? 85f : chance;
+
+		float roll = ThreadLocalRandom.current().nextFloat() * 100f;
+
+		return roll < chance;
+
+	}
+
+	//called to get amount of gold to steal between 0 and max gold
+	protected static int getAmountToSteal(Item i) {
+		if (i.getItemBase() != null && i.getItemBase().getUUID() == 7) {
+			int amount = i.getNumOfItems();
+			if (amount < 1)
+				return -1;
+			int a = ThreadLocalRandom.current().nextInt(amount + 1);
+			int b = ThreadLocalRandom.current().nextInt(amount + 1);
+			int c = ThreadLocalRandom.current().nextInt(amount + 1);
+			return (a + b + c) / 3;
+		} else
+			return 0;
+	}
+
+	@Override
+	protected void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc,
+			int numTrains, ActionsBase ab, PowersBase pb, int duration) {
+		// TODO Auto-generated method stub
+
+	}
+}
diff --git a/src/engine/powers/poweractions/SummonPowerAction.java b/src/engine/powers/poweractions/SummonPowerAction.java
new file mode 100644
index 00000000..32ef0e64
--- /dev/null
+++ b/src/engine/powers/poweractions/SummonPowerAction.java
@@ -0,0 +1,78 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.powers.poweractions;
+
+import engine.Enum;
+import engine.gameManager.SessionManager;
+import engine.gameManager.ZoneManager;
+import engine.math.Vector3fImmutable;
+import engine.net.Dispatch;
+import engine.net.DispatchMessage;
+import engine.net.client.ClientConnection;
+import engine.net.client.msg.RecvSummonsRequestMsg;
+import engine.objects.AbstractCharacter;
+import engine.objects.AbstractWorldObject;
+import engine.objects.PlayerCharacter;
+import engine.objects.Zone;
+import engine.powers.ActionsBase;
+import engine.powers.PowersBase;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+public class SummonPowerAction extends AbstractPowerAction {
+
+	/**
+	 * ResultSet Constructor
+	 */
+	public SummonPowerAction(ResultSet rs) throws SQLException {
+		super(rs);
+	}
+
+	@Override
+	protected void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc, int trains, ActionsBase ab,
+			PowersBase pb) {
+
+		if (source == null || awo == null || !(awo.getObjectType().equals(Enum.GameObjectType.PlayerCharacter)) || !(source.getObjectType().equals(Enum.GameObjectType.PlayerCharacter)))
+			return;
+
+		PlayerCharacter target = (PlayerCharacter) awo;
+
+		ClientConnection conn = SessionManager.getClientConnection(target);
+
+		if (conn == null)
+			return;
+
+		// TODO get location of summoning player
+		Zone zone = ZoneManager.findSmallestZone(source.getLoc());
+		String location = "Somewhere";
+
+		if (zone != null)
+			location = zone.getName();
+
+		RecvSummonsRequestMsg rsrm = new RecvSummonsRequestMsg(source.getObjectType().ordinal(), source.getObjectUUID(), source.getFirstName(),
+				location, false);
+
+		Dispatch dispatch = Dispatch.borrow(target, rsrm);
+		DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+
+	}
+
+	@Override
+	protected void _handleChant(AbstractCharacter source, AbstractWorldObject target, Vector3fImmutable targetLoc, int trains, ActionsBase ab, PowersBase pb) {
+	}
+
+	@Override
+	protected void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc,
+			int numTrains, ActionsBase ab, PowersBase pb, int duration) {
+		// TODO Auto-generated method stub
+
+	}
+}
diff --git a/src/engine/powers/poweractions/TeleportPowerAction.java b/src/engine/powers/poweractions/TeleportPowerAction.java
new file mode 100644
index 00000000..e04e642d
--- /dev/null
+++ b/src/engine/powers/poweractions/TeleportPowerAction.java
@@ -0,0 +1,116 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.powers.poweractions;
+
+import engine.Enum;
+import engine.Enum.ModType;
+import engine.Enum.SourceType;
+import engine.gameManager.MovementManager;
+import engine.gameManager.PowersManager;
+import engine.gameManager.ZoneManager;
+import engine.math.Vector3fImmutable;
+import engine.objects.*;
+import engine.powers.ActionsBase;
+import engine.powers.PowersBase;
+import engine.powers.effectmodifiers.AbstractEffectModifier;
+import engine.server.MBServerStatics;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+
+public class TeleportPowerAction extends AbstractPowerAction {
+
+	private boolean ignoreNoTeleSpire;
+
+	public TeleportPowerAction(ResultSet rs) throws SQLException {
+		super(rs);
+
+		int flags = rs.getInt("flags");
+		this.ignoreNoTeleSpire = ((flags & 32768) != 0) ? true : false;
+	}
+
+	public boolean ignoreNoTeleSpire() {
+		return this.ignoreNoTeleSpire;
+	}
+
+	@Override
+	protected void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc, int trains, ActionsBase ab, PowersBase pb) {
+
+		if (!AbstractWorldObject.IsAbstractCharacter(awo))
+			return;
+
+		AbstractCharacter awoac = (AbstractCharacter) awo;
+
+		//verify targetLoc within range
+
+		if (awo.getLoc().distanceSquared2D(targetLoc) >  MBServerStatics.MAX_TELEPORT_RANGE *  MBServerStatics.MAX_TELEPORT_RANGE) {
+			if (awo.equals(source))
+				failTeleport(pb, awoac);
+			return;
+		}
+		
+		if (source.getBonuses().getBool(ModType.BlockedPowerType, SourceType.TELEPORT))
+			return;
+
+		City city = ZoneManager.getCityAtLocation(targetLoc);
+
+		// Intentionally fail if target location is not on
+		// the actual city zone.
+		if (city != null)
+		if (city.isLocationOnCityZone(targetLoc) == false)
+			city = null;
+
+		if (city != null){
+
+			for (String eff : city.getEffects().keySet()){
+
+				Effect spireEffect = city.getEffects().get(eff);
+
+				for (AbstractEffectModifier aem : spireEffect.getEffectModifiers()){
+
+					if (aem.getType().equals("TELEPORT") &&  !this.ignoreNoTeleSpire){
+						if (awo.equals(source))
+							failTeleport(pb, awoac);
+						return;
+					}
+				}
+			}
+		}
+
+		//TODO verify target loc is valid loc
+		
+		Regions region = Regions.GetRegionForTeleport(targetLoc);
+
+		if (region != null && !region.isOutside())
+			return;
+
+		MovementManager.translocate(awoac,targetLoc, region);
+	}
+
+	private static void failTeleport(PowersBase pb, AbstractCharacter awo) {
+
+		if (pb == null || awo == null || (!(awo.getObjectType().equals(Enum.GameObjectType.PlayerCharacter))))
+			return;
+
+		//teleport failed. Reset teleport power
+		PowersManager.finishRecycleTime(pb.getToken(), (PlayerCharacter) awo, true);
+	}
+
+	@Override
+	protected void _handleChant(AbstractCharacter source, AbstractWorldObject target, Vector3fImmutable targetLoc, int trains, ActionsBase ab, PowersBase pb) {
+	}
+
+	@Override
+	protected void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc,
+			int numTrains, ActionsBase ab, PowersBase pb, int duration) {
+		// TODO Auto-generated method stub
+	}
+}
\ No newline at end of file
diff --git a/src/engine/powers/poweractions/TrackPowerAction.java b/src/engine/powers/poweractions/TrackPowerAction.java
new file mode 100644
index 00000000..f12943c9
--- /dev/null
+++ b/src/engine/powers/poweractions/TrackPowerAction.java
@@ -0,0 +1,127 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.powers.poweractions;
+
+import engine.jobs.TrackJob;
+import engine.math.Vector3fImmutable;
+import engine.objects.AbstractCharacter;
+import engine.objects.AbstractWorldObject;
+import engine.powers.ActionsBase;
+import engine.powers.EffectsBase;
+import engine.powers.PowersBase;
+import engine.server.MBServerStatics;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.HashMap;
+
+
+public class TrackPowerAction extends AbstractPowerAction {
+
+	private String effectID;
+	private boolean trackPlayer;
+	private boolean trackCorpse;
+	private boolean trackAll;
+	private boolean trackDragon;
+	private boolean trackGiant;
+	private boolean trackNPC;
+	private boolean trackUndead;
+	private boolean trackVampire;
+	private int maxTrack;
+	private EffectsBase effect;
+
+	public TrackPowerAction(ResultSet rs, HashMap<String, EffectsBase> effects) throws SQLException {
+		super(rs);
+
+		this.effectID = rs.getString("effectID");
+		int flags = rs.getInt("flags");
+		this.trackPlayer = ((flags & 1024) == 1) ? true : false;
+		this.trackCorpse = ((flags & 2048) == 1) ? true : false;
+		String trackFilter = rs.getString("trackFilter");
+		this.trackAll = trackFilter.equals("All") ? true : false;
+		this.trackDragon = trackFilter.equals("Dragon") ? true : false;
+		this.trackGiant = trackFilter.equals("Giant") ? true : false;
+		this.trackNPC = trackFilter.equals("NPC") ? true : false;
+		this.trackUndead = trackFilter.equals("Undead") ? true : false;
+		this.trackVampire = trackFilter.equals("Vampire") ? true : false;
+
+		this.maxTrack = rs.getInt("maxTrack");
+		this.effect = effects.get(this.effectID);
+	}
+
+	public String getEffectID() {
+		return this.effectID;
+	}
+
+	public boolean trackPlayer() {
+		return this.trackPlayer;
+	}
+
+	public boolean trackCorpse() {
+		return this.trackCorpse;
+	}
+
+	public boolean trackAll() {
+		return this.trackAll;
+	}
+
+	public boolean trackDragon() {
+		return this.trackDragon;
+	}
+
+	public boolean trackGiant() {
+		return this.trackGiant;
+	}
+
+	public boolean trackNPC() {
+		return this.trackNPC;
+	}
+
+	public boolean trackUndead() {
+		return this.trackUndead;
+	}
+
+	public boolean trackVampire() {
+		return this.trackVampire;
+	}
+
+	public int getMaxTrack() {
+		return this.maxTrack;
+	}
+
+	public EffectsBase getEffect() {
+		return this.effect;
+	}
+
+	@Override
+	protected void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc, int trains, ActionsBase ab, PowersBase pb) {
+		if (source == null || awo == null || this.effect == null || pb == null || ab == null) {
+			//TODO log error here
+			return;
+		}
+
+		//add schedule job to end it if needed and add effect to pc
+		int duration = MBServerStatics.TRACK_ARROW_SENSITIVITY;
+		String stackType = ab.getStackType();
+		TrackJob eff = new TrackJob(source, awo, stackType, trains, ab, pb, this.effect, this);
+		source.addEffect(stackType, duration, eff, this.effect, trains);
+	}
+
+	@Override
+	protected void _handleChant(AbstractCharacter source, AbstractWorldObject target, Vector3fImmutable targetLoc, int trains, ActionsBase ab, PowersBase pb) {
+	}
+
+	@Override
+	protected void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc,
+			int numTrains, ActionsBase ab, PowersBase pb, int duration) {
+		// TODO Auto-generated method stub
+
+	}
+}
diff --git a/src/engine/powers/poweractions/TransferStatOTPowerAction.java b/src/engine/powers/poweractions/TransferStatOTPowerAction.java
new file mode 100644
index 00000000..f3940233
--- /dev/null
+++ b/src/engine/powers/poweractions/TransferStatOTPowerAction.java
@@ -0,0 +1,68 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.powers.poweractions;
+
+import engine.jobs.TransferStatOTJob;
+import engine.math.Vector3f;
+import engine.math.Vector3fImmutable;
+import engine.objects.AbstractCharacter;
+import engine.objects.AbstractWorldObject;
+import engine.powers.ActionsBase;
+import engine.powers.EffectsBase;
+import engine.powers.PowersBase;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.HashMap;
+
+
+public class TransferStatOTPowerAction extends TransferStatPowerAction {
+
+	private int numIterations;
+
+	public TransferStatOTPowerAction(ResultSet rs, HashMap<String, EffectsBase> effects) throws SQLException {
+		super(rs, effects);
+
+		this.numIterations = rs.getInt("numIterations");
+	}
+
+	public int getNumIterations() {
+		return this.numIterations;
+	}
+
+	protected void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3f targetLoc, int trains, ActionsBase ab, PowersBase pb) {
+		this.__startAction(source, awo, trains, ab, pb);
+	}
+
+	@Override
+	protected void __startAction(AbstractCharacter source, AbstractWorldObject awo, int trains, ActionsBase ab, PowersBase pb) {
+		if (this.effect == null || source == null || awo == null || ab == null || pb == null)
+			return;
+
+		//add schedule job to end it if needed and add effect to pc
+		int duration = ab.getDuration(trains);
+		String stackType = ab.getStackType();
+		stackType = (stackType.equals("IgnoreStack")) ? Integer.toString(ab.getUUID()) : stackType;
+		TransferStatOTJob eff = new TransferStatOTJob(source, awo, stackType, trains, ab, pb, this.effect, this);
+		int tick = eff.getTickLength();
+
+		if (duration > 0)
+			awo.addEffect(stackType, tick, eff, this.effect, trains);
+
+		//start effect icon for client. Skip applying dot until first iteration.
+		eff.setSkipApplyEffect(true);
+		this.effect.startEffect(source, awo, trains, eff);
+		eff.setSkipApplyEffect(false);
+	}
+
+	@Override
+	protected void _handleChant(AbstractCharacter source, AbstractWorldObject target, Vector3fImmutable targetLoc, int trains, ActionsBase ab, PowersBase pb) {
+	}
+}
diff --git a/src/engine/powers/poweractions/TransferStatPowerAction.java b/src/engine/powers/poweractions/TransferStatPowerAction.java
new file mode 100644
index 00000000..cb2f7960
--- /dev/null
+++ b/src/engine/powers/poweractions/TransferStatPowerAction.java
@@ -0,0 +1,299 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.powers.poweractions;
+
+import engine.Enum;
+import engine.Enum.DamageType;
+import engine.Enum.ModType;
+import engine.Enum.SourceType;
+import engine.gameManager.ChatManager;
+import engine.math.Vector3fImmutable;
+import engine.net.AbstractNetMsg;
+import engine.net.DispatchMessage;
+import engine.net.client.msg.ModifyHealthKillMsg;
+import engine.net.client.msg.ModifyHealthMsg;
+import engine.objects.*;
+import engine.powers.ActionsBase;
+import engine.powers.EffectsBase;
+import engine.powers.PowersBase;
+import engine.powers.effectmodifiers.HealthEffectModifier;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.HashMap;
+import java.util.concurrent.ThreadLocalRandom;
+
+public class TransferStatPowerAction extends AbstractPowerAction {
+
+	protected String effectID;
+	protected boolean transferFromHealth = false;
+	protected boolean transferFromMana = false;
+	protected boolean transferFromStamina = false;
+	protected boolean transferToHealth = false;
+	protected boolean transferToMana = false;
+	protected boolean transferToStamina = false;
+	protected float transferAmount;
+	protected float transferRamp;
+	protected boolean transferRampAdd;
+	protected float transferEfficiency;
+	protected float transferEfficiencyRamp;
+	protected boolean transferEfficiencyRampAdd;
+	protected boolean targetToCaster;
+	protected DamageType damageType;
+	protected EffectsBase effect;
+
+	public TransferStatPowerAction(ResultSet rs, HashMap<String, EffectsBase> effects) throws SQLException {
+		super(rs);
+		this.effectID = rs.getString("effectID");
+		String st = rs.getString("transferFromType");
+		if (st.equals("HEALTH"))
+			this.transferFromHealth = true;
+		else if (st.equals("MANA"))
+			this.transferFromMana = true;
+		else
+			this.transferFromStamina = true;
+		st = rs.getString("transferToType");
+		if (st.equals("HEALTH"))
+			this.transferToHealth = true;
+		else if (st.equals("MANA"))
+			this.transferToMana = true;
+		else
+			this.transferToStamina = true;
+		this.transferAmount = rs.getFloat("transferAmount");
+		this.transferRamp = rs.getFloat("transferRamp");
+		this.transferEfficiency = rs.getFloat("transferEfficiency");
+		this.transferEfficiencyRamp = rs.getFloat("transferEfficiencyRamp");
+		int flags = rs.getInt("flags");
+		this.transferRampAdd = ((flags & 4096) != 0) ? true : false;
+		this.transferEfficiencyRampAdd = ((flags & 8192) != 0) ? true : false;
+		this.targetToCaster = ((flags & 16384) != 0) ? true : false;
+		this.effect = effects.get(this.effectID);
+		try {
+			String damageString = rs.getString("damageType");
+			// Damage type can sometimes be null in the DB.
+
+			if (damageString.isEmpty() == false)
+				this.damageType = DamageType.valueOf(damageString);
+		} catch (Exception e) {
+			this.damageType = null;
+		}
+	}
+
+	public String getEffectID() {
+		return this.effectID;
+	}
+
+	public boolean transferFromHealth() {
+		return this.transferFromHealth;
+	}
+
+	public boolean transferFromMana() {
+		return this.transferFromMana;
+	}
+
+	public boolean transferFromStamina() {
+		return this.transferFromStamina;
+	}
+
+	public boolean transferToHealth() {
+		return this.transferToHealth;
+	}
+
+	public boolean transferToMana() {
+		return this.transferToMana;
+	}
+
+	public boolean transferToStamina() {
+		return this.transferToStamina;
+	}
+
+	public EffectsBase getEffect() {
+		return this.effect;
+	}
+
+	public float getTransferAmount(float trains) {
+		//		if (this.transferRampAdd)
+		return this.transferAmount + (this.transferRamp * trains);
+		//		else
+		//			return this.transferAmount * (1 + (this.transferRamp * trains));
+	}
+
+	public float getTransferEfficiency(float trains) {
+		return this.transferEfficiency + (this.transferEfficiencyRamp * trains);
+	}
+
+	public boolean targetToCaster() {
+		return this.targetToCaster;
+	}
+
+	@Override
+	protected void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc, int trains, ActionsBase ab, PowersBase pb) {
+		this.__startAction(source, awo, trains, ab, pb);
+	}
+
+	//Added for dependancy check on TransferStatOTPowerAction
+	protected void __startAction(AbstractCharacter source, AbstractWorldObject awo, int trains, ActionsBase ab, PowersBase pb) {
+		this.runAction(source, awo, trains, ab, pb);
+	}
+
+	public void runAction(AbstractCharacter source, AbstractWorldObject awo, int trains, ActionsBase ab, PowersBase pb) {
+		if (source == null || awo == null || ab == null || pb == null)
+			return;
+
+		if (!source.isAlive() || !awo.isAlive())
+			return;
+
+		AbstractWorldObject fromAwo;
+		AbstractWorldObject toAwo;
+		if (this.targetToCaster) {
+			fromAwo = awo;
+			toAwo = source;
+		} else {
+			fromAwo = source;
+			toAwo = awo;
+		}
+
+	
+
+		if (AbstractWorldObject.IsAbstractCharacter(fromAwo) && AbstractWorldObject.IsAbstractCharacter(toAwo)) {
+			AbstractCharacter from = (AbstractCharacter) fromAwo;
+			AbstractCharacter to = (AbstractCharacter) toAwo;
+
+			//get amount to drain
+			float fromAmount = getTransferAmount(trains);
+
+			//modify for resists if needed
+			if (this.damageType != null) {
+				Resists resists = from.getResists();
+				if (resists != null)
+					fromAmount = resists.getResistedDamage(to, from, this.damageType, fromAmount * -1, trains) * -1;
+			}
+
+			float min = fromAmount;// * (getTransferEfficiency(trains) / 100);
+			float max = min;
+			float damage = 0f;
+
+			if (source.getObjectType().equals(Enum.GameObjectType.PlayerCharacter)) {
+				PlayerCharacter pc = (PlayerCharacter) source;
+				float focus;
+				CharacterSkill skill = pc.getSkills().get(pb.getSkillName());
+				if (skill == null)
+					focus = CharacterSkill.getQuickMastery(pc, pb.getSkillName());
+				else
+					focus = skill.getModifiedAmount();
+
+				//TODO fix this formula later
+				float intt = (pc.getStatIntCurrent() >= 1) ? (float)pc.getStatIntCurrent() : 1f;
+				float spi = (pc.getStatSpiCurrent() >= 1) ? (float)pc.getStatSpiCurrent() : 1f;
+				//				min *= (intt * 0.0045 + 0.055 * (float)Math.sqrt(intt - 0.5) + spi * 0.006 + 0.07 * (float)Math.sqrt(spi - 0.5) + 0.02 * (int)focus);
+				//				max *= (intt * 0.0117 + 0.13 * (float)Math.sqrt(intt - 0.5) + spi * 0.0024 + (float)Math.sqrt(spi - 0.5) * 0.021 + 0.015 * (int)focus);
+				//				min *= (0.62 + 0.0192 * pc.getStatSpiCurrent() + 0.00415 * pc.getStatIntCurrent() + 0.015 * focus) / 2;
+				//				max *= (0.62 + 0.0192 * pc.getStatIntCurrent() + 0.00415 * pc.getStatSpiCurrent() + 0.015 * focus) / 2;
+				min = HealthEffectModifier.getMinDamage(min, intt, spi, focus);
+				max = HealthEffectModifier.getMaxDamage(max, intt, spi, focus);
+
+				// get range between min and max
+				float range = max - min;
+
+				//debug for spell damage and atr
+				if (pc.getDebug(16)) {
+					String smsg = "Damage: " + (int)Math.abs(min) + " - " + (int)Math.abs(max);
+					ChatManager.chatSystemInfo(pc, smsg);
+				}
+
+				// Damage is calculated twice to average a more central point
+				damage = ThreadLocalRandom.current().nextFloat() * range;
+				damage = (damage + (ThreadLocalRandom.current().nextFloat() * range)) / 2;
+
+				// put it back between min and max
+				damage += min;
+			}
+
+			// Apply any power effect modifiers (such as stances)
+			PlayerBonuses bonus = source.getBonuses();
+			if (bonus != null)
+				damage *= (1 + bonus.getFloatPercentAll(ModType.PowerDamageModifier, SourceType.None));
+
+			//get amount to transfer
+			fromAmount = damage;
+			float toAmount = fromAmount * (getTransferEfficiency(trains) / 100);
+
+			//get max amount to transfer, don't give more then the target has
+			float maxDrain;
+			if (this.transferFromHealth)
+				maxDrain = from.getCurrentHitpoints();
+			else if (this.transferFromMana)
+				maxDrain = from.getMana();
+			else
+				maxDrain = from.getStamina();
+			if (toAmount > maxDrain)
+				toAmount = maxDrain;
+
+			//prep messages for transfer
+			int powerID = pb.getToken();
+			int effectID = 496519310;
+			String powerName = pb.getName();
+			ModifyHealthMsg mhmTo;
+			//			ModifyHealthMsg mhmFrom;
+			AbstractNetMsg mhmFrom = null;
+			
+			//stop if target is immune to drains
+			if ( from.getBonuses().getBool(ModType.ImmuneTo, SourceType.Drain)) {
+				ModifyHealthMsg mhm = new ModifyHealthMsg(source, to, 0f, 0f, 0f, powerID, powerName, trains, effectID);
+				mhm.setUnknown03(5); //set target is immune
+				DispatchMessage.sendToAllInRange(from, mhm);
+				return;
+			}
+
+			//apply transfer bonus
+			if (this.transferToHealth) {
+				to.modifyHealth(toAmount, source, false);
+				mhmTo = new ModifyHealthMsg(source, to, toAmount, 0f, 0f, powerID, powerName, trains, effectID);
+			} else if (this.transferToMana) {
+				to.modifyMana(toAmount, source);
+				mhmTo = new ModifyHealthMsg(source, to, 0f, toAmount, 0f, powerID, powerName, trains, effectID);
+			} else {
+				to.modifyStamina(toAmount, source);
+				mhmTo = new ModifyHealthMsg(source, to, 0f, 0f, toAmount, powerID, powerName, trains, effectID);
+			}
+
+			//subtract transfer amount
+			if (this.transferFromHealth) {
+				float modFrom = from.modifyHealth(-fromAmount, source, false);
+				float cur = from.getHealth();
+				if (cur < 0 && modFrom != 0)
+					mhmFrom = new ModifyHealthKillMsg(source, from, -fromAmount, 0f, 0f, powerID, powerName, trains, effectID);
+				else
+					mhmFrom = new ModifyHealthMsg(source, from, -fromAmount, 0f, 0f, powerID, powerName, trains, effectID);
+			} else if (this.transferFromMana) {
+				from.modifyMana(-fromAmount, source);
+				mhmFrom = new ModifyHealthMsg(source, from, 0f, -fromAmount, 0f, powerID, powerName, trains, effectID);
+			} else {
+				from.modifyStamina(-fromAmount, source);
+				mhmFrom = new ModifyHealthMsg(source, from, 0f, 0f, -fromAmount, powerID, powerName, trains, effectID);
+			}
+
+			DispatchMessage.sendToAllInRange(to, mhmTo);
+			DispatchMessage.sendToAllInRange(from, mhmFrom);
+
+		}
+	}
+
+	@Override
+	protected void _handleChant(AbstractCharacter source, AbstractWorldObject target, Vector3fImmutable targetLoc, int trains, ActionsBase ab, PowersBase pb) {
+	}
+
+	@Override
+	protected void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc,
+			int numTrains, ActionsBase ab, PowersBase pb, int duration) {
+		// TODO Auto-generated method stub
+
+	}
+}
diff --git a/src/engine/powers/poweractions/TransformPowerAction.java b/src/engine/powers/poweractions/TransformPowerAction.java
new file mode 100644
index 00000000..6f8d0be2
--- /dev/null
+++ b/src/engine/powers/poweractions/TransformPowerAction.java
@@ -0,0 +1,71 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.powers.poweractions;
+
+import engine.jobs.FinishEffectTimeJob;
+import engine.math.Vector3fImmutable;
+import engine.objects.AbstractCharacter;
+import engine.objects.AbstractWorldObject;
+import engine.powers.ActionsBase;
+import engine.powers.EffectsBase;
+import engine.powers.PowersBase;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.HashMap;
+
+
+public class TransformPowerAction extends AbstractPowerAction {
+
+	private String effectID;
+	private EffectsBase effect;
+
+	public TransformPowerAction(ResultSet rs, HashMap<String, EffectsBase> effects) throws SQLException {
+		super(rs);
+
+		this.effectID = rs.getString("effectID");
+		this.effect = effects.get(this.effectID);
+	}
+
+	public String getEffectID() {
+		return this.effectID;
+	}
+
+	public EffectsBase getEffect() {
+		return this.effect;
+	}
+
+	@Override
+	protected void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc, int trains, ActionsBase ab, PowersBase pb) {
+		if (this.effect == null || pb == null || ab == null) {
+			//TODO log error here
+			return;
+		}
+
+		int duration = ab.getDuration(trains);
+		String stackType = ab.getStackType();
+		stackType = (stackType.equals("IgnoreStack")) ? Integer.toString(ab.getUUID()) : stackType;
+		FinishEffectTimeJob eff = new FinishEffectTimeJob(source, awo, stackType, trains, ab, pb, effect);
+		if (duration > 0)
+			awo.addEffect(stackType, duration, eff, effect, trains);
+		this.effect.startEffect(source, awo, trains, eff);
+	}
+
+	@Override
+	protected void _handleChant(AbstractCharacter source, AbstractWorldObject target, Vector3fImmutable targetLoc, int trains, ActionsBase ab, PowersBase pb) {
+	}
+
+	@Override
+	protected void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc,
+			int numTrains, ActionsBase ab, PowersBase pb, int duration) {
+		// TODO Auto-generated method stub
+
+	}
+}
diff --git a/src/engine/powers/poweractions/TreeChokePowerAction.java b/src/engine/powers/poweractions/TreeChokePowerAction.java
new file mode 100644
index 00000000..00a93f14
--- /dev/null
+++ b/src/engine/powers/poweractions/TreeChokePowerAction.java
@@ -0,0 +1,43 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.powers.poweractions;
+
+import engine.math.Vector3fImmutable;
+import engine.objects.AbstractCharacter;
+import engine.objects.AbstractWorldObject;
+import engine.powers.ActionsBase;
+import engine.powers.PowersBase;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+
+public class TreeChokePowerAction extends AbstractPowerAction {
+
+	public TreeChokePowerAction(ResultSet rs) throws SQLException {
+		super(rs);
+	}
+
+	@Override
+	protected void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc, int trains, ActionsBase ab, PowersBase pb) {
+
+	}
+
+	@Override
+	protected void _handleChant(AbstractCharacter source, AbstractWorldObject target, Vector3fImmutable targetLoc, int trains, ActionsBase ab, PowersBase pb) {
+	}
+
+	@Override
+	protected void _startAction(AbstractCharacter source, AbstractWorldObject awo, Vector3fImmutable targetLoc,
+			int numTrains, ActionsBase ab, PowersBase pb, int duration) {
+		// TODO Auto-generated method stub
+
+	}
+}
diff --git a/src/engine/server/MBServerStatics.java b/src/engine/server/MBServerStatics.java
new file mode 100644
index 00000000..6c2b46fb
--- /dev/null
+++ b/src/engine/server/MBServerStatics.java
@@ -0,0 +1,830 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.server;
+
+import engine.Enum;
+import engine.gameManager.ConfigManager;
+import engine.math.Vector3fImmutable;
+
+public class MBServerStatics {
+
+	public static final int revisionNumber = 1;
+
+    public static String getEmulatorVersion() {
+		return Integer.toString(revisionNumber);
+	}
+
+	public static final String CMDLINE_ARGS_EXE_NAME_DELIMITER = "-name";
+	public static final String CMDLINE_ARGS_CONFIG_FILE_PATH_DELIMITER = "-config";
+	public static final String CMDLINE_ARGS_CALLER_DELIMITER = "-caller";
+	public static final String CMDLINE_ARGS_REASON_DELIMITER = "-reason";
+	public static final String EXISTING_CONNECTION_CLOSED = "An existing connection was forcibly closed by the remote host";
+	public static final String RESET_BY_PEER = "Connection reset by peer";
+	/*
+	 * ####Debugging Flags####
+	 */
+	public static final boolean POWERS_DEBUG = false;
+	public static final boolean MOVEMENT_SYNC_DEBUG = false;
+	public static final boolean BONUS_TRAINS_ENABLED = false;
+	public static final boolean REGENS_DEBUG = false;
+	public static final boolean SHOW_SAFE_MODE_CHANGE = false;
+	public static final boolean COMBAT_TARGET_HITBOX_DEBUG = false; // output
+	// hit box
+	// calcs
+	public static final boolean PRINT_INCOMING_OPCODES = false; // print
+	// incoming
+	// opcodes to
+	// console
+
+	public static final int BANK_GOLD_LIMIT = 25000000;
+	public static final int PLAYER_GOLD_LIMIT = 10000000;
+	public static final int BUILDING_GOLD_LIMIT = 15000000;
+
+	public static final String VENDOR_FULL = "This vendor has no more gold to give.";
+	public static final boolean HEIGHTMAP_DEBUG = false;
+	public static final boolean FAST_LOAD = false; // skip loading mobs,
+	// buildings, npcs
+	public static final boolean FAST_LOAD_INIT = false; // skip loading mobs,
+	// buildings, npcs
+	/*
+	 * Login cache flags
+	 */
+	public static final boolean SKIP_CACHE_LOGIN = false; // skip caching														// login server
+	public static final boolean SKIP_CACHE_LOGIN_PLAYER = false; // skip caching															// on login
+	public static final boolean SKIP_CACHE_LOGIN_ITEM = false; // skip caching
+
+	/*
+	 * Logger
+	 */
+	public static final int bannerWidth = 80;
+	public static final int typeWidth = 10;
+	public static final int originWidth = 25;
+	public static final int logWidth = 80;
+
+	/*
+	 * ConfigSystem related
+	 */
+	public static final String DEFAULT_CONFIG_DIR = "mb.conf/";
+	public static final String DEFAULT_DATA_DIR = "mb.data/";
+	/*
+	 * ChatManager related
+	 */
+	public static final int SHOUT_PERCEPTION_RADIUS_MOD = 2;
+	/*
+	 * DevCmd related
+	 */
+	public static final String DEV_CMD_PREFIX = "./";
+
+	/*
+	 * JobManager related
+	 */
+
+	// The number of elements in INITIAL_WORKERS defines the initial number of
+	// job pools
+	public static final int[] INITIAL_JOBPOOL_WORKERS = { 4, 2, 1 };
+	public static final int DEFAULT_JOBPOOL_WORKERS = 1;
+	public static final int DEFAULT_LOGIN_JOBPOOL_WORKERS = 5;
+
+	public static final int JOBWORKER_IDLE_TIMEOUT_MS = 750;
+	public static final int JOBMANAGER_INTERNAL_MONITORING_INTERVAL_MS = 1000;
+	public static final int JOB_STALL_THRESHOLD_MS = 120 * 1000;
+	public static final int MAX_JOB_HISTORY_OBJECTS = 1000; // max number of
+	// historic jobs to
+	// store on queue
+	// after execution
+	public static final int JOBSTATISTICS_WAKE_INTERVAL_MS = 500; // wake up and
+	// gather
+	// job stats
+	// every X
+	// ms,
+	// decrease
+	// this is
+	// we blow
+	// the job
+	// history
+	// queue
+
+	public static final int SCHEDULER_INITIAL_CAPACITY = 1000;
+	public static final int SCHEDULER_EXECUTION_TIME_COMPENSATION = 16;
+
+	/*
+	 * Concurrent Hash Map - Defaults
+	 */
+
+	public static final int CHM_INIT_CAP = 10;
+	public static final float CHM_LOAD = 0.75f;
+	public static final int CHM_THREAD_HIGH = 4;
+	public static final int CHM_THREAD_MED = 2;
+	public static final int CHM_THREAD_LOW = 1;
+
+	/*
+	 * LoginServer related
+	 */
+
+	public static final String PCMajorVer = "1.2.25.5";
+	public static final String PCMinorVer = "5.25.5";
+
+	public static final String MACMajorVer = "1.2.24.3";
+	public static final String MACMinorVer = "5.24.3";
+
+	/*
+	 * LoginErrorMsg related
+	 */
+	public static final int LOGINERROR_INVALID_USERNAME_PASSWORD = 1;
+	public static final int LOGINERROR_ACCOUNT_SUSPENDED = 2;
+
+	/*
+	 * Message is Version:
+	 */
+	public static final int LOGINERROR_INCORRECT_CLIENT_VERSION = 3;
+	public static final int LOGINERROR_NOT_ALLOWED_TO_LOGIN_YET = 4;
+
+	/*
+	 * Message is 'Error ='
+	 */
+	public static final int LOGINERROR_LOGINSERVER_IS_UNAVAILABLE = 5;
+	public static final int LOGINERROR_INVALID_ADMIN_USERNAME_PASSWORD = 6;
+	public static final int LOGINERROR_NO_MORE_PLAYTIME_ON_ACCOUNT = 7;
+	public static final int LOGINERROR_ACCOUNT_DOESNT_HAVE_SUBSCRIPTION = 8;
+	public static final int LOGINERROR_ACCOUNT_INSECURE_CHANGE_PASSWORD = 9;
+	public static final int LOGINERROR_TOO_MANY_LOGIN_TRIES = 10;
+
+	/*
+	 * Message is 'Error ='
+	 */
+	public static final int LOGINERROR_NOMOREPLAYTIME = 7;
+	public static final int LOGINERROR_INACTIVE = 8;
+	public static final int LOGINERROR_UNABLE_TO_LOGIN = 11;
+	public static final int LOGINERROR_LOGINSERVER_BUSY = 12;
+	public static final int LOGINERROR_BLANK = 13;
+
+	/*
+	 * >13 = 'blank' 12 = 'Login Server is currently busy, please try again in a
+	 * few minutes.' 11 = 'Unable to login. Please try again. If this problem
+	 * persists, contact customer support (error = )' 10 = 'You have made too
+	 * many unsuccessful login attempts, you must wait 15 minutes.' 9 = 'Your
+	 * Account is insecure, you must change your password before logging in
+	 * again.' 8 = 'This Account does not have an active Shadowbane
+	 * Subscription' 7 = 'No More PlayTime on this account 6 = 'Invalid
+	 * Administrator Username/Password' 5 = 'Login Server Is Unavailable (Error
+	 * = 0)' 4 = 'YouAreNotAllowedToLoginYet' 3 = 'Incorrect ClientVersion,
+	 * latest is' 2 = 'This Account Has Been Suspended' 1 = 'Invalid
+	 * Username/Password'
+	 */
+
+	/*
+	 * Name Validation Related
+	 */
+	public static final int INVALIDNAME_FIRSTNAME_MUST_BE_LONGER = 1;
+	public static final int INVALIDNAME_FIRSTANDLAST_MUST_BE_SHORTER = 2;
+	public static final int INVALIDNAME_FIRSTNAME_MUST_NOT_HAVE_SPACES = 3;
+	public static final int INVALIDNAME_FIRSTNAME_INVALID_CHARACTERS = 4;
+	public static final int INVALIDNAME_PLEASE_CHOOSE_ANOTHER_FIRSTNAME = 5;
+	public static final int INVALIDNAME_PLEASE_CHOOSE_ANOTHER_LASTNAME = 7;
+	public static final int INVALIDNAME_LASTNAME_UNAVAILABLE = 8;
+	public static final int INVALIDNAME_FIRSTNAME_UNAVAILABLE = 9;
+	public static final int INVALIDNAME_WRONG_WORLD_ID = 10;
+	public static final int INVALIDNAME_GENERIC = 11;
+
+	/*
+	 * 1: A first name of at least 3 character(s) must be entered 2: Your first
+	 * and last name cannot be more than 15 characters each. 3: Your first name
+	 * may not contain spaces 4: There are invalid characters in the first name.
+	 * 5: Please choose another first name 7: Please choose another last name 8:
+	 * That last name is unavailable 9: That first name is unavailable 10: Your
+	 * client sent an invalid world id 11: Invalid name. Choose another
+	 */
+	public static final int MIN_NAME_LENGTH = 3;
+	public static final int MAX_NAME_LENGTH = 15;
+
+	/*
+	 * ClientConnection related
+	 */
+	public static final boolean TCP_NO_DELAY_DEFAULT = true;
+	public static final byte MAX_CRYPTO_INIT_TRIES = 10;
+	
+
+	/*
+	 * EmuConnectionManager related
+	 */
+	public static final long delayBetweenConnectionChecks = 5000L; // in ms
+	public static final long delayBetweenReconnectAttempts = 2000L; // in ms
+	public static final int maxReconnectAttempts = 20;
+	public static final long reconnectTimeout = 15000L;
+	public static boolean DEBUG_PROTOCOL = false;
+	/*
+	 * Account Related
+	 */
+
+	public static final byte MAX_LOGIN_ATTEMPTS = 5;
+    public static final int RESET_LOGIN_ATTEMPTS_AFTER = (15 * 60 * 1000); // in
+    // ms
+    public static final int MAX_ACTIVE_GAME_ACCOUNTS_PER_DISCORD_ACCOUNT = 4; // 0
+	// to
+	// disable
+	/*
+	 * Character related
+	 */
+
+	public static final byte MAX_NUM_OF_CHARACTERS = 7;
+
+	public static final int STAT_STR_ID = 0x8AC3C0E6;
+	public static final int STAT_SPI_ID = 0xACB82E33;
+	public static final int STAT_CON_ID = 0xB15DC77E;
+	public static final int STAT_DEX_ID = 0xE07B3336;
+	public static final int STAT_INT_ID = 0xFF665EC3;
+
+	/*
+	 * Skill attributeIDs
+	 */
+
+	public static final int SKILL_RUNNING = 5;
+
+	/*
+	 * EquipSlot
+	 */
+
+	public static final int SLOT_UNEQUIPPED = 0;
+	public static final int SLOT_MAINHAND = 1;
+	public static final int SLOT_OFFHAND = 2;
+	public static final int SLOT_HELMET = 3;
+	public static final int SLOT_CHEST = 4;
+	public static final int SLOT_ARMS = 5;
+	public static final int SLOT_GLOVES = 6;
+	public static final int SLOT_RING1 = 7;
+	public static final int SLOT_RING2 = 8;
+	public static final int SLOT_NECKLACE = 9;
+	public static final int SLOT_LEGGINGS = 10;
+	public static final int SLOT_FEET = 11;
+	public static final int SLOT_HAIRSTYLE = 18; // 17 & 18? Weird.
+	public static final int SLOT_BEARDSTYLE = 17; // 17 & 18? Weird.
+
+	// Equip[0] = Slot1 = Weapon MainHand
+	// Equip[1] = Slot2 = OffHand
+	// Equip[2] = Slot3 = Helmet
+	// Equip[3] = Slot4 = Chest
+	// Equip[4] = Slot5 = Arms
+	// Equip[5] = Slot6 = Gloves
+	// Equip[6] = Slot7 = Ring1
+	// Equip[7] = Slot8 = Ring2
+	// Equip[8] = Slot9 = Necklace
+	// Equip[9] = Slot10 = Leggings
+	// Equip[10] = Slot11 = Feet
+	// Equip[11] = Slot17 = HairStyle
+	// Equip[12] = Slot18 = BeardStyle
+
+	/*
+	 * Group Formation Names
+	 */
+	public static final String[] FORMATION_NAMES = { "Column", "Line", "Box",
+			"Triangle", "Circle", "Ranks", "Wedge", "Inverse Wedge", "T" };
+
+	/*
+	 * Runes
+	 */
+
+	public static final int RUNETYPE_TRAIT = 1;
+
+	public static final int RUNE_COST_ATTRIBUTE_ID = 0;
+
+	public static final int RUNE_STR_ATTRIBUTE_ID = 1;
+	public static final int RUNE_DEX_ATTRIBUTE_ID = 2;
+	public static final int RUNE_CON_ATTRIBUTE_ID = 3;
+	public static final int RUNE_INT_ATTRIBUTE_ID = 4;
+	public static final int RUNE_SPI_ATTRIBUTE_ID = 5;
+
+	public static final int RUNE_STR_MAX_ATTRIBUTE_ID = 6;
+	public static final int RUNE_DEX_MAX_ATTRIBUTE_ID = 7;
+	public static final int RUNE_CON_MAX_ATTRIBUTE_ID = 8;
+	public static final int RUNE_INT_MAX_ATTRIBUTE_ID = 9;
+	public static final int RUNE_SPI_MAX_ATTRIBUTE_ID = 10;
+
+	public static final int RUNE_STR_MIN_NEEDED_ATTRIBUTE_ID = 11;
+	public static final int RUNE_DEX_MIN_NEEDED_ATTRIBUTE_ID = 12;
+	public static final int RUNE_CON_MIN_NEEDED_ATTRIBUTE_ID = 13;
+	public static final int RUNE_INT_MIN_NEEDED_ATTRIBUTE_ID = 14;
+	public static final int RUNE_SPI_MIN_NEEDED_ATTRIBUTE_ID = 15;
+
+	/*
+	 * DBMan
+	 */
+	public static final int NO_DB_ROW_ASSIGNED_YET = Integer.MAX_VALUE;
+
+	/*
+	 * PreparedStatement query debugging
+	 */
+	public static final boolean DB_DEBUGGING_ON_BY_DEFAULT = false; // warning:
+	// not
+	// recommended
+	// for a
+	// live
+	// production
+	// server
+	public static final boolean ENABLE_QUERY_TIME_WARNING = true;
+	public static final boolean ENABLE_UPDATE_TIME_WARNING = true;
+	public static final boolean ENABLE_EXECUTION_TIME_WARNING = true;
+
+	/*
+	 * ClientEncryption
+	 */
+	public static final int AUTHENTICATION_WAIT_TIMEOUT = 1000 * 2; // seconds
+	public static final int MaxGetKeyFromClientTries = 4;
+	public static final int MaxProtocolMessagesPerSecond = 20;  // 60 per second
+
+	/*
+	 * Guild Colors
+	 */
+
+	// public static final int GUILD_COLOR_LIGHTGREEN = 0;
+	// public static final int GUILD_COLOR_GREEN = 1;
+	// public static final int GUILD_COLOR_DARKGREEN = 2;
+	// public static final int GUILD_COLOR_LIGHTBLUE = 3;
+	// public static final int GUILD_COLOR_BLUE = 4;
+	// public static final int GUILD_COLOR_DARKBLUE = 5;
+	// public static final int GUILD_COLOR_PURPLE = 6;
+	// public static final int GUILD_COLOR_DARKRED = 7;
+	// public static final int GUILD_COLOR_LIGHTRED = 8;
+	// public static final int GUILD_COLOR_ORANGE = 9;
+	// public static final int GUILD_COLOR_BROWNORANGE = 10;
+	// public static final int GUILD_COLOR_BROWN = 11;
+	// public static final int GUILD_COLOR_BROWNYELLOW = 12;
+	// public static final int GUILD_COLOR_YELLOW = 13;
+	// public static final int GUILD_COLOR_LIGHTGREY = 14;
+	// public static final int GUILD_COLOR_GREY = 15;
+	// public static final int GUILD_COLOR_DARKGREY = 16;
+	// public static final int GUILD_COLOR_BLACK = 17;
+	// public static final int GUILD_COLOR_BLUEGREEN = 18;
+	// public static final int GUILD_COLOR_WHITE = 19;
+
+	/*
+	 * Timeout Related
+	 */
+	public static final int AFK_TIMEOUT_MS = (30 * 60 * 1000) * 100; // Added
+	// *100
+	// to
+	// discount
+	// it as
+	// a
+	// "random DC reason"
+	public static final int KEEPALIVE_TIMEOUT_MS = (2 * 60 * 1000)
+			+ (15 * 1000);
+	public static final int TIMEOUT_CHECKS_TIMER_MS = (60 * 1000);
+
+	/*
+	 * Masks for Quad Tree. Masks should be multiple of 2.
+	 */
+
+	public static final int MASK_PLAYER = 1;
+	public static final int MASK_MOB = 2;
+	public static final int MASK_PET = 4;
+	public static final int MASK_CORPSE = 8;
+	public static final int MASK_BUILDING = 16;
+	public static final int MASK_UNDEAD = 64;
+	public static final int MASK_BEAST = 128;
+	public static final int MASK_HUMANOID = 256;
+	public static final int MASK_NPC = 512;
+	public static final int MASK_IAGENT = 2048;
+
+	public static final int MASK_DRAGON = 4096;
+	public static final int MASK_RAT = 8192;
+	public static final int MASK_SIEGE = 16384;
+	public static final int MASK_CITY = 32768;
+	public static final int MASK_ZONE = 65536;
+
+	/*
+	 * Combined QT Masks. For convenience
+	 */
+
+	public static final int MASK_AGGRO = 5; // Player, Pet
+	public static final int MASK_MOBILE = 7; // Player, Mob, Pet
+	public static final int MASK_STATIC = 568; // Corpse, Building, Trigger, NPC
+
+	/*
+	 * World Coordinate Data
+	 */
+	public static final double MAX_WORLD_HEIGHT = -98304.0;
+	public static final double MAX_WORLD_WIDTH = 131072.0;
+	public static final float SEA_FLOOR_ALTITUDE = -1000f;
+	public static int SPATIAL_HASH_BUCKETSX = 16384;
+	public static int SPATIAL_HASH_BUCKETSY = 12288;
+	public static float MAX_PLAYER_X_LOC = 129999;
+	public static float MAX_PLAYER_Y_LOC = -97000;
+	public static String NO_DELETE_COMBAT =  "Can't delete items when in Combat with another player.";
+
+	/*
+	 * Rates
+	 */
+
+	public static float EXP_RATE_MOD = 2f; // Probably don't want to declare
+	// as final.
+	public static float GOLD_RATE_MOD = 1.0f; // Probably don't want to declare
+	// as final.
+	public static float DROP_RATE_MOD = 1.0f; // Probably don't want to declare
+	// as final.
+
+	// Hotzones
+	public static float HOT_EXP_RATE_MOD = 2.0f; // Probably don't want to
+	// declare as final.
+	public static float HOT_GOLD_RATE_MOD = 1.5f; // Probably don't want to
+	// declare as final.
+	public static float HOT_DROP_RATE_MOD = 1.8f; // Probably don't want to
+	// declare as final.
+
+	/*
+	 * Ranges
+	 */
+	public static final int CHARACTER_LOAD_RANGE = 400; // load range of mobile objects
+	// (default: 300)
+	public static final int STRUCTURE_LOAD_RANGE = 700; // load range of
+	// (default: 600)
+
+	public static float LOOT_RANGE = 100;
+	public static final int EXP_RANGE = 400;
+	public static final int GOLD_SPLIT_RANGE = 600;
+	// non-moving objects
+	public static final int SAY_RANGE = 200;
+	public static final int SHOUT_RANGE = 300;
+	public static final int STATIC_THRESHOLD = 75; // Range must travel before
+	// reloading statics
+	public static final int FORMATION_RANGE = 75; // Max Distance a player can
+	// be from group lead on
+	// formation move
+	public static final int OPENCLOSEDOORDISTANCE = 128; // Max distance a
+	public static final int DOOR_CLOSE_TIMER = 30000; // 30 seconds
+	// player can be from a door in order to toggle its state
+	public static final int TRADE_RANGE = 10; // Max distance a player can be
+	// from another player to trade
+	public static final int NPC_TALK_RANGE = 20; // Range player can be to talk
+	// to npc
+	public static final int MAX_TELEPORT_RANGE = 1020; // Max range teleports
+	// will work at
+	public static final int RANGED_WEAPON_RANGE = 35; // any weapon attack
+	// range beyond this
+	// is ranged.
+	public static final int CALL_FOR_HELP_RADIUS = 100; // Range mobs will
+	// respond to calls
+	// for help
+
+	public static final int TREE_TELEPORT_RADIUS = 30;
+
+	public static float MOB_SPEED_WALK = 6.5f;
+
+	public static float MOB_SPEED_WALKCOMBAT = 4.4f;
+
+	public static float MOB_SPEED_RUN = 14.67f;
+
+	public static float MOB_SPEED_RUNCOMBAT = 14.67f;
+
+
+	/*
+	 * Noob Island Start Location for new players
+	 */
+
+	public static final int[] DEFAULTGRID = {-1,1}; 
+	public static final float startX = 19128;// 70149f; //19318.0f;
+	public static final float startY = 94f; // 94f;
+	public static final float startZ = -73553; // -73661.0f;
+	public static final Vector3fImmutable DEFAULT_START = new Vector3fImmutable(
+			MBServerStatics.startX, MBServerStatics.startY,
+			MBServerStatics.startZ);
+
+	/*
+	 * Base movement speeds. Do NOT modify these. They must match the client
+	 */
+	public static final float FLYWALKSPEED = 6.33f;
+	public static final float FLYRUNSPEED = 18.38f;
+	public static final float SWIMSPEED = 6.5f;
+	public static final float WALKSPEED = 6.5f;
+	public static final float RUNSPEED = 14.67f;
+	public static final float COMBATWALKSPEED = 4.44f;
+	public static final float COMBATRUNSPEED = 14.67f;
+	public static final float RUNSPEED_MOB = 15.4f;
+
+	public static final float MOVEMENT_DESYNC_TOLERANCE = 2f; // Distance out of
+	public static String ITEMNOTINVENTORY = "Item must be in your inventory.";
+	public static String ZEROITEM = "This item has zero quantity.";
+	// sync with
+	// client can be
+	// before
+	// generating
+	// debug
+	// messages
+	// max units a player can desync before the server stops forcing
+	// client->server sync
+	public static final float MOVEMENT_MAX_DESYNC = 1000;
+
+	public static final int IGNORE_LIST_MAX = 60;
+
+	public static final float NO_WEAPON_RANGE = 8f; // Range for attack with no
+	// weapon
+
+	
+	public static final float REGEN_IDLE = .06f;
+	/*
+	 * Base regen rates. Do NOT modify these. They must match the client %per
+	 * second for health/mana. x per second for stamina.
+	 */
+	public static final float HEALTH_REGEN_SIT = 0.0033333f; // 100% in 3
+	// minutes
+	public static final float HEALTH_REGEN_IDLE = 0.000666667f; // 100% in 25
+	// minutes
+	public static final float HEALTH_REGEN_WALK = 0.0005f; // 100% in 33.33
+	// minutes
+	public static final float HEALTH_REGEN_RUN = 0f;
+	public static final float HEALTH_REGEN_SWIM_NOSTAMINA = -.03f; // 100% in
+	// 33.33
+	// seconds.
+	// Needs
+	// verified
+
+	public static final float HEALTH_REGEN_SIT_STATIC = 0.33333f; // 100% in 3
+	// minutes
+	public static final float HEALTH_REGEN_IDLE_STATIC = 0.0666667f; // 100% in
+	// 25
+	// minutes
+	public static final float HEALTH_REGEN_WALK_STATIC = 0.05f; // 100% in 33.33
+	// minutes
+	public static final float HEALTH_REGEN_RUN_STATIC = 0f;
+	public static final float HEALTH_REGEN_SWIM_NOSTAMINA_STATIC = 0f; // 100%
+	
+	public static final float MANA_REGEN_STATIC = 0.16666666666666666666666666666667f;
+	// in 30
+	// seconds.
+	// Needs
+	// verified
+
+	public static final float MANA_REGEN_SIT = 0.008333333f; // 100% in 2
+	// minutes <=
+	// needs
+	// verified
+	public static final float MANA_REGEN_IDLE = 0.00166667f; // 100% in 10
+	// minutes <=
+	// needs
+	// verified
+	public static final float MANA_REGEN_WALK = 0.00125f; // 100% in 13.333
+	// minutes <= needs
+	// verified
+	public static final float MANA_REGEN_RUN = 0f;
+
+	public static final float STAMINA_REGEN_SIT = 2f; // 2 per second
+	public static final float STAMINA_REGEN_IDLE = 0.2f; // 1 per 5 seconds
+	public static final float STAMINA_REGEN_WALK = 0f;
+	public static final float STAMINA_REGEN_RUN_COMBAT = -0.6499999762f;
+	public static final float STAMINA_REGEN_RUN_NONCOMBAT = -0.400000006f;
+	public static final float STAMINA_REGEN_SWIM = -1f; // -1 per second
+	public static  float STAMINA_REGEN_FLY_IDLE = -2f; // needs verifying
+	public static  float STAMINA_REGEN_FLY_WALK = -1f; // needs verifying
+	public static  float STAMINA_REGEN_FLY_RUN = -1.400000006f; // needs verifying
+	public static  float STAMINA_REGEN_FLY_RUN_COMBAT = -1.6499999762f; // needs verifying
+
+	public static final int REGEN_SENSITIVITY_PLAYER = 250; // calc regen ever X
+	// ms
+	public static final int REGEN_SENSITIVITY_MOB = 1000; // calc regen ever X
+	// ms
+	/*
+	 * Tombstone type to show Tombstone (2022); Tombstone, Grave (2023);
+	 * Tombstone, Skull (2024);
+	 */
+	public static final int TOMBSTONE = 2024;
+	public static final int DEATH_SHROUD_DURATION = 1; // 3 minute death shroud
+	public static final int SAFE_MODE_DURATION = 1; // 3 minute safe mode
+
+	/*
+	 * Timers
+	 */
+	public static final int LOGOUT_TIMER_MS = 1000; // logout delay applied
+	// after the last
+	// aggressive action
+	public static final int CLEANUP_TIMER_MS = 15 * 60 * 1000; // Remove player
+	// from cache
+	// after 15
+	// minutes
+	public static final int CORPSE_CLEANUP_TIMER_MS = 15 * 60 * 1000; // Cleanup
+	// corpse
+	// in
+	// world
+	// after
+	// 15
+	// minutes
+	public static final int DEFAULT_SPAWN_TIME_MS = 3 * 60 * 1000; // 3 minute
+	// respawn
+	// on mobs
+	// default
+	public static final int SESSION_CLEANUP_TIMER_MS = 30 * 1000; // cleanup
+	// sessions
+	// for login
+	// 30
+	// seconds
+	// after
+	// logout
+	public static final int MOVEMENT_FREQUENCY_MS = 1000; // Update movement
+	// once every X ms
+	public static final int FLY_FREQUENCY_MS = 1000; // Update flight once every
+	
+	public static final float FLY_RATE = .0078f;
+	// x ms
+	public static final int HEIGHT_CHANGE_TIMER_MS = 125; // Time in ms to fly
+	// up or down 1 unit
+	public static final long OPCODE_HANDLE_TIME_WARNING_MS = 250L;
+	public static final long DB_QUERY_WARNING_TIME_MS = 250L;
+	public static final long DB_UPDATE_WARNING_TIME_MS = 250L;
+	public static final long DB_EXECUTION_WARNING_TIME_MS = 250L;
+	public static boolean DB_ENABLE_QUERY_OUTPUT = false;
+	public static final int SUMMON_MAX_WAIT = 18000; // 18 seconds to accept
+	// summons
+	public static final int THIRTY_SECONDS = 30000;
+	public static final int FOURTYFIVE_SECONDS = 45000;
+	public static final int ONE_MINUTE = 60000;
+	public static final int FIVE_MINUTES = 300000;
+	public static final int FIFTEEN_MINUTES = 900000;
+	public static final int THIRTY_MINUTES = 1800000;
+	public static final long TWENTY_FOUR_HOURS = 86400000;
+	public static final int LOAD_OBJECT_DELAY = 500; // long to wait to update
+	public static int IPLimit = 5000;
+	// group list after
+	// LoadChar
+	public static final int UPDATE_LINK_WORLD = 2500;
+	public static final int UPDATE_LINK_LOGIN = 2500;
+	public static final int TELEPORT_TIME_IN_SECONDS = 10;
+	public static final int REPLEDGE_TIME_IN_SECONDS = 0;
+	public static final int CHECK_DATABASE_UPDATES = 10000; // update database
+	// changes every 10
+	// seconds.
+	public static final int RUNEGATE_CLOSE_TIME = 30000; // runegate close timer
+	public static final long PLAYER_KILL_XP_TIMER = 60 * 60 * 1000; // 60
+	// minutes
+	// between
+	// grant xp
+	// on same
+	// target
+	public static final int UPDATE_GROUP_RATE = 10000; // Update group info
+	// every 10 seconds
+	public static float PLAYER_HATE_DELIMITER = 50; // reduces 50 hate a second
+	// while player idling.
+	public static float PLAYER_COMBAT_HATE_MODIFIER = 2;
+
+	/*
+	 * AI
+	 */
+
+	// The min distance from players at which the AI Manager feels safe to turn
+	// off a mob.
+	public static int AI_BASE_AGGRO_RANGE = 60;
+	public static int AI_DROP_AGGRO_RANGE = 60;
+	public static int AI_RECALL_RANGE = 400;
+	public static int AI_PULSE_MOB_THRESHOLD = 200;
+	public static int AI_THREAD_SLEEP = 1000;
+	public static int AI_PATROL_DIVISOR = 10;
+	public static int AI_POWER_DIVISOR = 20;
+	public static int AI_PET_HEEL_DISTANCE = 10;
+	public static int AI_PATROL_RADIUS = 60;
+	
+	public static float AI_MAX_ANGLE = 10f;
+
+	public static final int AI_PET_TIME_BETWEEN_JOB_TICKS_MS = 250;
+
+	// Pet Settings
+	public static final float PET_TELEPORT_DISTANCE = 600; // distance a pet
+	// teleports to
+	// player
+	public static final float PET_FOLLOW_DISTANCE = 10; // distance a pet starts
+	// moving towards owner
+	public static final float PET_REST_DISTANCE = 4; // distance a pet stops
+	// moving towards owner
+
+	/*
+	 * Combat
+	 */
+	public static final int COMBAT_SEND_DODGE = 20;
+	public static final int COMBAT_SEND_BLOCK = 21;
+	public static final int COMBAT_SEND_PARRY = 22;
+	public static final short LEVELCAP = 75;
+	public static final int LEVEL_CON_WHITE = 7;
+	public static final int RESPAWN_TIMER = 90 * 1000;
+	public static final int DESPAWN_TIMER = 12 * 1000;
+	public static final int DESPAWN_TIMER_WITH_LOOT = 90 * 1000;
+	public static final int DESPAWN_TIMER_ONCE_LOOTED = 5 * 1000;
+	public static final int MAX_COMBAT_HITBOX_RADIUS = 80;
+	public static final int PROC_CHANCE = 5; // %chance to proc
+	public static float PRODUCTION_TIME_MULTIPLIER = .5f;
+
+	/*
+	 * Mob loot -- gold calculations
+	 */
+	public static final String STRONGBOX_DELAY_STRING = "StrongboxSpam";
+	public static final String STRONGBOX_DELAY_OUTPUT = "You must wait 1 minute to do this again.";
+	public static final int BIG_SPAM_DELAY = 10000;
+	public static String BIG_SPAM_DELAY_STRING = "BIGSPAM";
+
+	public static final double GOLD_DROP_PERCENTAGE_CHANCE = 61d;
+	public static final double GOLD_DROP_MULTIPLIER_GLOBAL = 1.0d; // tweak all
+	// rates at
+	// once
+	public static final double GOLD_DROP_MULTIPLIER_HOTZONE = 2.0d;
+	public static final double GOLD_DROP_MULTIPLIER_MAELSTROM = 1.1d;
+	public static final double GOLD_DROP_MULTIPLIER_OBLIVION = 1.1d;
+	public static final double[] GOLD_DROP_MINIMUM_PER_MOB_LEVEL = { 450, 450,
+			450, 450, 450, // 0 - 4
+			450, 450, 450, 450, 450, // 5 - 9
+			450, 1000, 1000, 1000, 1000, // 10 - 14
+			1000, 1000, 1000, 1000, 1000, // 15 - 19
+			1000, 1000, 1000, 1000, 1000, // 20 - 24
+			2000, 2000, 2000, 2000, 2000, // 25 - 29
+			2000, 2000, 2000, 2000, 2000, // 30 - 34
+			2000, 2000, 2000, 2000, 2000, // 35 - 39
+			4000, 4000, 4000, 4000, 4000, // 40 - 44
+			4000, 4000, 4000, 4000, 4000, // 45 - 49
+			5000 // 50+
+	};
+	public static final double[] GOLD_DROP_MAXIMUM_PER_MOB_LEVEL = { 1000,
+			1000, 1000, 1000, 1000, // 0 - 4
+			1000, 1000, 1000, 1000, 1000, // 5 - 9
+			1000, 2500, 2500, 2500, 2500, // 10 - 14
+			2500, 2500, 2500, 2500, 2500, // 15 - 19
+			2500, 2500, 2500, 2500, 2500, // 20 - 24
+			4000, 4000, 4000, 4000, 4000, // 25 - 29
+			4000, 4000, 4000, 4000, 4000, // 30 - 34
+			4000, 4000, 4000, 4000, 4000, // 35 - 39
+			9000, 9000, 9000, 9000, 9000, // 40 - 44
+			9000, 9000, 9000, 9000, 9000, // 45 - 49
+			12000 // 50+
+	};
+
+    // DO NOT FINAL THESE FIELD!
+    public static Enum.AccountStatus accessLevel; // Min account level to login to server
+    public static boolean blockLogin = false;
+	public static boolean ENABLE_VAULT_FILL = false;
+	public static boolean ENABLE_MOB_LOOT = true;
+	public static boolean ENABLE_AUDIT_JOB_WORKERS = true;
+	public static boolean ENABLE_COMBAT_TARGET_HITBOX = true;
+
+	/*
+	 * Track Sensitivity
+	 */
+	// Rate that track arrow refreshes. When inside TRACK_ARROW_FAST_RANGE, use
+	// TRACK_ARROW_SENSITIVITY_FAST speed, otherwise use TRACK_ARROW_SENSITIVITY
+	// speed.
+	public static final float TRACK_ARROW_FAST_RANGE = 50f; // Range to go from
+	// Fast arrow to
+	// slow
+	public static final int TRACK_ARROW_SENSITIVITY = 1000; // Refresh track
+	// arrows every X ms
+	public static final int TRACK_ARROW_SENSITIVITY_FAST = 250; // Refresh track
+	// arrows every
+	// X ms
+
+	/*
+	 * Population breakpoints
+	 */
+	public static final int LOW_POPULATION = 100;
+	public static final int NORMAL_POPULATION = 500;
+	public static final int HIGH_POPULATION = 1000;
+	public static final int VERY_OVERPOPULATED_POPULATION = 3000;
+	public static final int FULL_POPULATION = 5000;
+
+	// Refresh sensetivities
+	public static final int TRACK_WINDOW_THRESHOLD = 1000; // max refresh once
+	// every 1 seconds.
+	public static final int WHO_WINDOW_THRESHOLD = 3000; // max refresh once
+	// every 3 seconds.
+	public static final int VENDOR_WINDOW_THRESHOLD = 2000; // max refresh once
+	// every 2 seconds.
+	public static final int PURCHASE_THRESHOLD = 500; // max refresh once every
+	// 0.5 seconds.
+	public static final int SELL_THRESHOLD = 100; // max refresh once every 0.1
+	// seconds.
+	public static final int MAX_PLAYER_LOAD_SIZE = 1000;
+
+	// Mine related
+	public static final int MINE_EARLY_WINDOW = 16; // 3pm
+	public static final int MINE_LATE_WINDOW = 0; // Midnight
+
+	// Race
+	public static final float RADIUS_ARACOIX = 0.68999999761581f;
+	public static final float RADIUS_MINOTAUR = 0.69960004091263f;
+	public static final float RADIUS_DWARF = 0;
+	public static final float RADIUS_HUMAN = 0;
+	public static final float RADIUS_NEPHILIM = 0;
+	public static final float RADIUS_AELFBORN = 0;
+    public static final float RADIUS_ELF = 0;
+    public static final float RADIUS_VAMPIRE = 0;
+    public static final float RADIUS_IREKEI = 0;
+    public static final float RADIUS_HALF_GIANT = 0;
+    public static final float RADIUS_SHADE = 0;
+    public static final float RADIUS_CENTAUR = 0.68999999761581f;
+
+    public static String JUNIOR = "Junior";
+    public static String VETERAN = "Veteran";
+    public static String ELITE = "Elite";
+
+    public static int worldMapID = Integer.parseInt(ConfigManager.MB_WORLD_MAPID.getValue());
+    public static int worldUUID = Integer.parseInt(ConfigManager.MB_WORLD_UUID.getValue());
+    public static Enum.AccountStatus worldAccessLevel = Enum.AccountStatus.valueOf(ConfigManager.MB_WORLD_ACCESS_LVL.getValue());
+}
diff --git a/src/engine/server/login/LoginServer.java b/src/engine/server/login/LoginServer.java
new file mode 100644
index 00000000..07da1326
--- /dev/null
+++ b/src/engine/server/login/LoginServer.java
@@ -0,0 +1,524 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.server.login;
+
+import com.zaxxer.hikari.HikariConfig;
+import com.zaxxer.hikari.HikariDataSource;
+import engine.Enum;
+import engine.gameManager.*;
+import engine.job.JobScheduler;
+import engine.jobs.CSessionCleanupJob;
+import engine.net.Network;
+import engine.net.client.ClientConnection;
+import engine.net.client.ClientConnectionManager;
+import engine.net.client.Protocol;
+import engine.net.client.msg.login.ServerStatusMsg;
+import engine.net.client.msg.login.VersionInfoMsg;
+import engine.objects.*;
+import engine.server.MBServerStatics;
+import engine.util.ByteUtils;
+import engine.util.ThreadUtils;
+import org.pmw.tinylog.Configurator;
+import org.pmw.tinylog.Level;
+import org.pmw.tinylog.Logger;
+import org.pmw.tinylog.labelers.TimestampLabeler;
+import org.pmw.tinylog.policies.StartupPolicy;
+import org.pmw.tinylog.writers.RollingFileWriter;
+
+import java.io.*;
+import java.net.InetAddress;
+import java.net.URL;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.time.LocalDateTime;
+import java.util.Iterator;
+
+import static java.lang.System.exit;
+
+public class LoginServer {
+
+
+    // Instance variables
+
+    private VersionInfoMsg versionInfoMessage;
+    public static HikariDataSource connectionPool = null;
+    public static int population = 0;
+    public static boolean worldServerRunning = false;
+    public static boolean loginServerRunning = false;
+
+    public static ServerStatusMsg serverStatusMsg = new ServerStatusMsg(0, (byte) 1);
+
+    // This is the entrypoint for the MagicBane Login Server when
+    // it is executed by the command line scripts.  The fun begins here!
+
+    public static void main(String[] args) {
+
+        LoginServer loginServer;
+
+        // Initialize TinyLog logger with our own format
+
+        Configurator.defaultConfig()
+                .addWriter(new RollingFileWriter("logs/login/login.txt", 30, new TimestampLabeler(), new StartupPolicy()))
+                .level(Level.DEBUG)
+                .formatPattern("{level} {date:yyyy-MM-dd HH:mm:ss.SSS} [{thread}] {class}.{method}({line}) : {message}")
+                .activate();
+
+        try {
+
+            // Configure the the Login Server
+
+            loginServer = new LoginServer();
+            ConfigManager.loginServer = loginServer;
+            ConfigManager.handler = new LoginServerMsgHandler(loginServer);
+
+            ConfigManager.serverType = Enum.ServerType.LOGINSERVER;
+
+            if (ConfigManager.init() == false) {
+              Logger.error("ABORT! Missing config entry!");
+              return;
+            }
+
+            // Start the Login Server
+
+            loginServer.init();
+            loginServer.exec();
+
+            exit(0);
+
+        } catch (Exception e) {
+            Logger.error(e);
+            e.printStackTrace();
+            exit(1);
+        }
+    }
+
+    // Mainline execution loop for the login server.
+
+    private void exec() {
+
+
+        LocalDateTime nextCacheTime = LocalDateTime.now();
+        LocalDateTime nextServerTime = LocalDateTime.now();
+        LocalDateTime nextDatabaseTime = LocalDateTime.now();
+
+        loginServerRunning = true;
+
+        while (true) {
+
+            // Invalidate cache for players driven by forum
+            // and stored procedure forum_link_pass()
+
+            try {
+
+                // Run cache routine right away if requested.
+
+                File cacheFile = new File("cacheInvalid");
+
+
+                if (cacheFile.exists() == true) {
+                    nextCacheTime = LocalDateTime.now();
+                    Files.deleteIfExists(Paths.get("cacheInvalid"));
+                }
+
+                if (LocalDateTime.now().isAfter(nextCacheTime)) {
+                    invalidateCacheList();
+                    nextCacheTime = LocalDateTime.now().plusSeconds(30);
+                }
+
+                if (LocalDateTime.now().isAfter(nextServerTime)) {
+                    checkServerHealth();
+                    nextServerTime = LocalDateTime.now().plusSeconds(1);
+                }
+
+                if (LocalDateTime.now().isAfter(nextDatabaseTime)) {
+                    String pop = SimulationManager.getPopulationString();
+                    Logger.info("Keepalive: " + pop);
+                    nextDatabaseTime = LocalDateTime.now().plusMinutes(30);
+                }
+
+                ThreadUtils.sleep(100);
+            } catch (Exception e) {
+                Logger.error(e);
+                e.printStackTrace();
+            }
+        }
+    }
+
+    // Constructor
+
+    public LoginServer() {
+
+    }
+
+    private boolean init() {
+
+        // Initialize Application Protocol
+
+        Protocol.initProtocolLookup();
+
+        // Configure the VersionInfoMsgs:
+
+        this.versionInfoMessage = new VersionInfoMsg(MBServerStatics.PCMajorVer,
+                MBServerStatics.PCMinorVer);
+
+        Logger.info("Initializing Database Pool");
+        initDatabasePool();
+
+        Logger.info("Initializing Database layer");
+        initDatabaseLayer();
+
+        Logger.info("Initializing Network");
+        Network.init();
+
+        Logger.info("Initializing Client Connection Manager");
+        initClientConnectionManager();
+
+        // instantiate AccountManager
+        Logger.info("Initializing SessionManager.");
+
+        // Sets cross server behavior
+        SessionManager.setCrossServerBehavior(0);
+
+        // activate powers manager
+        Logger.info("Initializing PowersManager.");
+        PowersManager.initPowersManager(false);
+
+        RuneBaseAttribute.LoadAllAttributes();
+        RuneBase.LoadAllRuneBases();
+        BaseClass.LoadAllBaseClasses();
+        Race.loadAllRaces();
+        RuneBaseEffect.LoadRuneBaseEffects();
+
+        Logger.info("Initializing Blueprint data.");
+        Blueprint.loadAllBlueprints();
+
+        Logger.info("Loading Kits");
+        DbManager.KitQueries.GET_ALL_KITS();
+
+        Logger.info("Initializing ItemBase data.");
+        ItemBase.loadAllItemBases();
+
+        Logger.info("Initializing Race data");
+        Enum.RaceType.initRaceTypeTables();
+        Race.loadAllRaces();
+
+        Logger.info("Initializing Errant Guild");
+        Guild.CreateErrantGuild();
+
+        Logger.info("Loading All Guilds");
+        DbManager.GuildQueries.GET_ALL_GUILDS();
+
+
+        Logger.info("***Boot Successful***");
+        return true;
+    }
+
+    private boolean initDatabaseLayer() {
+
+        // Try starting a GOM <-> DB connection.
+        try {
+
+            Logger.info("Configuring GameObjectManager to use Database: '"
+                    + ConfigManager.MB_DATABASE_NAME.getValue() + "' on "
+                    + ConfigManager.MB_DATABASE_ADDRESS.getValue() + ':'
+                    + ConfigManager.MB_DATABASE_PORT.getValue());
+
+            DbManager.configureDatabaseLayer();
+
+        } catch (Exception e) {
+            Logger.error(e.getMessage());
+            return false;
+        }
+
+        PreparedStatementShared.submitPreparedStatementsCleaningJob();
+
+        if (MBServerStatics.DB_DEBUGGING_ON_BY_DEFAULT) {
+            PreparedStatementShared.enableDebugging();
+        }
+
+        return true;
+    }
+
+    public void removeClient(ClientConnection conn) {
+        if (conn == null) {
+            Logger.info(
+                    "ClientConnection null in removeClient.");
+            return;
+        }
+        String key = ByteUtils.byteArrayToSafeStringHex(conn
+                .getSecretKeyBytes());
+
+        CSessionCleanupJob cscj = new CSessionCleanupJob(key);
+
+        JobScheduler.getInstance().scheduleJob(cscj,
+                MBServerStatics.SESSION_CLEANUP_TIMER_MS);
+    }
+
+    private void initClientConnectionManager() {
+
+        try {
+
+            String name = ConfigManager.MB_WORLD_NAME.getValue();
+
+            // Find publicIP address for use in worldserver response
+            // message.  Sending the client to an unroutable address
+            // doesn't work so well.
+
+                URL whatismyip = new URL("http://checkip.amazonaws.com");
+                BufferedReader in = new BufferedReader(new InputStreamReader(
+                        whatismyip.openStream()));
+                ConfigManager.MB_PUBLIC_ADDR.setValue(in.readLine());
+                Logger.info("Public address: " + ConfigManager.MB_PUBLIC_ADDR.getValue());
+
+            Logger.info("Magicbane network config: " + ConfigManager.MB_BIND_ADDR.getValue() + ":" + ConfigManager.MB_LOGIN_PORT.getValue());
+
+            InetAddress addy = InetAddress.getByName(ConfigManager.MB_BIND_ADDR.getValue());
+            int port = Integer.parseInt(ConfigManager.MB_LOGIN_PORT.getValue());
+
+            ClientConnectionManager connectionManager = new ClientConnectionManager(name + ".ClientConnMan", addy,
+                    port);
+            connectionManager.startup();
+
+        } catch (IOException e) {
+            Logger.error(e.toString());
+        }
+    }
+    /*
+     * message handlers (relay)
+     */
+
+    // ==============================
+    // Support Functions
+    // ==============================
+
+    public VersionInfoMsg getDefaultVersionInfo() {
+        return versionInfoMessage;
+    }
+
+    //this updates a server being up or down without resending the entire char select screen.
+    public void updateServersForAll(boolean isRunning) {
+
+        try {
+
+            Iterator<ClientConnection> i = SessionManager.getAllActiveClientConnections().iterator();
+
+            while (i.hasNext()) {
+
+                ClientConnection clientConnection = i.next();
+
+                if (clientConnection == null)
+                    continue;
+
+                Account ac = clientConnection.getAccount();
+
+                if (ac == null)
+                    continue;
+
+                boolean isUp = isRunning;
+
+
+                if (MBServerStatics.worldAccessLevel.ordinal() > ac.status.ordinal())
+                    isUp = false;
+
+                LoginServer.serverStatusMsg.setServerID(MBServerStatics.worldMapID);
+                LoginServer.serverStatusMsg.setIsUp(isUp ? (byte) 1 : (byte) 0);
+                clientConnection.sendMsg(LoginServer.serverStatusMsg);
+            }
+        } catch (Exception e) {
+            Logger.error(e);
+            e.printStackTrace();
+        }
+    }
+
+    public void checkServerHealth() {
+
+        // Check if worldserver is running
+
+        if (!isPortInUse(Integer.parseInt(ConfigManager.MB_WORLD_PORT.getValue()))) {
+            worldServerRunning = false;
+            population = 0;
+            updateServersForAll(worldServerRunning);
+            return;
+        }
+
+        // Worldserver is running and writes a polling file.
+        // Read the current population count from the server and
+        // update player displays accordingly.
+
+        worldServerRunning = true;
+        population = readPopulationFile();
+        updateServersForAll(worldServerRunning);
+
+    }
+
+    private void initDatabasePool() {
+
+        HikariConfig config = new HikariConfig();
+
+        config.setMaximumPoolSize(33); // (16 cores 1 spindle)
+
+        config.setJdbcUrl("jdbc:mysql://" + ConfigManager.MB_DATABASE_ADDRESS.getValue() +
+                ":" + ConfigManager.MB_DATABASE_PORT.getValue() + "/" +
+                      ConfigManager.MB_DATABASE_NAME.getValue());
+        config.setUsername(ConfigManager.MB_DATABASE_USER.getValue());
+        config.setPassword(ConfigManager.MB_DATABASE_PASS.getValue());
+        config.addDataSourceProperty("characterEncoding", "utf8");
+        config.addDataSourceProperty("cachePrepStmts", "true");
+        config.addDataSourceProperty("prepStmtCacheSize", "250");
+        config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048");
+
+        connectionPool = new HikariDataSource(config); // setup the connection pool
+
+        Logger.info("local database connection configured");
+    }
+
+    public void invalidateCacheList() {
+
+        int objectUUID;
+        String objectType;
+
+        try (Connection connection = connectionPool.getConnection();
+             PreparedStatement statement = connection.prepareStatement("SELECT * FROM `login_cachelist`");
+             ResultSet rs = statement.executeQuery()) {
+
+            while (rs.next()) {
+
+                objectUUID = rs.getInt("UID");
+                objectType = rs.getString("type");
+
+                Logger.info("INVALIDATED : " +  objectType + " UUID: " + objectUUID);
+
+                switch (objectType) {
+
+                    case "account":
+                        DbManager.removeFromCache(Enum.GameObjectType.Account, objectUUID);
+                        break;
+                    case "character":
+                        DbManager.removeFromCache(Enum.GameObjectType.PlayerCharacter, objectUUID);
+                        PlayerCharacter player = (PlayerCharacter) DbManager.getObject(Enum.GameObjectType.PlayerCharacter, objectUUID);
+                        PlayerCharacter.initializePlayer(player);
+                        player.getAccount().characterMap.replace(player.getObjectUUID(), player);
+                        Logger.info("Player active state is : " + player.isActive());
+                        break;
+                }
+
+            }
+
+        } catch (SQLException e) {
+            Logger.info(e.toString());
+        }
+
+        // clear the db table
+
+        try (Connection connection = connectionPool.getConnection();
+             PreparedStatement statement = connection.prepareStatement("DELETE FROM `login_cachelist`")) {
+
+            statement.execute();
+
+        } catch (SQLException e) {
+            Logger.info(e.toString());
+        }
+
+
+    }
+
+    public static boolean getActiveBaneQuery(PlayerCharacter playerCharacter) {
+
+        boolean outStatus = false;
+
+        // char has never logged on so cannot have dropped a bane
+
+        if (playerCharacter.getHash() == null)
+            return outStatus;
+
+        // query data warehouse for unresolved bane with this character
+
+        try (Connection connection = connectionPool.getConnection();
+             PreparedStatement statement = buildQueryActiveBaneStatement(connection, playerCharacter);
+             ResultSet rs = statement.executeQuery()) {
+
+            while (rs.next()) {
+
+                outStatus = true;
+            }
+
+        } catch (SQLException e) {
+            Logger.error(e.toString());
+        }
+
+        return outStatus;
+    }
+
+    private static PreparedStatement buildQueryActiveBaneStatement(Connection connection, PlayerCharacter playerCharacter) throws SQLException {
+        PreparedStatement outStatement;
+        String queryString = "SELECT `city_id` FROM `warehouse_banehistory` WHERE `char_id` = ? AND `RESOLUTION` = 'PENDING'";
+        outStatement = connection.prepareStatement(queryString);
+        outStatement.setString(1, playerCharacter.getHash());
+        return outStatement;
+
+    }
+
+    public static boolean isPortInUse(int port) {
+
+        ProcessBuilder builder = new ProcessBuilder("/bin/bash", "-c", "lsof -i tcp:" + port + " | tail -n +2 | awk '{print $2}'");
+        builder.redirectErrorStream(true);
+        Process process = null;
+        String line = null;
+        boolean portInUse = false;
+
+        try {
+            process = builder.start();
+
+            InputStream is = process.getInputStream();
+            BufferedReader reader = new BufferedReader(new InputStreamReader(is));
+
+            while ((line = reader.readLine()) != null) {
+                portInUse = true;
+            }
+
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+
+        return portInUse;
+    }
+
+    private int readPopulationFile() {
+
+        ProcessBuilder builder = new ProcessBuilder("/bin/bash", "-c", "cat " + MBServerStatics.DEFAULT_DATA_DIR + ConfigManager.MB_WORLD_NAME.getValue().replaceAll("'","") + ".pop");
+        builder.redirectErrorStream(true);
+        Process process = null;
+        String line = null;
+        int population = 0;
+
+        try {
+
+            process = builder.start();
+
+            InputStream is = process.getInputStream();
+            BufferedReader reader = new BufferedReader(new InputStreamReader(is));
+
+            while ((line = reader.readLine()) != null) {
+                population = Integer.parseInt(line);
+            }
+
+        } catch (IOException e) {
+            e.printStackTrace();
+            return 0;
+        }
+
+        return population;
+    }
+
+}
diff --git a/src/engine/server/login/LoginServerMsgHandler.java b/src/engine/server/login/LoginServerMsgHandler.java
new file mode 100644
index 00000000..640baf8f
--- /dev/null
+++ b/src/engine/server/login/LoginServerMsgHandler.java
@@ -0,0 +1,444 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.server.login;
+
+import engine.Enum;
+import engine.Enum.DispatchChannel;
+import engine.Enum.GameObjectType;
+import engine.gameManager.DbManager;
+import engine.gameManager.SessionManager;
+import engine.job.JobScheduler;
+import engine.jobs.DisconnectJob;
+import engine.net.Dispatch;
+import engine.net.DispatchMessage;
+import engine.net.NetMsgHandler;
+import engine.net.client.ClientConnection;
+import engine.net.client.Protocol;
+import engine.net.client.msg.ClientNetMsg;
+import engine.net.client.msg.ServerInfoMsg;
+import engine.net.client.msg.login.*;
+import engine.objects.Account;
+import engine.objects.GuildStatusController;
+import engine.objects.PlayerCharacter;
+import engine.server.MBServerStatics;
+import engine.session.CSSession;
+import engine.session.Session;
+import engine.util.ByteUtils;
+import engine.util.StringUtils;
+import org.pmw.tinylog.Logger;
+
+public class LoginServerMsgHandler implements NetMsgHandler {
+
+    private final LoginServer server;
+
+    LoginServerMsgHandler(LoginServer server) {
+        super();
+        this.server = server;
+    }
+
+    /*
+     * =========================================================================
+     * Client Messages
+     * =========================================================================
+     */
+    @Override
+    public boolean handleClientMsg(ClientNetMsg clientNetMsg) {
+
+        if (clientNetMsg == null) {
+            Logger.error("Recieved null msg. Returning.");
+            return false;
+        }
+
+        ClientConnection origin = (ClientConnection) clientNetMsg.getOrigin();
+        Protocol protocolMsg = clientNetMsg.getProtocolMsg();
+
+        try {
+
+            switch (protocolMsg) {
+
+                case VERSIONINFO:
+                    this.VerifyCorrectClientVersion((VersionInfoMsg) clientNetMsg);
+                    break;
+
+                case LOGIN:
+                    if (LoginServer.loginServerRunning == true)
+                        this.Login((ClientLoginInfoMsg) clientNetMsg, origin);
+                    else
+                        this.KickToLogin(MBServerStatics.LOGINERROR_LOGINSERVER_BUSY, "", origin);
+                    break;
+
+                case KEEPALIVESERVERCLIENT:
+                    // echo the keep alive back
+                    origin.sendMsg(clientNetMsg);
+                    break;
+
+                case SELECTSERVER:
+                    this.SendServerInfo(origin);
+                    break;
+
+                case CREATECHAR:
+                    this.CommitNewCharacter((CommitNewCharacterMsg) clientNetMsg, origin);
+                    break;
+
+                case REMOVECHAR:
+                    this.DeleteCharacter((DeleteCharacterMsg) clientNetMsg, origin);
+                    break;
+
+                case SELECTCHAR:
+                    this.RequestGameServer((GameServerIPRequestMsg) clientNetMsg, origin);
+                    break;
+
+                case SETSELECTEDOBECT:
+                    // Why is this being sent to login server?
+                    break;
+
+                default:
+                    String ocHex = StringUtils.toHexString(protocolMsg.opcode);
+                    Logger.error("Cannot not handle Opcode: " + ocHex);
+                    return false;
+            }
+
+        } catch (Exception e) {
+            Logger.error("protocolMsg:" + protocolMsg + e.toString());
+            return false;
+        }
+
+        return true;
+    }
+
+    private void VerifyCorrectClientVersion(VersionInfoMsg vim) {
+        ClientConnection cc;
+        String cMajorVer;
+        String cMinorVer;
+        VersionInfoMsg outVim;
+
+        cc = (ClientConnection) vim.getOrigin();
+        cMajorVer = vim.getMajorVersion();
+        cMinorVer = vim.getMinorVersion();
+
+       if (!cMajorVer.equals(this.server.getDefaultVersionInfo().getMajorVersion())) {
+            this.KickToLogin(MBServerStatics.LOGINERROR_INCORRECT_CLIENT_VERSION, "Major Version Failure: " + cMajorVer, cc);
+            return;
+        }
+
+       /* if (!cMinorVer.equals(this.server.getDefaultVersionInfo().getMinorVersion())) {
+            this.KickToLogin(MBServerStatics.LOGINERROR_INCORRECT_CLIENT_VERSION, "Minor Version Failure: " + cMinorVer, cc);
+            return;
+        } */
+
+        if (cMinorVer == null) {
+            this.KickToLogin(MBServerStatics.LOGINERROR_INCORRECT_CLIENT_VERSION, "Minor Version Failure: ", cc);
+            return;
+        }
+
+        if (cMinorVer.length()  < 8 || cMinorVer.length()  > 16) {
+            this.KickToLogin(MBServerStatics.LOGINERROR_INCORRECT_CLIENT_VERSION, "Minor Version Failure: ", cc);
+            return;
+        }
+
+        // set MachineID for this connection
+
+        cc.machineID = cMinorVer;
+
+        //  send fake right back to the client
+        outVim = new VersionInfoMsg(vim.getMajorVersion(), this.server.getDefaultVersionInfo().getMinorVersion() );
+        cc.sendMsg(outVim);
+    }
+
+    // our data access should be in a separate object
+    private void Login(ClientLoginInfoMsg clientLoginInfoMessage, ClientConnection clientConnection) {
+
+        // Add zero length strings to eliminate the need for null checking.
+        String uname = clientLoginInfoMessage.getUname();
+        String pass = clientLoginInfoMessage.getPword();
+
+        // Check to see if there is actually any data in uname.pass
+        if (uname.length() == 0) {
+            this.KickToLogin(MBServerStatics.LOGINERROR_UNABLE_TO_LOGIN, "The username provided was zero length.", clientConnection);
+            return;
+        }
+
+        if (pass.length() == 0) {
+            this.KickToLogin(MBServerStatics.LOGINERROR_UNABLE_TO_LOGIN, "The password provided was zero length.", clientConnection);
+            return;
+        }
+
+        Account account;
+
+        account = DbManager.AccountQueries.GET_ACCOUNT(uname);
+
+        if (account == null) {
+
+            this.KickToLogin(MBServerStatics.LOGINERROR_INVALID_USERNAME_PASSWORD, "Could not find account (" + uname + ')', clientConnection);
+            Logger.info("Could not find account (" + uname + ')');
+            return;
+
+        }
+
+        if (account.getLastLoginFailure() + MBServerStatics.RESET_LOGIN_ATTEMPTS_AFTER < System.currentTimeMillis())
+            account.resetLoginAttempts();
+
+        // TODO: Log the login attempts IP, name, password and timestamp
+        // Check number invalid login attempts. If 5 or greater, kick to login.
+        if (account.getLoginAttempts() >= MBServerStatics.MAX_LOGIN_ATTEMPTS) {
+
+            this.KickToLogin(MBServerStatics.LOGINERROR_UNABLE_TO_LOGIN, "Too many login in attempts for '" + uname + '\'', clientConnection);
+            Logger.info("Too many login in attempts for '" + uname + '\'');
+            return;
+        }
+
+        if (account.lastPasswordCheck < System.currentTimeMillis()) {
+            account.lastPasswordCheck = System.currentTimeMillis() + MBServerStatics.ONE_MINUTE;
+        }
+
+        // Attempt to validate login
+        try {
+            if (!account.passIsValid(pass, clientConnection.getClientIpAddress(), clientConnection.machineID)) {
+
+                account.incrementLoginAttempts();
+                this.KickToLogin(MBServerStatics.LOGINERROR_INVALID_USERNAME_PASSWORD, "", clientConnection);
+                Logger.info("Incorrect password(" + uname + ')');
+                return;
+            }
+        } catch (IllegalArgumentException e1) {
+            this.KickToLogin(MBServerStatics.LOGINERROR_UNABLE_TO_LOGIN, "", clientConnection);
+            Logger.info("Failed forum account validation(" + uname + ')');
+        }
+
+        // Account deactivated
+
+        if (account.status.equals(Enum.AccountStatus.BANNED)) {
+            this.KickToLogin(MBServerStatics.LOGINERROR_NO_MORE_PLAYTIME_ON_ACCOUNT, "", clientConnection);
+            return;
+        }
+
+        // Check to see if we have a Session mapped with this Account:
+        Session session = SessionManager.getSession(account);
+
+        // If there is, then the account is in use and must be handled:
+        // kick the 'other connection'
+        if (session != null)
+            this.KickToLogin(MBServerStatics.LOGINERROR_UNABLE_TO_LOGIN, "Your account has been accessed from a different IP & Port.", session.getConn()); // Logout the character
+
+
+        // TODO implement character logout
+        // Get a new session
+        session = SessionManager.getNewSession(account, clientConnection);
+
+        // Set Invalid Login Attempts to 0
+        account.resetLoginAttempts();
+
+        // Send Login Response
+        ClientLoginInfoMsg loginResponse = new ClientLoginInfoMsg(clientLoginInfoMessage);
+        loginResponse.setUnknown06(8323072);
+        loginResponse.setUnknown07(3276800);
+        loginResponse.setUnknown08(196608);
+        loginResponse.setUnknown09((short) 15);
+
+        clientConnection.sendMsg(loginResponse);
+
+        // send character select screen
+        try {
+            this.sendCharacterSelectScreen(session);
+        } catch (Exception e) {
+            Logger.error("Unable to Send Character Select Screen to client");
+            this.KickToLogin(MBServerStatics.LOGINERROR_UNABLE_TO_LOGIN, "Unable to send Character Select Screen to client.", clientConnection);
+            return;
+        }
+
+        // Logging
+        String addyPort = clientConnection.getRemoteAddressAndPortAsString();
+        int id = account.getObjectUUID();
+
+        Logger.info(uname + '(' + id + ") has successfully logged in from " + addyPort);
+
+    }
+
+    private void KickToLogin(int errCode, String message, ClientConnection origin) {
+        LoginErrorMsg msg = new LoginErrorMsg(errCode, message);
+
+        PlayerCharacter player = origin.getPlayerCharacter();
+
+        if (player == null) {
+            origin.sendMsg(msg);
+        } else {
+            Dispatch dispatch = Dispatch.borrow(player, msg);
+            DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.PRIMARY);
+        }
+
+
+        Logger.info("Kicking to Login. Message: '" + message + '\'');
+
+        DisconnectJob dj = new DisconnectJob(origin);
+        JobScheduler.getInstance().scheduleJob(dj, 250);
+    }
+
+    protected void sendCharacterSelectScreen(Session s) {
+        sendCharacterSelectScreen(s, false);
+    }
+
+    private void sendCharacterSelectScreen(Session s, boolean fromCommit) {
+
+        if (s.getAccount() != null) {
+            CharSelectScreenMsg cssm = new CharSelectScreenMsg(s, fromCommit);
+            s.getConn().sendMsg(cssm);
+        } else {
+            Logger.error("No Account Found: Unable to Send Character Select Screen");
+            this.KickToLogin(MBServerStatics.LOGINERROR_UNABLE_TO_LOGIN, "Unable to send Character Select Screen to client.", s.getConn());
+        }
+    }
+
+    private void SendServerInfo(ClientConnection conn) {
+        ServerInfoMsg sim = new ServerInfoMsg();
+
+        if (!conn.sendMsg(sim)) {
+            Logger.error("Failed to send message");
+
+            this.KickToLogin(MBServerStatics.LOGINERROR_UNABLE_TO_LOGIN, "Unable to send ServerInfoMsg to client.", conn);
+        }
+    }
+
+    private void CommitNewCharacter(CommitNewCharacterMsg commitNewCharacterMessage, ClientConnection clientConnection) {
+
+        Session session = SessionManager.getSession(clientConnection);
+
+        if (session.getAccount() == null)
+            return;
+
+        try {
+            // Check to see if there is an available slot.
+            if (session.getAccount().characterMap.size() >= MBServerStatics.MAX_NUM_OF_CHARACTERS) {
+                this.sendCharacterSelectScreen(session);
+                return;
+            }
+
+            PlayerCharacter pc = PlayerCharacter.generatePCFromCommitNewCharacterMsg(session.getAccount(), commitNewCharacterMessage, clientConnection);
+
+            if (pc == null) {
+                Logger.info("Player returned null while creating character.");
+                this.sendCharacterSelectScreen(session, true);
+                return;
+            }
+
+            PlayerCharacter.initializePlayer(pc);
+            session.getAccount().characterMap.putIfAbsent(pc.getObjectUUID(), pc);
+            // Send back to Character Select Screen
+            this.sendCharacterSelectScreen(session, true);
+
+        } catch (Exception e) {
+            Logger.error(e);
+            this.sendCharacterSelectScreen(session, true);
+        }
+    }
+
+    public static void sendInvalidNameMsg(String firstName, String lastName, int errorCode, ClientConnection clientConnection) {
+
+        InvalidNameMsg invalidNameMessage;
+
+        if (firstName.length() > 256 || lastName.length() > 256)
+            invalidNameMessage = new InvalidNameMsg(firstName, lastName, errorCode);
+        else
+            invalidNameMessage = new InvalidNameMsg(firstName, lastName, errorCode);
+
+        clientConnection.sendMsg(invalidNameMessage);
+    }
+
+    private void DeleteCharacter(DeleteCharacterMsg msg, ClientConnection origin) {
+
+        try {
+            PlayerCharacter player;
+            Session session;
+
+            session = SessionManager.getSession(origin);
+            player = (PlayerCharacter) DbManager.getObject(GameObjectType.PlayerCharacter, msg.getCharacterUUID());
+
+            if (player == null) {
+                Logger.error("Delete Error: PlayerID=" + msg.getCharacterUUID() + " not found.");
+                this.sendCharacterSelectScreen(session);
+                return;
+            }
+
+            if (session.getAccount() == null) {
+                Logger.error("Delete Error: Account not found.");
+                this.sendCharacterSelectScreen(session);
+                return;
+            }
+
+            if (player.getAccount() != origin.getAccount()) {
+                Logger.error("Delete Error: Character " + player.getName() + " does not belong to account " + origin.getAccount().getUname());
+                this.sendCharacterSelectScreen(session);
+                return;
+            }
+
+            //Can't delete as Guild Leader
+            //TODO either find an error or just gdisband.
+
+            if (GuildStatusController.isGuildLeader(player.getGuildStatus())) {
+                this.KickToLogin(MBServerStatics.LOGINERROR_UNABLE_TO_LOGIN, "Cannot delete a guild leader.", origin);
+                return;
+            }
+
+            // check for active banes
+
+            if (LoginServer.getActiveBaneQuery(player)) {
+                Logger.info("Character " + player.getName() + " has unresolved bane");
+                this.KickToLogin(MBServerStatics.LOGINERROR_UNABLE_TO_LOGIN, "Player has unresolved bane.", origin);
+                return;
+            }
+
+            player.getAccount().characterMap.remove(player.getObjectUUID());
+            player.deactivateCharacter();
+
+            // TODO Delete Equipment
+            // Resend Character Select Screen.
+            this.sendCharacterSelectScreen(session);
+
+        } catch (Exception e) {
+            Logger.error(e);
+        }
+    }
+
+    private void RequestGameServer(GameServerIPRequestMsg gameServerIPRequestMessage, ClientConnection conn) {
+
+        Session session;
+        PlayerCharacter player;
+
+        session = SessionManager.getSession(conn);
+        player = (PlayerCharacter) DbManager.getObject(GameObjectType.PlayerCharacter, gameServerIPRequestMessage.getCharacterUUID());
+
+        if (player == null) {
+            Logger.info("Unable to find character ID " + gameServerIPRequestMessage.getCharacterUUID());
+            this.KickToLogin(MBServerStatics.LOGINERROR_UNABLE_TO_LOGIN, "PlayerCharacter lookup failed in .RequestGameServer().", conn);
+            return;
+        }
+
+        try {
+            if (!CSSession.updateCrossServerSession(ByteUtils.byteArrayToSafeStringHex(conn.getSecretKeyBytes()), gameServerIPRequestMessage.getCharacterUUID())) {
+                Logger.info("Failed to update Cross server session, Kicking to Login for Character " + player.getObjectUUID());
+                this.KickToLogin(MBServerStatics.LOGINERROR_UNABLE_TO_LOGIN, "Failed to update Session Information", conn);
+                return;
+            }
+        } catch (Exception e) {
+            Logger.info("Failed to update Cross server session, Kicking to Login for Character " + player.getObjectUUID());
+            Logger.error(e);
+        }
+
+        // Set the last character used.
+        Account account = session.getAccount();
+        account.setLastCharIDUsed(gameServerIPRequestMessage.getCharacterUUID());
+
+        GameServerIPResponseMsg gsiprm = new GameServerIPResponseMsg();
+
+        if (!conn.sendMsg(gsiprm)) {
+            Logger.error("Failed to send message");
+            this.KickToLogin(MBServerStatics.LOGINERROR_UNABLE_TO_LOGIN, "Unable to send GameServerIPResponseMsg to client.", conn);
+        }
+    }
+}
diff --git a/src/engine/server/world/WorldServer.java b/src/engine/server/world/WorldServer.java
new file mode 100644
index 00000000..371b44f5
--- /dev/null
+++ b/src/engine/server/world/WorldServer.java
@@ -0,0 +1,861 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+
+package engine.server.world;
+
+import engine.Enum;
+import engine.Enum.BuildingGroup;
+import engine.Enum.DispatchChannel;
+import engine.Enum.MinionType;
+import engine.Enum.SupportMsgType;
+import engine.InterestManagement.HeightMap;
+import engine.InterestManagement.RealmMap;
+import engine.InterestManagement.WorldGrid;
+import engine.ai.MobileFSMManager;
+import engine.db.archive.DataWarehouse;
+import engine.exception.MsgSendException;
+import engine.gameManager.*;
+import engine.job.JobContainer;
+import engine.job.JobScheduler;
+import engine.jobs.LogoutCharacterJob;
+import engine.jobs.MineActiveJob;
+import engine.loot.LootManager;
+import engine.net.Dispatch;
+import engine.net.DispatchMessage;
+import engine.net.ItemProductionManager;
+import engine.net.Network;
+import engine.net.client.ClientConnection;
+import engine.net.client.ClientConnectionManager;
+import engine.net.client.ClientMessagePump;
+import engine.net.client.Protocol;
+import engine.net.client.msg.RefinerScreenMsg;
+import engine.net.client.msg.TrainerInfoMsg;
+import engine.net.client.msg.UpdateStateMsg;
+import engine.net.client.msg.chat.ChatSystemMsg;
+import engine.objects.*;
+import engine.server.MBServerStatics;
+import engine.util.ThreadUtils;
+import engine.workthreads.DisconnectTrashTask;
+import engine.workthreads.HourlyJobThread;
+import engine.workthreads.PurgeOprhans;
+import engine.workthreads.WarehousePushThread;
+import org.pmw.tinylog.Configurator;
+import org.pmw.tinylog.Level;
+import org.pmw.tinylog.Logger;
+import org.pmw.tinylog.labelers.TimestampLabeler;
+import org.pmw.tinylog.policies.StartupPolicy;
+import org.pmw.tinylog.writers.RollingFileWriter;
+
+import java.io.*;
+import java.net.InetAddress;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.time.LocalDateTime;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Timer;
+
+import static engine.gameManager.SimulationManager.SERVERHEARTBEAT;
+import static java.lang.System.exit;
+
+public class WorldServer {
+
+	private static LocalDateTime bootTime = LocalDateTime.now();
+	private static long lastHZChange = System.currentTimeMillis();
+	public boolean isRunning = false;
+
+	// Member variable declaration
+
+	public static HashMap<Integer,HashMap<Integer,ArrayList<Integer>>> ZoneFidelityMobRunes = new HashMap<>();
+
+	public WorldServer() {
+		super();
+	}
+
+	public static void main(String[] args) {
+
+		WorldServer worldServer;
+
+		// Configure TinyLogger
+		Configurator.defaultConfig()
+				.addWriter(new RollingFileWriter("logs/world/world.txt", 30, new TimestampLabeler(), new StartupPolicy()))
+				.level(Level.DEBUG)
+				.formatPattern("{level} {date:yyyy-MM-dd HH:mm:ss.SSS} [{thread}] {class}.{method}({line}) : {message}")
+				.writingThread("main", 2)
+				.activate();
+
+		if (ConfigManager.init() == false) {
+			Logger.error("ABORT! Missing config entry!");
+			return;
+		}
+
+		try {
+			
+			worldServer = new WorldServer();
+
+			ConfigManager.serverType = Enum.ServerType.WORLDSERVER;
+			ConfigManager.worldServer = worldServer;
+			ConfigManager.handler = new ClientMessagePump(worldServer);
+
+			worldServer.init();
+			
+			int retVal = worldServer.exec();
+
+			if (retVal != 0)
+				Logger.error(
+						".exec() returned value: '" + retVal);
+			exit(retVal);
+
+		} catch (Exception e) {
+			Logger.error(e.getMessage());
+			exit(1);
+		}
+	}
+
+	public static long getLastHZChange() {
+		return lastHZChange;
+	}
+
+	public static void setLastHZChange(long lastChange) {
+		lastHZChange = lastChange;
+	}
+
+	public static void trainerInfo(TrainerInfoMsg msg, ClientConnection origin) {
+
+		NPC npc = NPC.getFromCache(msg.getObjectID());
+		float sellPercent = 1;
+
+		if (npc != null){
+			
+			if (origin.getPlayerCharacter() != null)
+				sellPercent = npc.getSellPercent(origin.getPlayerCharacter());
+			else
+				sellPercent = npc.getSellPercent();
+			
+			msg.setTrainPercent(sellPercent); //TrainMsg.getTrainPercent(npc));
+		}
+
+		Dispatch dispatch = Dispatch.borrow(origin.getPlayerCharacter(), msg);
+		DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+
+	}
+
+	public static void refinerScreen(RefinerScreenMsg msg, ClientConnection origin)
+			throws MsgSendException {
+
+		NPC npc = NPC.getFromCache(msg.getNpcID());
+
+		if (npc != null)
+			msg.setUnknown02(0); //cost to refine?
+
+		Dispatch dispatch = Dispatch.borrow(origin.getPlayerCharacter(), msg);
+		DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
+	}
+
+	public static void shutdown() {
+		exit(1);
+	}
+
+	public static String getUptimeString() {
+		String outString = null;
+		java.time.Duration uptimeDuration;
+		String newLine = System.getProperty("line.separator");
+
+		try {
+			outString = "[LUA_UPTIME()]" + newLine;
+			uptimeDuration = java.time.Duration.between(LocalDateTime.now(), WorldServer.bootTime);
+			long uptimeSeconds = Math.abs(uptimeDuration.getSeconds());
+			String uptime =   String.format("%d hours %02d minutes %02d seconds", uptimeSeconds / 3600, (uptimeSeconds % 3600) / 60, (uptimeSeconds % 60));
+			outString += "uptime: " + uptime;
+			outString += " pop: " + SessionManager.getActivePlayerCharacterCount() + " max pop: " + SessionManager._maxPopulation;
+		} catch (Exception e) {
+			Logger.error("Failed to build string");
+		}
+		return outString;
+	}
+
+	private int exec() {
+
+		LocalDateTime nextHeartbeatTime = LocalDateTime.now();
+		LocalDateTime nextPopulationFileTime = LocalDateTime.now();
+		LocalDateTime nextFlashTrashCheckTime = LocalDateTime.now();
+		LocalDateTime nextHourlyJobTime = LocalDateTime.now().withMinute(0).withSecond(0).plusHours(1);
+		LocalDateTime nextWareHousePushTime = LocalDateTime.now();;
+
+		// Begin execution of main game loop
+
+		this.isRunning = true;
+
+		while (true) {
+
+			if (LocalDateTime.now().isAfter(nextHeartbeatTime)) {
+				SERVERHEARTBEAT.tick();
+				nextHeartbeatTime = LocalDateTime.now().plusNanos(50000000);
+			}
+
+			if (LocalDateTime.now().isAfter(nextPopulationFileTime)) {
+				writePopulationFile();
+				nextPopulationFileTime = LocalDateTime.now().plusMinutes(1);
+			}
+
+			if (LocalDateTime.now().isAfter(nextFlashTrashCheckTime)) {
+				processFlashFile();
+				processTrashFile();
+				nextFlashTrashCheckTime = LocalDateTime.now().plusSeconds(15);
+			}
+
+			if (LocalDateTime.now().isAfter(nextHourlyJobTime)) {
+				Thread hourlyJobThread = new Thread(new HourlyJobThread());
+				hourlyJobThread.setName("hourlyJob");
+				hourlyJobThread.start();
+				nextHourlyJobTime = LocalDateTime.now().withMinute(0).withSecond(0).plusHours(1);
+			}
+
+			if (LocalDateTime.now().isAfter(nextWareHousePushTime)) {
+				Thread warehousePushThread = new Thread(new WarehousePushThread());
+				warehousePushThread.setName("warehousePush");
+				warehousePushThread.start();
+				nextWareHousePushTime = LocalDateTime.now().plusMinutes(15);
+			}
+
+			ThreadUtils.sleep(50);
+		}
+	}
+
+	private void initClientConnectionManager() {
+
+		try {
+
+			String name = ConfigManager.MB_WORLD_NAME.getValue();
+
+			Logger.info("Magicbane network config: " + ConfigManager.MB_BIND_ADDR.getValue() + ":" + ConfigManager.MB_WORLD_PORT.getValue());
+
+			InetAddress addy = InetAddress.getByName(ConfigManager.MB_BIND_ADDR.getValue());
+			int port = Integer.parseInt(ConfigManager.MB_WORLD_PORT.getValue());
+
+			ClientConnectionManager connectionManager =  new ClientConnectionManager(name + ".ClientConnMan", addy,
+					port);
+			connectionManager.startup();
+
+		} catch (IOException e) {
+			Logger.error("Exception while creating a ClientConnectionManager.");
+		}
+	}
+
+	private boolean init() {
+
+		Logger.info("MAGICBANE SERVER GREETING:");
+		Logger.info(ConfigManager.MB_WORLD_GREETING.getValue());
+
+		Logger.info("Initialize network protocol");
+		Protocol.initProtocolLookup();
+
+		Logger.info("Initialize database layer");
+		initDatabaselayer();
+
+		Logger.info("Setting cross server session behavior");
+		SessionManager.setCrossServerBehavior(1); // Sets cross server behavior
+
+		Logger.info("Starting Item Production thread");
+		ItemProductionManager.ITEMPRODUCTIONMANAGER.startMessagePump();
+
+		Logger.info("Initializing Errant Guild");
+		Guild.CreateErrantGuild();
+
+		Logger.info("Initializing PowersManager.");
+		// activate powers manager
+		PowersManager.initPowersManager(true);
+		
+		Logger.info("Initializing granted Skills for Runes");
+		DbManager.SkillsBaseQueries.LOAD_ALL_RUNE_SKILLS();
+		
+		Logger.info("Initializing Player Friends");
+		DbManager.PlayerCharacterQueries.LOAD_PLAYER_FRIENDS();
+		
+		Logger.info("Initializing NPC Profits");
+		DbManager.NPCQueries.LOAD_NPC_PROFITS();
+		
+		Logger.info("Initializing MeshBounds");
+		MeshBounds.InitializeBuildingBounds();
+
+		// Load ItemBases
+		Logger.info("Loading ItemBases");
+		ItemBase.loadAllItemBases();
+		
+		Logger.info("Loading PromotionClasses");
+		DbManager.PromotionQueries.GET_ALL_PROMOTIONS();
+
+		Logger.info("Loading NPC and Mob Equipment Sets");
+		EquipmentSetEntry.LoadAllEquipmentSets();
+
+		Logger.info("Loading Gold Loot for Mobbases");
+		MobbaseGoldEntry.LoadMobbaseGold();
+
+		Logger.info("Loading fidelity mob runes.");
+		DbManager.MobQueries.LOAD_RUNES_FOR_FIDELITY_MOBS();
+
+		//load lootTable
+		Logger.info("Loading Loot Tables");
+		LootTable.populateLootTables();
+
+		// Load new loot system
+		Logger.info("Loading SuperLoot Tables");
+		LootManager.loadLootData();
+		RuneBaseAttribute.LoadAllAttributes();
+		RuneBase.LoadAllRuneBases();
+		BaseClass.LoadAllBaseClasses();
+		Race.loadAllRaces();
+		RuneBaseEffect.LoadRuneBaseEffects();
+
+		Logger.info("Loading MobBases.");
+		DbManager.MobBaseQueries.GET_ALL_MOBBASES();
+
+		//load item enchantment values
+		DbManager.LootQueries.LOAD_ENCHANT_VALUES();
+
+		//initialize realms
+		Logger.info("Loading Realms");
+		Realm.loadAllRealms();
+
+		Logger.info("Loading Kits");
+		DbManager.KitQueries.GET_ALL_KITS();
+		
+		Logger.info("Loading World Grid");
+		WorldGrid.InitializeGridObjects();
+		
+		Logger.info("Starting InterestManager.");
+		WorldGrid.startLoadJob();
+		
+		
+		Logger.info("Loading Spaital Hash");
+		RealmMap.loadRealmImageMap();
+
+		DbManager.MobBaseQueries.SET_AI_DEFAULTS();
+
+		Logger.info("Loading blueprint data.");
+		StaticColliders.loadAllStaticColliders();
+		BuildingRegions.loadAllStaticColliders();
+		Blueprint.loadAllDoorNumbers();
+		Blueprint.loadAllBlueprints();
+
+		Logger.info("Loading Special Loot For Mobs");
+		DbManager.SpecialLootQueries.GenerateSpecialLoot();
+
+		Logger.info("Initializing Heightmap data");
+		HeightMap.loadAlHeightMaps();
+
+		Logger.info("Loading Race data");
+		Enum.RaceType.initRaceTypeTables();
+		Race.loadAllRaces();
+
+		Logger.info("Loading building mountpoint data.");
+		BuildingLocation.loadAllLocations();
+
+		// Starting before loading of structures/guilds/characters
+		// so the database connections are available to write
+		// historical data.
+
+		Logger.info("Starting Data Warehouse");
+		DataWarehouse.bootStrap();
+
+		Logger.info("Loading Minion Bases.");
+		MinionType.InitializeMinions();
+
+		Logger.info("Loading Support Types");
+		SupportMsgType.InitializeSupportMsgType();
+
+		//Load Buildings, Mobs and NPCs for server
+
+		getWorldBuildingsMobsNPCs();
+
+		// Configure realms for serialization
+		// Doing this after the world is loaded
+
+		Logger.info("Configuring realm serialization data");
+
+		try{
+			Realm.configureAllRealms();
+		}catch(Exception e){
+			Logger.error( e.getMessage());
+		}
+
+		Logger.info("Loading Mine data.");
+		//DbManager.MineQueries.syncMineWindowsWithToday();
+		Mine.loadAllMines();
+
+		Logger.info("Loading Shrine data.");
+		DbManager.ShrineQueries.LOAD_ALL_SHRINES();
+
+		Logger.info("Initialize Resource type lookup");
+		Enum.ResourceType.InitializeResourceTypes();
+
+		Logger.info("Loading Warehouse data.");
+		DbManager.WarehouseQueries.LOAD_ALL_WAREHOUSES();
+
+		Logger.info("Loading Runegate data.");
+		Runegate.loadAllRunegates();
+
+		Logger.info("Loading Pirate Names.");
+		NPC.loadAllPirateNames();
+
+		Logger.info("Loading Max Skills for Trainers");
+		DbManager.SkillsBaseQueries.LOAD_ALL_MAX_SKILLS_FOR_CONTRACT();
+
+		//pick a startup Hotzone
+		ZoneManager.generateAndSetRandomHotzone();
+		
+		Logger.info("Loading All Players from database to Server Cache");
+		long start = System.currentTimeMillis();
+		try{
+			DbManager.PlayerCharacterQueries.GET_ALL_CHARACTERS();
+		}catch(Exception e){
+			e.printStackTrace();
+		}
+	
+		long end = System.currentTimeMillis();
+		Logger.info("Loading All Players took "  + (end - start) + " ms.");
+		
+		ItemProductionManager.ITEMPRODUCTIONMANAGER.initialize();
+
+		Logger.info("Loading Player Heraldries");
+		DbManager.PlayerCharacterQueries.LOAD_HERALDY();
+		
+		Logger.info("Running Heraldry Audit for Deleted Players");
+		Heraldry.AuditHeraldry();
+
+		if (ZoneManager.getHotZone() != null)
+			WorldServer.setLastHZChange(System.currentTimeMillis());
+
+		//Start Mines.
+
+		MineActiveJob maj = new MineActiveJob();
+		maj.run();
+
+		Logger.info("Starting Mobile AI FSM");
+		MobileFSMManager.getInstance();
+
+
+		for (Zone zone : ZoneManager.getAllZones()) {
+			if (zone.getHeightMap() != null) {
+				if (zone.getHeightMap().getBucketWidthX() == 0) {
+					System.out.println("Zone load num: " + zone.getLoadNum() + " has no bucket width");
+				}
+			}
+		}
+
+		Logger.info("World data loaded.");
+
+		//set default accesslevel for server  *** Refactor who two separate variables?
+		MBServerStatics.accessLevel = MBServerStatics.worldAccessLevel;
+		Logger.info("Default access level set to " + MBServerStatics.accessLevel);
+
+		Logger.info("Initializing Network");
+		Network.init();
+
+		Logger.info("Initializing Client Connection Manager");
+		initClientConnectionManager();
+
+		Logger.info("Starting message pumps");
+		DispatchMessage.startMessagePump();
+
+		// Run maintenance
+		MaintenanceManager.dailyMaintenance();
+
+		// Disabled but kept in case of emergency
+		Logger.info("Starting Orphan Item Purge");
+		PurgeOprhans.startPurgeThread();
+
+		// Calculate bootstrap time and rest boot time to current time.
+		java.time.Duration bootDuration = java.time.Duration.between(LocalDateTime.now(), bootTime);
+		long bootSeconds = Math.abs(bootDuration.getSeconds());
+		String boottime = String.format("%d hours %02d minutes %02d seconds", bootSeconds / 3600, (bootSeconds % 3600) / 60, (bootSeconds % 60));
+		Logger.info("Bootstrap time was " + boottime);
+
+		bootTime = LocalDateTime.now();
+		LootTable.initialized = true;
+
+		Logger.info("Running garbage collection...");
+		System.gc();
+		return true;
+	}
+
+	protected boolean initDatabaselayer() {
+
+		// Try starting a GOM <-> DB connection.
+		try {
+
+			Logger.info("Configuring GameObjectManager to use Database: '"
+					+  ConfigManager.MB_DATABASE_NAME.getValue() + "' on "
+					+  ConfigManager.MB_DATABASE_ADDRESS.getValue() + ':'
+					+  ConfigManager.MB_DATABASE_PORT.getValue());
+
+			DbManager.configureDatabaseLayer();
+
+		} catch (Exception e) {
+			Logger.error(e.getMessage());
+			return false;
+		}
+
+		PreparedStatementShared.submitPreparedStatementsCleaningJob();
+
+		if (MBServerStatics.DB_DEBUGGING_ON_BY_DEFAULT) {
+			PreparedStatementShared.enableDebugging();
+		}
+
+		return true;
+	}
+
+
+	private void getWorldBuildingsMobsNPCs() {
+
+		ArrayList<Zone> rootParent;
+
+		rootParent = DbManager.ZoneQueries.GET_MAP_NODES(MBServerStatics.worldUUID);
+
+		if (rootParent.isEmpty()) {
+			Logger.error("populateWorldBuildings: No entries found in worldMap for parent " + MBServerStatics.worldUUID);
+			return;
+		}
+
+		//Set sea floor object for server
+		Zone seaFloor = rootParent.get(0);
+		seaFloor.setParent(null);
+		ZoneManager.setSeaFloor(seaFloor);
+
+		//  zoneManager.addZone(seaFloor.getLoadNum(), seaFloor); <- DIE IN A FUCKING CAR FIRE BONUS CODE LIKE THIS SUCKS FUCKING DICK
+
+		rootParent.addAll(DbManager.ZoneQueries.GET_ALL_NODES(seaFloor));
+
+		long start = System.currentTimeMillis();
+
+		for (Zone zone : rootParent) {
+
+			try {
+				ZoneManager.addZone(zone.getLoadNum(), zone);
+
+				try{
+					zone.generateWorldAltitude();
+				}catch(Exception e){
+					Logger.error( e.getMessage());
+					e.printStackTrace();
+				}
+
+				//Handle Buildings
+
+				ArrayList<Building> bList;
+				bList = DbManager.BuildingQueries.GET_ALL_BUILDINGS_FOR_ZONE(zone);
+
+				for (Building b : bList) {
+
+					try {
+						b.setObjectTypeMask(MBServerStatics.MASK_BUILDING);
+						b.setLoc(b.getLoc());
+					} catch (Exception e) {
+						Logger.error( b.getObjectUUID() + " returned an Error Message :" + e.getMessage());
+					}
+				}
+
+				//Handle Mobs
+				ArrayList<Mob> mobs;
+				mobs = DbManager.MobQueries.GET_ALL_MOBS_FOR_ZONE(zone);
+
+				for (Mob m : mobs) {
+					m.setObjectTypeMask(MBServerStatics.MASK_MOB | m.getTypeMasks());
+					m.setLoc(m.getLoc());
+					m.setParentZone(zone);
+
+				//ADD GUARDS HERE.
+				if (m.getBuilding() != null && m.getBuilding().getBlueprint() != null && m.getBuilding().getBlueprint().getBuildingGroup() == BuildingGroup.BARRACK)
+					DbManager.MobQueries.LOAD_PATROL_POINTS(m);
+				}
+
+				//Handle npc's
+				ArrayList<NPC> npcs;
+
+				// Ignore npc's on the seafloor (npc guild leaders, etc)
+
+				if (zone.equals(seaFloor))
+					continue;
+
+				npcs = DbManager.NPCQueries.GET_ALL_NPCS_FOR_ZONE(zone);
+
+				for (NPC n : npcs) {
+
+					try {
+						n.setObjectTypeMask(MBServerStatics.MASK_NPC);
+						n.setLoc(n.getLoc());
+						n.setParentZone(zone);
+					} catch (Exception e) {
+						Logger.error( n.getObjectUUID() + " returned an Error Message :" + e.getMessage());
+					}
+				}
+
+				//Handle cities
+
+				City.loadCities(zone);
+				ZoneManager.populateWorldZones(zone);
+
+			} catch (Exception e) {
+				Logger.info(e.getMessage() + zone.getName() + ' ' + zone.getObjectUUID());
+			}
+		}
+
+		Logger.info("time to load: " + (System.currentTimeMillis() - start) + " ms");
+	}
+
+	/**
+	 * Called to remove a client on "leave world", "quit game", killed client
+	 * process, etc.
+	 */
+
+	public void removeClient(ClientConnection origin) {
+
+		if (origin == null) {
+			Logger.info(
+					"ClientConnection null in removeClient.");
+			return;
+		}
+
+		PlayerCharacter pc = SessionManager.getPlayerCharacter(
+				origin);
+
+		if (pc == null)
+			// TODO log this
+			return;
+
+		//cancel any trade
+		if (pc.getCharItemManager() != null)
+			pc.getCharItemManager().endTrade(true);
+
+		// logout
+		long delta = MBServerStatics.LOGOUT_TIMER_MS;
+
+		if (System.currentTimeMillis() - pc.getTimeStamp("LastCombatPlayer") < 60000) {
+			delta = 60000;
+
+		}
+		pc.stopMovement(pc.getLoc());
+		UpdateStateMsg updateStateMsg = new UpdateStateMsg();
+		updateStateMsg.setPlayer(pc);
+	
+		updateStateMsg.setActivity(5);
+		DispatchMessage.dispatchMsgToInterestArea(pc, updateStateMsg, DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, false, false);
+		
+		if (pc.getRegion() != null)
+			if (PlayerCharacter.CanBindToBuilding(pc, pc.getRegion().parentBuildingID))
+				pc.bindBuilding = pc.getRegion().parentBuildingID;
+			else
+				pc.bindBuilding = 0;
+		
+		pc.getLoadedObjects().clear();
+		pc.getLoadedStaticObjects().clear();
+
+		LogoutCharacterJob logoutJob = new LogoutCharacterJob(pc, this);
+		JobContainer jc = JobScheduler.getInstance().scheduleJob(logoutJob,
+				System.currentTimeMillis() + delta);
+		pc.getTimers().put("Logout", jc);
+		pc.getTimestamps().put("logout", System.currentTimeMillis());
+		
+		//send update to friends that you are logged off.
+
+		PlayerFriends.SendFriendsStatus(pc,false);
+
+	}
+
+	public void logoutCharacter(PlayerCharacter player) {
+
+		if (player == null) {
+			Logger.error("Unable to find PlayerCharacter to logout");
+			return;
+		}
+
+		player.getTimestamps().put("logout", System.currentTimeMillis());
+		player.setEnteredWorld(false);
+
+		// remove from simulation and zero current loc
+
+		WorldGrid.RemoveWorldObject(player);
+
+		// clear Logout Timer
+
+		if (player.getTimers() != null)
+			player.getTimers().remove("Logout");
+
+		if (player.getPet() != null)
+			player.getPet().dismiss();
+		
+		player.dismissNecroPets();
+
+		// Set player inactive so they quit loading for other players
+
+		player.setActive(false);
+
+		// Remove from group
+
+		Group group = GroupManager.getGroup(player);
+
+		try {
+			if (group != null)
+				GroupManager.LeaveGroup(player);
+		} catch (MsgSendException e) {
+			Logger.error( e.toString());
+		}
+		
+		player.respawnLock.writeLock().lock();
+		try{
+			if (!player.isAlive())
+				player.respawn(false, false, true);
+		}catch(Exception e){
+			Logger.error(e);
+		}finally{
+			player.respawnLock.writeLock().unlock();
+		}
+	}
+
+
+	public static void writePopulationFile() {
+
+		int population = SessionManager.getActivePlayerCharacterCount();
+try {
+	
+
+		File populationFile = new File(MBServerStatics.DEFAULT_DATA_DIR + ConfigManager.MB_WORLD_NAME.getValue().replaceAll("'","") + ".pop");
+		FileWriter fileWriter;
+
+		try {
+			fileWriter = new FileWriter(populationFile, false);
+			fileWriter.write(Integer.toString(population));
+			fileWriter.close();
+		} catch (IOException e) {
+			e.printStackTrace();
+		}
+		
+}catch(Exception e){
+	Logger.error(e);
+}
+	}
+
+	private void processTrashFile() {
+
+		ArrayList<String> machineList;
+		ArrayList<PlayerCharacter> trashList = new ArrayList<>();
+		ArrayList<Integer> accountList = new ArrayList<>();
+
+		File trashFile = new File("trash");
+
+		if (trashFile.exists() == false)
+			return;
+
+		// Build list of machineID's in the trash file
+
+		machineList = DbManager.AccountQueries.GET_TRASH_LIST();
+
+		// Build list of trash characters associated with that machineID
+
+		for (String machineID:machineList) {
+			trashList = DbManager.AccountQueries.GET_ALL_CHARS_FOR_MACHINE(machineID);
+
+
+			// Deactivate these players and add them to loginCache table
+
+			for (PlayerCharacter trashPlayer : trashList) {
+
+				if (trashPlayer == null)
+					continue;
+
+				// Need to collate accounts.
+
+				if (!accountList.contains(trashPlayer.getAccount().getObjectUUID()))
+					accountList.add(trashPlayer.getAccount().getObjectUUID());
+
+				DbManager.PlayerCharacterQueries.SET_ACTIVE(trashPlayer, false);
+				DbManager.AccountQueries.INVALIDATE_LOGIN_CACHE(trashPlayer.getObjectUUID(), "character");
+			}
+		}
+
+		//  delete vault of associated accounts and then invalidate them
+		//  in the login cache.
+
+		for (Integer accountID : accountList) {
+			DbManager.AccountQueries.DELETE_VAULT_FOR_ACCOUNT(accountID);
+			DbManager.AccountQueries.INVALIDATE_LOGIN_CACHE(accountID, "account");
+		}
+
+		// Trigger the Login Server to invalidate these accounts in the cache..
+
+		try {
+			Files.write(Paths.get("cacheInvalid"), "".getBytes());
+		} catch (IOException e) {
+			e.printStackTrace();
+		}
+
+		// If any of these players are active disconnect them.
+		// The account and player should be removed from the login
+		// server cache file by now.
+
+		Timer timer = new Timer("Disconnect Trash");
+		timer.schedule(new DisconnectTrashTask( trashList ), 3000L);
+
+		// Clean up after ourselves
+
+		try {
+			Files.deleteIfExists(Paths.get("trash"));
+			DbManager.AccountQueries.CLEAR_TRASH_TABLE();
+		} catch (IOException e) {
+			e.printStackTrace();
+		}
+
+		}
+
+	private void processFlashFile() {
+
+		File flashFile = new File("flash");
+		String flashString;
+		List<String> fileContents;
+
+		if (flashFile.exists() == false)
+			return;
+
+		try {
+			fileContents = Files.readAllLines(Paths.get("flash"));
+		} catch (IOException e) {
+			return;
+		}
+
+		// Flash file detected: read contents
+		// and send as a flash.
+
+		flashString = fileContents.toString();
+
+		if (flashString == null)
+			return;
+
+		if (flashString == "")
+			flashString = "Rebooting for to fix bug.";
+
+		Logger.info( "Sending flash from external interface");
+		Logger.info( "Msg: " + flashString);
+
+		ChatSystemMsg msg = new ChatSystemMsg(null, flashString);
+		msg.setChannel(engine.Enum.ChatChannelType.FLASH.getChannelID());
+		msg.setMessageType(Enum.ChatMessageType.INFO.ordinal());
+		DispatchMessage.dispatchMsgToAll(msg);
+
+		// Delete file
+
+		try {
+			Files.deleteIfExists(Paths.get("flash"));
+		} catch (IOException e) {
+			e.printStackTrace();
+		}
+
+	}
+}
\ No newline at end of file
diff --git a/src/engine/session/CSSession.java b/src/engine/session/CSSession.java
new file mode 100644
index 00000000..c6704d96
--- /dev/null
+++ b/src/engine/session/CSSession.java
@@ -0,0 +1,123 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.session;
+
+import engine.gameManager.DbManager;
+import engine.objects.AbstractGameObject;
+import engine.objects.Account;
+import engine.objects.PlayerCharacter;
+
+import java.net.InetAddress;
+
+
+public class CSSession extends AbstractGameObject {
+
+	private String sessionID;
+	private PlayerCharacter playerCharacter;
+	private Account account;
+
+
+	private String machineID;
+
+	public CSSession(String sessionID, Account acc, PlayerCharacter pc, String machineID) {
+		super();
+		this.sessionID = sessionID;
+		this.playerCharacter = pc;
+		this.account = acc;
+		this.machineID = machineID;
+
+		if (this.playerCharacter != null)
+			PlayerCharacter.initializePlayer(this.playerCharacter);
+	}
+
+	public PlayerCharacter getPlayerCharacter() {
+		return this.playerCharacter;
+	}
+
+	public void setPlayerCharacter(PlayerCharacter pc) {
+		this.playerCharacter = pc;
+	}
+
+	public Account getAccount() {
+		return this.account;
+	}
+
+	public void setAccount(Account acc) {
+		this.account = acc;
+	}
+
+	@Override
+	public void removeFromCache() {}
+
+	/*
+	 * Database
+	 */
+	public static boolean addCrossServerSession(String secKey, Account acc, InetAddress inet, String machineID) {
+		return DbManager.CSSessionQueries.ADD_CSSESSION(secKey, acc, inet, machineID);
+		//		PreparedStatementShared ps = null;
+		//		try {
+		//			ps = prepareStatement("INSERT INTO sessions (secretKey, accountID, vbID, sessionIP) VALUES (?,?,?,INET_ATON(?))");
+		//			ps.setString(1, secKey);
+		//			ps.setInt(2, acc.getUUID(), true);
+		//			ps.setInt(3, acc.getVBID());
+		//			ps.setString(4, StringUtils.InetAddressToClientString(inet));
+		//			if (ps.executeUpdate() > 0)
+		//				return true;
+		//		} catch (SQLException e) {
+		//			Logger.error("CSSession", "Failed to create cross server session");
+		//		} finally {
+		//			ps.release();
+		//		}
+		//		return false;
+	}
+
+	public static boolean deleteCrossServerSession(String secKey) {
+		return DbManager.CSSessionQueries.DELETE_CSSESSION(secKey);
+		//		PreparedStatementShared ps = null;
+		//		try {
+		//			ps = prepareStatement("DELETE FROM sessions WHERE secretKey = ?");
+		//			ps.setString(1, secKey);
+		//			if (ps.executeUpdate() > 0)
+		//				return true;
+		//		} catch (SQLException e) {
+		//			Logger.error("CSSession", "Failed to delete cross server session");
+		//		} finally {
+		//			ps.release();
+		//		}
+		//		return false;
+	}
+
+	public static boolean updateCrossServerSession(String secKey, int charID) {
+		return DbManager.CSSessionQueries.UPDATE_CSSESSION(secKey, charID);
+	}
+
+	public static CSSession getCrossServerSession(String secKey) {
+
+		CSSession sessionInfo;
+
+		try {
+			sessionInfo =  DbManager.CSSessionQueries.GET_CSSESSION(secKey);
+		} catch (Exception e) {
+			sessionInfo = null;
+		}
+
+		return  sessionInfo;
+	}
+
+	public String getMachineID() {
+		return machineID;
+	}
+
+	@Override
+	public void updateDatabase() {
+		// TODO Auto-generated method stub
+
+	}
+}
diff --git a/src/engine/session/Session.java b/src/engine/session/Session.java
new file mode 100644
index 00000000..d02ffd94
--- /dev/null
+++ b/src/engine/session/Session.java
@@ -0,0 +1,71 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+ package engine.session;
+
+import engine.Enum;
+import engine.gameManager.DbManager;
+import engine.net.client.ClientConnection;
+import engine.objects.Account;
+import engine.objects.PlayerCharacter;
+
+
+public class Session {
+
+	private SessionID sessionID;
+	private PlayerCharacter playerCharacter;
+	private Account account;
+	private ClientConnection conn;
+
+	public Session(SessionID sessionID, Account acc, ClientConnection conn) {
+		super();
+		this.sessionID = sessionID;
+		this.playerCharacter = null;
+		this.account = acc;
+		this.conn = conn;
+	}
+
+	public PlayerCharacter getPlayerCharacter() {
+			if (this.playerCharacter != null) {
+
+			if (DbManager.inCache(Enum.GameObjectType.PlayerCharacter, this.playerCharacter.getObjectUUID()))
+				this.playerCharacter = (PlayerCharacter) DbManager.getFromCache(Enum.GameObjectType.PlayerCharacter, this.playerCharacter.getObjectUUID());
+		}
+		return this.playerCharacter;
+	}
+
+	public void setPlayerCharacter(PlayerCharacter pc) {
+		this.playerCharacter = pc;
+	}
+
+	public SessionID getSessionID() {
+		return this.sessionID;
+	}
+
+	public void setSessionID(SessionID sessionID) {
+		this.sessionID = sessionID;
+	}
+
+	public Account getAccount() {
+		return this.account;
+	}
+
+	public void setAccount(Account acc) {
+		this.account = acc;
+	}
+
+	public ClientConnection getConn() {
+		return this.conn;
+	}
+
+	public void setConn(ClientConnection conn) {
+		this.conn = conn;
+	}
+
+}
diff --git a/src/engine/session/SessionID.java b/src/engine/session/SessionID.java
new file mode 100644
index 00000000..921b976b
--- /dev/null
+++ b/src/engine/session/SessionID.java
@@ -0,0 +1,50 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.session;
+
+import engine.util.ByteUtils;
+
+public class SessionID {
+
+	private final byte[] data;
+	private final String dataAsString;
+
+	public SessionID(byte[] data) {
+		super();
+		this.data = data;
+		this.dataAsString = ByteUtils.byteArrayToStringHex(data);
+	}
+
+	@Override
+	public boolean equals(Object obj) {
+		boolean out = false;
+		if(obj instanceof SessionID) {
+			out = true;
+			SessionID id = (SessionID) obj;
+			for (int i = 0; out && i < id.data.length; ++i) {
+				if (id.data[i] != this.data[i]) {
+					out = false;
+				}
+			}
+		}
+		
+		return out;
+	}
+
+	@Override
+	public String toString() {
+		return this.dataAsString;
+	}
+
+	public final byte[] getData() {
+		return data;
+	}
+
+}
diff --git a/src/engine/util/ByteAnalyzer.java b/src/engine/util/ByteAnalyzer.java
new file mode 100644
index 00000000..a70de21c
--- /dev/null
+++ b/src/engine/util/ByteAnalyzer.java
@@ -0,0 +1,224 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+ package engine.util;
+
+import java.io.ByteArrayInputStream;
+import java.io.DataInputStream;
+import java.io.IOException;
+
+public class ByteAnalyzer {
+
+	public static void analyze4Bytes(byte[] data, String Label,
+			boolean switchEndian) throws IOException {
+
+		ByteArrayInputStream bias = new ByteArrayInputStream(data);
+		DataInputStream dis = new DataInputStream(bias);
+		dis.mark(4);
+
+		System.out.println("Analysis 4 bytes: (" + Label + ')');
+		System.out.println("\t Hex: " + ByteUtils.byteArrayToStringHex(data));
+		System.out.println(ByteAnalyzer.buildAll4(dis, switchEndian));
+		System.out.println(ByteAnalyzer.buildUTF8(dis));
+		System.out.println(ByteAnalyzer.buildUTF16(dis));
+		System.out.println(ByteAnalyzer.buildRawNumericalBytes(dis));
+		System.out.println();
+	}
+
+	public static void analyze8Bytes(byte[] data, String Label,
+			boolean switchEndian) throws IOException {
+
+		ByteArrayInputStream bias = new ByteArrayInputStream(data);
+		DataInputStream dis = new DataInputStream(bias);
+		dis.mark(8);
+
+		System.out.println("Analysis for 8 bytes: (" + Label + ')');
+		System.out.println("\tHex: " + ByteUtils.byteArrayToStringHex(data));
+		System.out.println(ByteAnalyzer.buildAll8(dis, switchEndian));
+		dis.reset();
+		System.out.println(ByteAnalyzer.buildUTF8(dis));
+		System.out.println(ByteAnalyzer.buildUTF16(dis));
+		System.out.println(ByteAnalyzer.buildRawNumericalBytes(dis));
+		System.out.println("\n");
+	}
+
+	public static String buildAll8(DataInputStream indis, boolean se)
+			throws IOException {
+		byte[] ba = new byte[8];
+		indis.read(ba);
+		DataInputStream dis = new DataInputStream(new ByteArrayInputStream(ba));
+		dis.mark(8);
+
+		String out = "";
+
+		out += buildFromTemplate("8", dis, se);
+		out += buildFromTemplate("4.4", dis, se);
+
+		out += buildFromTemplate("4.2.2", dis, se);
+		out += buildFromTemplate("4.2.1.1", dis, se);
+		out += buildFromTemplate("4.1.2.1", dis, se);
+		out += buildFromTemplate("4.1.1.2", dis, se);
+		out += buildFromTemplate("4.1.1.1.1", dis, se);
+
+		out += buildFromTemplate("2.2.4", dis, se);
+		out += buildFromTemplate("2.1.1.4", dis, se);
+		out += buildFromTemplate("1.2.1.4", dis, se);
+		out += buildFromTemplate("1.1.2.4", dis, se);
+		out += buildFromTemplate("1.1.1.1.4", dis, se);
+
+		out += buildFromTemplate("2.4.2", dis, se);
+		out += buildFromTemplate("2.4.1.1", dis, se);
+		out += buildFromTemplate("1.1.4.2", dis, se);
+		out += buildFromTemplate("1.1.4.1.1", dis, se);
+
+		out += buildFromTemplate("2.1.2.2.1", dis, se);
+		out += buildFromTemplate("2.1.2.1.2", dis, se);
+		out += buildFromTemplate("1.2.2.2.1", dis, se);
+		out += buildFromTemplate("1.2.2.1.2", dis, se);
+
+		out += buildFromTemplate("1.1.1.2.2.1", dis, se);
+		out += buildFromTemplate("2.1.2.1.1.1", dis, se);
+		out += buildFromTemplate("1.1.1.2.1.1.1", dis, se);
+		out += buildFromTemplate("1.1.1.1.1.1.1.1", dis, se);
+
+		out += buildFromTemplate("2.1.1.1.1.1.1", dis, se);
+		out += buildFromTemplate("1.2.1.1.1.1.1", dis, se);
+		out += buildFromTemplate("1.1.2.1.1.1.1", dis, se);
+
+		out += buildFromTemplate("1.1.1.1.2.1.1", dis, se);
+		out += buildFromTemplate("1.1.1.1.1.2.1", dis, se);
+		out += buildFromTemplate("1.1.1.1.1.1.2", dis, se);
+
+		return out;
+	}
+
+	public static String buildAll4(DataInputStream indis, boolean se)
+			throws IOException {
+		byte[] ba = new byte[4];
+		indis.read(ba);
+		DataInputStream dis = new DataInputStream(new ByteArrayInputStream(ba));
+		dis.mark(4);
+
+		String out = "";
+		out += buildFromTemplate("4", dis, se);
+		out += buildFromTemplate("2.2", dis, se);
+		out += buildFromTemplate("2.1.1", dis, se);
+		out += buildFromTemplate("1.2.1", dis, se);
+		out += buildFromTemplate("1.1.2", dis, se);
+		out += buildFromTemplate("1.1.1.1", dis, se);
+
+		return out;
+	}
+
+	public static String buildFromTemplate(String template,
+			DataInputStream dis, boolean se) throws IOException {
+		String out = '\t' + template + ": ";
+
+		for (int i = template.length(); i < 16; ++i) {
+			out += " ";
+		}
+
+		template = template.replace(".", "");
+		String[] items = template.split("");
+		dis.mark(dis.available());
+
+		for (String s : items) {
+			if (s.equals("1")) {
+				out += " (B:" + dis.readByte();
+				dis.reset();
+				out += "/uB:" + dis.readUnsignedByte() + ')';
+			} else if (s.equals("2")) {
+				byte[] read = new byte[2];
+				dis.read(read);
+				byte[] use = new byte[2];
+				if (se) {
+					use = ByteUtils.switchByteArrayEndianness(read);
+				} else {
+					use = read;
+				}
+				out += " (S:"
+						+ new DataInputStream(new ByteArrayInputStream(use))
+								.readShort();
+				out += "/uS:"
+						+ new DataInputStream(new ByteArrayInputStream(use))
+								.readUnsignedShort() + ')';
+
+			} else if (s.equals("4")) {
+				byte[] read = new byte[4];
+				dis.read(read);
+				byte[] use = new byte[4];
+				if (se) {
+					use = ByteUtils.switchByteArrayEndianness(read);
+				} else {
+					use = read;
+				}
+				out += "  (I:";
+				out += new DataInputStream(new ByteArrayInputStream(use))
+						.readInt();
+
+				out += " / F:";
+				out += new DataInputStream(new ByteArrayInputStream(use))
+						.readFloat()
+						+ ")";
+			} else if (s.equals("8")) {
+				byte[] read = new byte[8];
+				dis.read(read);
+
+				byte[] use = new byte[8];
+				if (se) {
+					use = ByteUtils.switchByteArrayEndianness(read);
+				} else {
+					use = read;
+				}
+				out += "  (L:";
+				out += new DataInputStream(new ByteArrayInputStream(use))
+						.readLong();
+
+				out += " / D:";
+				out += new DataInputStream(new ByteArrayInputStream(use))
+						.readDouble()
+						+ ")";
+			}
+		}
+		dis.reset();
+		return out + '\n';
+	}
+
+	public static String buildUTF8(DataInputStream dis) throws IOException {
+		dis.mark(dis.available());
+		String out = "\tUTF-8: ";
+		while (dis.available() > 1) {
+			out += " '" + (char) dis.read() + '\'';
+		}
+		dis.reset();
+		return out;
+	}
+
+	public static String buildUTF16(DataInputStream dis) throws IOException {
+		dis.mark(dis.available());
+		String out = "\tUTF-16:";
+		while (dis.available() > 1) {
+			out += " '" + dis.readChar() + '\'';
+		}
+		dis.reset();
+		return out;
+	}
+
+	public static String buildRawNumericalBytes(DataInputStream dis)
+			throws IOException {
+		dis.mark(dis.available());
+		String out = "\tRaw Bytes (int vals): ";
+		while (dis.available() > 1) {
+			out += " '" + dis.read() + '\'';
+		}
+		dis.reset();
+		return out;
+	}
+
+}
diff --git a/src/engine/util/ByteBufferUtils.java b/src/engine/util/ByteBufferUtils.java
new file mode 100644
index 00000000..f3aa91c8
--- /dev/null
+++ b/src/engine/util/ByteBufferUtils.java
@@ -0,0 +1,221 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.util;
+
+import java.io.UnsupportedEncodingException;
+import java.nio.BufferOverflowException;
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+
+public class ByteBufferUtils {
+	public static String getString(ByteBuffer bb)
+			throws BufferUnderflowException {
+		return getString(bb, false, false);
+	}
+
+	public static String getString(ByteBuffer bb, boolean switchEndian, boolean small)
+			throws BufferUnderflowException {
+		String out = "";
+		synchronized (bb) {
+
+			//This version works with non-latin characters
+			int stringLen;
+			if (small)
+				stringLen = (int)bb.get();
+			else
+				stringLen = bb.getInt();
+			if (switchEndian)
+				stringLen = ((Integer.reverseBytes(stringLen)) * 2);
+			else
+				stringLen *= 2;
+			byte[] b = new byte[stringLen];
+			for (int i=0;i<stringLen;i+=2) {
+				if (switchEndian) {
+					b[i+1] = bb.get();
+					b[i] = bb.get();
+				} else {
+					b[i] = bb.get();
+					b[i+1] = bb.get();
+				}
+			}
+			try {
+				out = new String(b, "UTF-16BE");
+			} catch (UnsupportedEncodingException e) {}
+		}
+		return out;
+	}
+
+	public static void putString(ByteBuffer bb, String data, boolean small)
+			throws BufferOverflowException {
+		putString(bb, data, false, small);
+	}
+
+	public static void putString(ByteBuffer bb, String data,
+			boolean switchEndian, boolean small) throws BufferOverflowException {
+		if (data == null) {
+			data = "";
+		}
+		char[] chars = data.toCharArray();
+
+		int length = chars.length;
+		if (small && length > 255)
+			length = 255; //limit for smallString
+
+		synchronized (bb) {
+			// Write length
+			if (small)
+				bb.put((byte)length);
+			else {
+				if (switchEndian) {
+					bb.putInt(Integer.reverseBytes(length));
+				} else {
+					bb.putInt(length);
+				}
+			}
+			// Write chars
+			for (int i=0;i<length;i++) {
+				char c = chars[i];
+				if (switchEndian) {
+					bb.putChar(Character.reverseBytes(c));
+				} else {
+					bb.putChar(c);
+				}
+			}
+		}
+	}
+
+	public static String getHexString(ByteBuffer bb)
+			throws BufferUnderflowException {
+		return getHexString(bb, false);
+	}
+
+	public static String getHexString(ByteBuffer bb, boolean switchEndian)
+			throws BufferUnderflowException {
+		String out = "";
+		synchronized (bb) {
+			// Read len
+			int stringLen = bb.getInt();
+
+			if (switchEndian) {
+				stringLen = Integer.reverseBytes(stringLen);
+			}
+
+			// Read len worth of chars
+			for (int i = 0; i < stringLen; ++i) {
+				out += Integer.toString((bb.get() & 0xff) + 0x100, 16)
+						.substring(1);
+			}
+		}
+		return out;
+	}
+
+	public static void putHexString(ByteBuffer bb, String data)
+			throws BufferOverflowException {
+		putHexString(bb, data, false);
+	}
+
+	public static void putHexString(ByteBuffer bb, String data,
+			boolean switchEndian) throws BufferOverflowException {
+
+		if (data == null) {
+			data = "";
+		}
+
+		byte[] bts = new byte[data.length() / 2];
+
+		for (int i = 0; i < bts.length; i++) {
+			bts[i] = (byte) Integer.parseInt(data.substring(2 * i, 2 * i + 1),
+					16);
+		}
+
+		synchronized (bb) {
+			if (switchEndian) {
+				bb.putInt(Integer.reverseBytes(data.length() / 2));
+			} else {
+				bb.putInt(data.length() / 2);
+			}
+			bb.put(bts);
+		}
+	}
+
+	public static String getUnicodeString(ByteBuffer bb)
+			throws BufferUnderflowException {
+		return getUnicodeString(bb, false);
+	}
+
+	public static String getUnicodeString(ByteBuffer bb, boolean switchEndian)
+			throws BufferUnderflowException {
+		byte[] out;
+		short thisChar;
+		synchronized (bb) {
+			// Read len
+			int stringLen = bb.getInt();
+			if (switchEndian) {
+				stringLen = Integer.reverseBytes(stringLen);
+			}
+			out = new byte[stringLen];
+			// Read len worth of chars
+			for (int i = 0; i < stringLen; ++i) {
+				thisChar = bb.getShort();
+				if (switchEndian)
+					Short.reverseBytes(thisChar);
+				out[i] = (byte) (thisChar & 0xff); // ignore first byte
+			}
+		}
+		return new String(out);
+	}
+
+	public static void putUnicodeString(ByteBuffer bb, String data)
+			throws BufferOverflowException {
+		putUnicodeString(bb, data, false);
+	}
+
+	public static void putUnicodeString(ByteBuffer bb, String data,
+			boolean switchEndian) throws BufferOverflowException {
+		byte[] out;
+		short thisChar;
+		if (data == null)
+			return;
+		out = new byte[data.length()];
+		out = data.getBytes();
+
+		for (byte b : out) {
+			thisChar = b;
+			if (switchEndian)
+				thisChar = Short.reverseBytes(thisChar);
+			bb.putShort(thisChar);
+		}
+	}
+
+	public static boolean checkByteBufferNearFull(ByteBuffer bb) {
+        return bb.position() >= (bb.capacity() * 0.9);
+    }
+
+	//FIXME: Replace these!!!
+//	public static ByteBuffer resizeByteBuffer(ByteBuffer bb, int multiplyer) {
+//
+//		ByteBuffer out = ByteBuffer.allocate(bb.capacity() * multiplyer);
+//
+//		// Copy the data to a temp buf
+//		bb.flip();
+//		out.put(bb);
+//
+//		return out;
+//	}
+//
+//	public static ByteBuffer shrinkByteBuffer(ByteBuffer bb) {
+//
+//		bb.flip();
+//		ByteBuffer out = ByteBuffer.allocate(bb.remaining());
+//		out.put(bb);
+//		return out;
+//	}
+
+}
diff --git a/src/engine/util/ByteUtils.java b/src/engine/util/ByteUtils.java
new file mode 100644
index 00000000..51fb65c9
--- /dev/null
+++ b/src/engine/util/ByteUtils.java
@@ -0,0 +1,171 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+ package engine.util;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.zip.GZIPInputStream;
+import java.util.zip.GZIPOutputStream;
+
+public abstract class ByteUtils {
+
+	private ByteUtils() {
+	}
+
+	public static byte[] switchByteArrayEndianness(byte[] in) {
+		int size = in.length;
+
+		byte[] out = new byte[size];
+
+		for (int i = 0; i < size; ++i) {
+			out[size - i - 1] = in[i];
+		}
+		return out;
+	}
+
+	/*
+	 * Converts a single byte to a hex StringBuffer
+	 */
+	public static void byteToStringHex(byte b, StringBuffer buf) {
+		char[] hexChars = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+				'A', 'B', 'C', 'D', 'E', 'F' };
+		int high = ((b & 0xf0) >> 4);
+		int low = (b & 0x0f);
+		buf.append(hexChars[high]);
+		buf.append(hexChars[low]);
+	}
+
+	/*
+	 * Converts a single byte to a hex String
+	 */
+	public static String byteToStringHex(byte b) {
+		StringBuffer sb = new StringBuffer();
+		byteToStringHex(b, sb);
+		return sb.toString();
+	}
+
+	/*
+	 * Converts a byte array to hex String
+	 */
+	public static String byteArrayToStringHex(byte[] block) {
+		StringBuffer buf = new StringBuffer();
+		int len = block.length;
+		
+		for (int i = 0; i < len; i++) {
+			ByteUtils.byteToStringHex(block[i], buf);
+			if (i < len - 1) {
+				buf.append(':');
+			}
+		}
+		return buf.toString();
+	}
+
+	/*
+	 * Converts a single byte to a hex StringBuffer
+	 */
+	public static void byteToSafeStringHex(byte b, StringBuffer buf) {
+		char[] hexChars = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+				'a', 'b', 'c', 'd', 'e', 'f' };
+		int high = ((b & 0xf0) >> 4);
+		int low = (b & 0x0f);
+		buf.append(hexChars[high]);
+		buf.append(hexChars[low]);
+	}
+
+	/*
+	 * Converts a single byte to a hex String
+	 */
+	public static String byteToSafeStringHex(byte b) {
+		StringBuffer sb = new StringBuffer();
+		byteToSafeStringHex(b, sb);
+		return sb.toString();
+	}
+
+	/*
+	 * Converts a byte array to hex String
+	 */
+	public static String byteArrayToSafeStringHex(byte[] block) {
+		StringBuffer buf = new StringBuffer();
+
+		int len = block.length;
+
+		for (int i = 0; i < len; i++) {
+			ByteUtils.byteToSafeStringHex(block[i], buf);
+		}
+		return buf.toString();
+	}
+
+	/*
+	 * Converts a hex string to Byte Array
+	 */
+	public static byte[] stringHexToByteArray(String hex) {
+		int length = hex.length();
+		char[] hexchar = hex.toCharArray();
+		byte[] ret = new byte[length / 2];
+		int i1 = 0;
+
+		for (int i = 0; i < length - 1; i += 2) {
+			ret[i1] = (byte) (Character.digit(hexchar[i], 16) * 16 + Character
+					.digit(hexchar[i + 1], 16));
+			i1++;
+		}
+
+		return ret;
+	}
+	
+	/*
+	 * Converts a hex string formatted by our byteToStringHex to a byte array
+	 * returns null if passed a null string
+	 */
+	public static byte[] formattedStringHexToByteArray(String hex) {
+		if(hex == null){
+			return null;
+		}
+			
+		String tmpString = hex.replaceAll(":","");
+		int length = tmpString.length();
+		char[] hexchar = tmpString.toCharArray();
+		byte[] ret = new byte[length / 2];
+		int i1 = 0;
+
+		for (int i = 0; i < length - 1; i += 2) {
+			ret[i1] = (byte) (Character.digit(hexchar[i], 16) * 16 + Character
+					.digit(hexchar[i + 1], 16));
+			i1++;
+		}
+
+		return ret;
+	}
+	
+	public static byte[] compress(final byte[] in) throws IOException{
+        final ByteArrayOutputStream out = new ByteArrayOutputStream();
+        final GZIPOutputStream gzOs = new GZIPOutputStream(out);
+        gzOs.write(in);
+        gzOs.close();
+        return out.toByteArray();
+	}
+	
+    public static byte[] decompress(final byte[] in) throws IOException{
+        final ByteArrayOutputStream out = new ByteArrayOutputStream();
+        final GZIPInputStream gzIs = new GZIPInputStream(new ByteArrayInputStream(in));
+        final byte[] buffer = new byte[512];
+        int lastRead = 0;
+        
+        lastRead = gzIs.read(buffer);
+        while (lastRead > 0) {
+        	out.write(buffer,0,lastRead);
+            lastRead = gzIs.read(buffer);
+        }
+        gzIs.close();
+        return out.toByteArray();
+    }
+	
+}
diff --git a/src/engine/util/Hasher.java b/src/engine/util/Hasher.java
new file mode 100644
index 00000000..2914c03a
--- /dev/null
+++ b/src/engine/util/Hasher.java
@@ -0,0 +1,382 @@
+package engine.util;
+
+
+import java.util.*;
+
+/*
+ * Author: https://github.com/peet/hashids.java
+ */
+
+public class Hasher {
+
+    private static final String DEFAULT_ALPHABET = "xcS4F6h89aUbideAI7tkynuopqrXCgTE5GBKHLMjfRsz";
+    private static final int[] PRIMES = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43};
+    private static final int[] SEPS_INDICES = {0, 4, 8, 12};
+
+    private String salt_ = "";
+
+    private String alphabet_ = "";
+
+    private int minHashLength_;
+
+    private ArrayList<Character> seps_ = new ArrayList<>();
+    private ArrayList<Character> guards_ = new ArrayList<>();
+
+    public Hasher() {
+        this("");
+    }
+
+    public Hasher(String salt) {
+        this(salt, 0);
+    }
+
+    public Hasher(String salt, int minHashLength) {
+        this(salt, minHashLength, DEFAULT_ALPHABET);
+    }
+
+    public Hasher(String salt, int minHashLength, String alphabet) {
+        if (alphabet == null || alphabet.trim().isEmpty()) {
+            throw new IllegalArgumentException("alphabet must not be empty");
+        }
+
+        if (salt != null) {
+            salt_ = salt;
+        }
+
+        if (minHashLength > 0) {
+            minHashLength_ = minHashLength;
+        }
+
+        alphabet_ = join(new LinkedHashSet<>(Arrays.asList(alphabet.split(""))), "");
+
+        if (alphabet_.length() < 4) {
+            throw new IllegalArgumentException("Alphabet must contain at least 4 unique characters.");
+        }
+
+        for (int prime : PRIMES) {
+            if (prime < alphabet_.length()) {
+                char c = alphabet_.charAt(prime - 1);
+                seps_.add(c);
+                alphabet_ = alphabet_.replace(c, ' ');
+            }
+        }
+
+        for (int index : SEPS_INDICES) {
+            if (index < seps_.size()) {
+                guards_.add(seps_.get(index));
+                seps_.remove(index);
+            }
+        }
+
+        alphabet_ = consistentShuffle(alphabet_.replaceAll(" ", ""), salt_);
+    }
+
+    public String encrypt(long... numbers) {
+        return encode(numbers, alphabet_, salt_, minHashLength_);
+    }
+
+    public long[] decrypt(String hash) {
+        return decode(hash);
+    }
+
+    private String encode(long[] numbers, String alphabet, String salt, int minHashLength) {
+        String ret = "";
+        String seps = consistentShuffle(join(seps_, ""), join(numbers, ""));
+        char lotteryChar = 0;
+
+        for (int i = 0; i < numbers.length; i++) {
+            if (i == 0) {
+                String lotterySalt = join(numbers, "-");
+                for (long number : numbers) {
+                    lotterySalt += "-" + (number + 1) * 2;
+                }
+                String lottery = consistentShuffle(alphabet, lotterySalt);
+                lotteryChar = lottery.charAt(0);
+                ret += lotteryChar;
+
+                alphabet = lotteryChar + alphabet.replaceAll(String.valueOf(lotteryChar), "");
+            }
+
+            alphabet = consistentShuffle(alphabet, ((int) lotteryChar & 12345) + salt);
+            ret += hash(numbers[i], alphabet);
+
+            if (i + 1 < numbers.length) {
+                ret += seps.charAt((int) ((numbers[i] + i) % seps.length()));
+            }
+        }
+
+        if (ret.length() < minHashLength) {
+            int firstIndex = 0;
+            for (int i = 0; i < numbers.length; i++) {
+                firstIndex += (i + 1) * numbers[i];
+            }
+
+            int guardIndex = firstIndex % guards_.size();
+            char guard = guards_.get(guardIndex);
+            ret = guard + ret;
+
+            if (ret.length() < minHashLength) {
+                guardIndex = (guardIndex + ret.length()) % guards_.size();
+                guard = guards_.get(guardIndex);
+                ret += guard;
+            }
+        }
+
+        while (ret.length() < minHashLength) {
+            long[] padArray = new long[]{alphabet.charAt(1), alphabet.charAt(0)};
+            String padLeft = encode(padArray, alphabet, salt, 0);
+            String padRight = encode(padArray, alphabet, join(padArray, ""), 0);
+
+            ret = padLeft + ret + padRight;
+            int excess = ret.length() - minHashLength;
+            if (excess > 0) {
+                ret = ret.substring(excess / 2, excess / 2 + minHashLength);
+            }
+            alphabet = consistentShuffle(alphabet, salt + ret);
+        }
+
+        return ret;
+    }
+
+    private static String hash(long number, String alphabet) {
+        String hash = "";
+
+        while (number > 0) {
+            hash = alphabet.charAt((int) (number % alphabet.length())) + hash;
+            number /= alphabet.length();
+        }
+
+        return hash;
+    }
+
+    private static long unhash(String hash, String alphabet) {
+        int number = 0;
+
+        for (int i = 0; i < hash.length(); i++) {
+            int pos = alphabet.indexOf(hash.charAt(i));
+            number += pos * (int) Math.pow(alphabet.length(), hash.length() - i - 1);
+        }
+
+        return number;
+    }
+
+    private long[] decode(String hash) {
+        List<Long> ret = new ArrayList<>();
+        String originalHash = hash;
+
+        if (hash != null && !hash.isEmpty()) {
+            String alphabet = "";
+            char lotteryChar = 0;
+
+            for (char guard : guards_) {
+                hash = hash.replaceAll(String.valueOf(guard), " ");
+            }
+
+            String[] hashSplit = hash.split(" ");
+
+            hash = hashSplit[hashSplit.length == 3 || hashSplit.length == 2 ? 1 : 0];
+
+            for (char sep : seps_) {
+                hash = hash.replaceAll(String.valueOf(sep), " ");
+            }
+
+            String[] hashArray = hash.split(" ");
+            for (int i = 0; i < hashArray.length; i++) {
+                String subHash = hashArray[i];
+
+                if (subHash != null && !subHash.isEmpty()) {
+                    if (i == 0) {
+                        lotteryChar = hash.charAt(0);
+                        subHash = subHash.substring(1);
+                        alphabet = lotteryChar + alphabet_.replaceAll(String.valueOf(lotteryChar), "");
+                    }
+                }
+
+                if (alphabet.length() > 0) {
+                    alphabet = consistentShuffle(alphabet, ((int) lotteryChar & 12345) + salt_);
+                    ret.add(unhash(subHash, alphabet));
+                }
+            }
+        }
+
+        long[] numbers = longListToPrimitiveArray(ret);
+
+        if (!encrypt(numbers).equals(originalHash)) {
+            return new long[0];
+        }
+
+        return numbers;
+    }
+
+    private static String consistentShuffle(String alphabet, String salt) {
+        String ret = "";
+
+        if (!alphabet.isEmpty()) {
+            List<String> alphabetArray = charArrayToStringList(alphabet.toCharArray());
+            if (salt == null || salt.isEmpty()) {
+                salt = new String(new char[]{'\0'});
+            }
+
+            int[] sortingArray = new int[salt.length()];
+            for (int i = 0; i < salt.length(); i++) {
+                sortingArray[i] = salt.charAt(i);
+            }
+
+            for (int i = 0; i < sortingArray.length; i++) {
+                boolean add = true;
+
+                for (int k = i; k != sortingArray.length + i - 1; k++) {
+                    int nextIndex = (k + 1) % sortingArray.length;
+
+                    if (add) {
+                        sortingArray[i] += sortingArray[nextIndex] + (k * i);
+                    } else {
+                        sortingArray[i] -= sortingArray[nextIndex];
+                    }
+
+                    add = !add;
+                }
+
+                sortingArray[i] = Math.abs(sortingArray[i]);
+            }
+
+            int i = 0;
+            while (alphabetArray.size() > 0) {
+                int pos = sortingArray[i];
+                if (pos >= alphabetArray.size()) {
+                    pos %= alphabetArray.size();
+                }
+                ret += alphabetArray.get(pos);
+                alphabetArray.remove(pos);
+                i = ++i % sortingArray.length;
+            }
+        }
+        return ret;
+    }
+
+    public String getSalt() {
+        return salt_;
+    }
+
+    public String getAlphabet() {
+        return alphabet_;
+    }
+
+    public int getMinHashLength() {
+        return minHashLength_;
+    }
+
+    public static String getVersion() {
+        return "0.1.4";
+    }
+
+    private static long[] longListToPrimitiveArray(List<Long> longs) {
+        long[] longArr = new long[longs.size()];
+        int i = 0;
+        for (long l : longs) {
+            longArr[i++] = l;
+        }
+        return longArr;
+    }
+
+    private static List<String> charArrayToStringList(char[] chars) {
+        ArrayList<String> list = new ArrayList<>(chars.length);
+        for (char c : chars) {
+            list.add(String.valueOf(c));
+        }
+        return list;
+    }
+
+    private static String join(long[] a, String delimiter) {
+        ArrayList<String> strList = new ArrayList<>(a.length);
+        for (long l : a) {
+            strList.add(String.valueOf(l));
+        }
+        return join(strList, delimiter);
+    }
+
+    private static String join(Collection<?> s, String delimiter) {
+        Iterator<?> iter = s.iterator();
+        if (iter.hasNext()) {
+            StringBuilder builder = new StringBuilder(s.size());
+            builder.append(iter.next());
+            while (iter.hasNext()) {
+                builder.append(delimiter);
+                builder.append(iter.next());
+            }
+            return builder.toString();
+        }
+        return "";
+    }
+
+    public static int SBStringHash(String toHash) {
+        byte[] hashArray = toHash.getBytes();
+        int hash = 0;
+        int shift = 0;
+
+        if (toHash.equals("SafeModeA"))
+            return -1661750486;
+        if (toHash.equals("SafeModeB"))
+            return -1661751254;
+
+        if (toHash.equals("INVIS-D"))
+            return -1661751254;
+
+        if (toHash.equals("SafeMode"))
+            return -1661750486;
+
+
+        if ((hashArray.length != 8 && hashArray.length != 7) || hashArray[3] != 45) {
+            hash = 0;
+            shift = 0;
+            for (int i = 0; i < hashArray.length; i++) {
+                if (i == 0)
+                    shift = 0;
+                else
+                    shift = shift + 5;
+                int toShift = hashArray[i] - 0x20;
+                hash ^= toShift << shift;
+
+                if (shift > 24) {
+
+
+                    int newShift = 0x20 - shift;
+                    hash ^= toShift >> newShift;
+
+                    if (shift >= 27) {
+                        shift = shift - 0x20;
+                    }
+                }
+            }
+            return hash;
+        } else {
+            int ecx = 0;
+            if (hashArray.length == 8) {
+                ecx = hashArray[7];
+            }
+            int eax = hashArray[4];
+            int esi = ecx * 0x8;
+            eax = eax ^ esi;
+            ecx = ecx ^ 0x5A0;
+            esi = hashArray[5];
+            eax = eax << 4;
+            eax = eax ^ esi;
+            esi = hashArray[6];
+            eax = eax << 4;
+            eax = eax ^ esi;
+            esi = hashArray[2];
+            eax = eax << 5;
+            eax = eax ^ esi;
+            esi = hashArray[1];
+            int edx = hashArray[0];
+            eax = eax << 5;
+            eax = eax ^ esi;
+            ecx = ecx / 2;
+            ecx = ecx / 2;
+            eax = eax << 5;
+            ecx = ecx ^ edx;
+            eax = eax ^ ecx;
+            return eax;
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/engine/util/MapLoader.java b/src/engine/util/MapLoader.java
new file mode 100644
index 00000000..b2ac6849
--- /dev/null
+++ b/src/engine/util/MapLoader.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright MagicBane 2013
+ */
+
+package engine.util;
+
+import engine.Enum.RealmType;
+import engine.server.MBServerStatics;
+import engine.server.world.WorldServer;
+import org.pmw.tinylog.Logger;
+
+import javax.imageio.ImageIO;
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.io.IOException;
+
+public enum MapLoader {
+
+    MAPLOADER;
+
+    public static int[][] loadMap() {
+
+        BufferedImage image;
+        int[][] realmMap;
+        long timeToLoad = System.currentTimeMillis();
+        long bytesRead = 0;
+        long realmsWritten = 0;
+
+        Integer realmUUID = null;
+        
+        // Load image from disk
+        
+        try {
+            image = ImageIO.read(new File(MBServerStatics.DEFAULT_DATA_DIR + "realmmap.png"));
+
+            // Array size determined by image size
+            MBServerStatics.SPATIAL_HASH_BUCKETSX = image.getWidth();
+            MBServerStatics.SPATIAL_HASH_BUCKETSY = image.getHeight();
+            realmMap = new int[MBServerStatics.SPATIAL_HASH_BUCKETSX][MBServerStatics.SPATIAL_HASH_BUCKETSY];
+        } catch (IOException e) {
+            Logger.error( "Error loading realm map: " + e.toString());
+            return null;
+        }
+
+        // Flip image on the y axis
+        
+        image = flipImage(image);
+        
+        // Initialize color lookup table
+
+        for (RealmType realm : RealmType.values()) {
+            realm.addToColorMap();
+        }
+
+        // Load spatial imageMap with color data from file
+
+        for (int i = 0; i < MBServerStatics.SPATIAL_HASH_BUCKETSY; i++) {
+            for (int j = 0; j < MBServerStatics.SPATIAL_HASH_BUCKETSX; j++) {
+				try {
+					int rgb = image.getRGB(j, i);
+					realmUUID = RealmType.getRealmIDByRGB(rgb);
+
+                if (realmUUID == null) {
+                    Logger.error("Corrupted png: unknown color " + rgb);
+                    WorldServer.shutdown();
+                }
+                
+                realmMap[j][i] = realmUUID.intValue();
+                bytesRead++;
+
+                if (realmUUID.intValue() != 0)
+                    realmsWritten++;
+
+				}catch (Exception e){
+					//					Logger.error("REALMEDIT ERROR", e.getMessage());
+					continue;
+				}
+
+
+            }
+        }
+        timeToLoad = System.currentTimeMillis() - timeToLoad;
+
+        Logger.info( bytesRead + " pixels processed in " + timeToLoad / 1000 + " seconds");
+        Logger.info("Realm pixels written : " + realmsWritten);
+        image = null;
+        return realmMap;
+    }
+
+    public static BufferedImage flipImage(BufferedImage img) {
+        
+        int w = img.getWidth();
+        int h = img.getHeight();
+        
+        BufferedImage dimg = new BufferedImage(w, h, img.getColorModel()
+                .getTransparency());
+        
+        Graphics2D g = dimg.createGraphics();
+        g.drawImage(img, 0, 0, w, h, 0, h, w, 0, null);
+        g.dispose();
+        return dimg;
+    }
+}
diff --git a/src/engine/util/MiscUtils.java b/src/engine/util/MiscUtils.java
new file mode 100644
index 00000000..550d21f5
--- /dev/null
+++ b/src/engine/util/MiscUtils.java
@@ -0,0 +1,94 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.util;
+
+import engine.server.MBServerStatics;
+
+import java.util.regex.Pattern;
+
+
+public class MiscUtils {
+
+	// no need to recompile these each call, put them in object scope and
+	// compile just once.
+	private static final Pattern lastNameRegex = Pattern
+			.compile("^[A-Za-z][-'A-Za-z\\x20]*$");
+	private static final Pattern firstNameRegex = Pattern
+			.compile("^[A-Za-z]+$");
+
+	public static boolean checkIfFirstNameInvalid(String firstName) {
+		if ((firstName == null) || (firstName.length() == 0)
+				|| (firstName.length() > MBServerStatics.MAX_NAME_LENGTH)
+				|| (firstName.length() < MBServerStatics.MIN_NAME_LENGTH)) {
+			return true;
+		}
+		return (!firstNameRegex.matcher(firstName).matches());
+	}
+
+	public static boolean checkIfLastNameInvalid(String lastName) {
+		if ((lastName != null) && (lastName.length() != 0)) {
+			// make sure it's less than max length
+            return lastName.length() > MBServerStatics.MAX_NAME_LENGTH;
+			// first character: A-Z, a-z
+			// remaining chars (optional): hyphen, apostrophe, A-Z, a-z, space
+//			return (!lastNameRegex.matcher(lastName).matches());
+		}
+		// empty last names are fine, return false
+		return false;
+	}
+
+	public static String getCallingMethodName() {
+		StackTraceElement e[] = Thread.currentThread().getStackTrace();
+		int numElements = e.length;
+
+		if (numElements < 1) {
+			return "NoStack";
+		}
+
+		if (numElements == 1) {
+			return e[0].getMethodName();
+		} else if (numElements == 2) {
+			return e[1].getMethodName();
+		} else if (numElements == 3) {
+			return e[2].getMethodName();
+		} else {
+			return e[3].getMethodName();
+		}
+	}
+
+	public static String getCallStackAsString() {
+		String out = "";
+
+		StackTraceElement e[] = Thread.currentThread().getStackTrace();
+		int numElements = e.length;
+
+		for (int i = (numElements - 1); i > 1; --i) {
+
+			String[] classStack = e[i].getClassName().split("\\.");
+			String methName = e[i].getMethodName();
+
+			String className = classStack[classStack.length - 1];
+
+			if (methName.equals("<init>")) {
+				methName = className;
+			}
+
+			out += className + '.' + methName + "()";
+
+			if (i > 2) {
+				out += " -> ";
+			}
+
+		}
+
+		return out;
+	}
+
+}
diff --git a/src/engine/util/StringUtils.java b/src/engine/util/StringUtils.java
new file mode 100644
index 00000000..e8cbbe66
--- /dev/null
+++ b/src/engine/util/StringUtils.java
@@ -0,0 +1,150 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+ package engine.util;
+
+import java.net.InetAddress;
+import java.util.StringTokenizer;
+
+public class StringUtils {
+
+	public static String addWS(String s, int totalLen) {
+		if (s.length() >= totalLen) {
+			return s;
+		}
+
+		int diff = totalLen - s.length();
+
+		String out = s;
+
+		for (int i = 0; i < diff; ++i) {
+			out += " ";
+		}
+		return out;
+	}
+
+	public static String bannerize(String s, int totalLen) {
+		if (s.length() >= totalLen) {
+			return s;
+		}
+
+		int diff = totalLen - s.length();
+		int halfDiff = diff / 2;
+
+		String side = "";
+		for (int i = 0; i < halfDiff; ++i) {
+			side += "*";
+		}
+
+		return side + ' ' + s + ' ' + side;
+	}
+
+	public static String InetAddressToClientString(InetAddress address) {
+		return address.toString().replaceAll("/", "");
+	}
+
+	public static String toHexString(int i) {
+		return Integer.toHexString(i).toUpperCase();
+	}
+
+	public static String toHexString(long l) {
+		return Long.toHexString(l).toUpperCase();
+	}
+
+	// Well done IDA Pro.
+
+	public static int hashString(String toHash) {
+		byte[] hashArray = toHash.getBytes();
+		int hash = 0;
+		int shift = 0;
+		if (hashArray.length == 8 ||hashArray.length == 7){
+			int ecx = 0;
+			if (hashArray.length == 8){
+				ecx = hashArray[7];
+			}
+			int eax = hashArray[4];
+			int esi = ecx * 0x8;
+            eax ^= esi;
+            ecx ^= 0x5A0;
+			esi = hashArray[5];
+            eax <<= 4;
+            eax ^= esi;
+			esi = hashArray[6];
+            eax <<= 4;
+            eax ^= esi;
+			esi = hashArray[2];
+            eax <<= 5;
+            eax ^= esi;
+			esi = hashArray[1];
+			int edx = hashArray[0];
+            eax <<= 5;
+            eax ^= esi;
+            ecx /= 2;
+            ecx /= 2;
+            eax <<= 5;
+            ecx ^= edx;
+            eax ^= ecx;
+			return eax;
+		}else{
+
+			for (int i = 0; i<hashArray.length;i++){
+				if (i == 0)
+					shift = 0;
+				else
+                    shift += 5;
+				int toShift = hashArray[i] - 0x20;
+				int shifted = (toShift<<shift);
+                hash ^= shifted;
+				if (shift > 24){
+					int newShift = 0x20 - shift;
+					int newShifted = toShift >> newShift;
+                    hash ^= newShifted;
+					if (shift > 27){
+                        shift -= 0x20;
+					}
+				}
+			}
+			return hash;
+		}
+	}
+
+         public static String wordWrap(String text,int LineWidth)
+	{
+		StringTokenizer st=new StringTokenizer(text);
+		int SpaceLeft=LineWidth;
+		int SpaceWidth=80;
+                String outString = "";
+                
+		while(st.hasMoreTokens())
+		{
+			String word=st.nextToken();
+			if((word.length()+SpaceWidth)>SpaceLeft)
+			{
+				outString+= '\n' +word+ ' ';
+				SpaceLeft=LineWidth-word.length();
+			}
+			else
+			{
+				outString+=word+ ' ';
+				SpaceLeft-=(word.length()+SpaceWidth);
+			}
+                
+		}
+                
+                return outString;
+
+}
+         
+public static String truncate(String input, int length) {
+  if (input != null && input.length() > length)
+    input = input.substring(0, length);
+  return input;
+}
+   
+}
diff --git a/src/engine/util/ThreadUtils.java b/src/engine/util/ThreadUtils.java
new file mode 100644
index 00000000..8b802141
--- /dev/null
+++ b/src/engine/util/ThreadUtils.java
@@ -0,0 +1,43 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.util;
+
+import org.pmw.tinylog.Logger;
+
+
+public abstract class ThreadUtils {
+
+	private ThreadUtils() {
+	}
+
+	/**
+	 * Force the current thread to sleep for <i>sec</i> seconds and <i>ms</i>
+	 * milliseconds.
+	 * 
+	 *
+	 */
+	public static void sleep(int sec, long ms) {
+		try {
+			Thread.sleep((1000L * sec) + ms);
+		} catch (InterruptedException e) {
+			Logger.error( e.toString());
+		}
+	}
+
+	/**
+	 * Force the current thread to sleep for <i>ms</i> milliseconds.
+	 * 
+	 *
+	 */
+	public static void sleep(long ms) {
+		ThreadUtils.sleep(0, ms);
+	}
+
+}
diff --git a/src/engine/workthreads/DestroyCityThread.java b/src/engine/workthreads/DestroyCityThread.java
new file mode 100644
index 00000000..47e3d97f
--- /dev/null
+++ b/src/engine/workthreads/DestroyCityThread.java
@@ -0,0 +1,155 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.workthreads;
+
+/*
+ * This thread is spawned to process destruction
+ * of a player owned city, including subguild
+ * and database cleanup.
+ *
+ * The 'destroyed' city zone persists until the
+ * next reboot.
+ */
+
+import engine.Enum;
+import engine.gameManager.DbManager;
+import engine.gameManager.GuildManager;
+import engine.gameManager.ZoneManager;
+import engine.math.Vector3fImmutable;
+import engine.objects.Building;
+import engine.objects.City;
+import engine.objects.Guild;
+import engine.objects.Zone;
+import org.pmw.tinylog.Logger;
+
+import java.util.ArrayList;
+
+public class DestroyCityThread implements Runnable {
+
+	City city;
+
+	public DestroyCityThread(City city) {
+
+		this.city = city;
+	}
+
+	public void run(){
+
+		// Member variable declaration
+
+		Zone cityZone;
+		Zone newParent;
+		Guild formerGuild;
+		Vector3fImmutable localCoords;
+		ArrayList<Guild> subGuildList;
+
+		// Member variable assignment
+
+		cityZone = city.getParent();
+		newParent = cityZone.getParent();
+		formerGuild = city.getTOL().getGuild();
+
+		// Former guild loses it's tree!
+
+		if (DbManager.GuildQueries.SET_GUILD_OWNED_CITY(formerGuild.getObjectUUID(), 0)) {
+
+			//Successful Update of guild
+
+			formerGuild.setGuildState(engine.Enum.GuildState.Errant);
+			formerGuild.setNation(null);
+			formerGuild.setCityUUID(0);
+			GuildManager.updateAllGuildTags(formerGuild);
+			GuildManager.updateAllGuildBinds(formerGuild, null);
+		}
+
+		// By losing the tree, the former owners lose all of their subguilds.
+
+		if (formerGuild.getSubGuildList().isEmpty() == false) {
+
+			subGuildList = new ArrayList<>();
+
+			for (Guild subGuild : formerGuild.getSubGuildList()) {
+				subGuildList.add(subGuild);
+			}
+
+			for (Guild subGuild : subGuildList) {
+				formerGuild.removeSubGuild(subGuild);
+			}
+		}
+
+		// Build list of buildings within this parent zone
+
+		for (Building cityBuilding : cityZone.zoneBuildingSet) {
+
+			// Sanity Check in case player deletes the building
+			// before this thread can get to it
+
+			if (cityBuilding == null)
+				continue;
+
+			// Do nothing with the banestone.  It will be removed elsewhere
+
+			if (cityBuilding.getBlueprint().getBuildingGroup().equals(Enum.BuildingGroup.BANESTONE))
+				continue;
+			
+			// All buildings are moved to a location relative
+			// to their new parent zone
+
+			localCoords = ZoneManager.worldToLocal(cityBuilding.getLoc(), newParent);
+
+			DbManager.BuildingQueries.MOVE_BUILDING(cityBuilding.getObjectUUID(), newParent.getObjectUUID(), localCoords.x, localCoords.y, localCoords.z);
+
+			// All buildings are re-parented to a zone one node
+			// higher in the tree (continent) as we will be
+			// deleting the city zone very shortly.
+
+			if (cityBuilding.getParentZoneID() != newParent.getParentZoneID())
+				cityBuilding.setParentZone(newParent);
+
+			// No longer a tree, no longer any protection contract!
+
+			cityBuilding.setProtectionState(Enum.ProtectionState.NONE);
+
+			// Destroy all remaining city assets
+
+			if ((cityBuilding.getBlueprint().getBuildingGroup() == Enum.BuildingGroup.BARRACK)
+					|| (cityBuilding.getBlueprint().isWallPiece())
+					|| (cityBuilding.getBlueprint().getBuildingGroup() == Enum.BuildingGroup.SHRINE)
+					|| (cityBuilding.getBlueprint().getBuildingGroup() == Enum.BuildingGroup.TOL)
+					|| (cityBuilding.getBlueprint().getBuildingGroup() == Enum.BuildingGroup.SPIRE)
+					|| (cityBuilding.getBlueprint().getBuildingGroup() == Enum.BuildingGroup.WAREHOUSE)) {
+
+				if (cityBuilding.getRank() != -1)
+					cityBuilding.setRank(-1);
+			}
+		}
+
+		if (city.getRealm() != null)
+			city.getRealm().removeCity(city.getObjectUUID());
+
+		// It's now safe to delete the city zone from the database
+		// which will cause a cascade delete of everything else
+
+
+		if (DbManager.ZoneQueries.DELETE_ZONE(cityZone) == false) {
+			Logger.error("DestroyCityThread", "Database error when deleting city zone: " + cityZone.getObjectUUID());
+			return;
+		}
+
+		// Refresh the city for map requests
+
+		City.lastCityUpdate = System.currentTimeMillis();
+
+		// Zone and city should vanish upon next reboot
+		// if the codebase reaches here.
+
+		Logger.info(city.getParent().getName() + " uuid:" + city.getObjectUUID() + "has been destroyed!");
+	}
+}
diff --git a/src/engine/workthreads/DisconnectTrashTask.java b/src/engine/workthreads/DisconnectTrashTask.java
new file mode 100644
index 00000000..97a5f012
--- /dev/null
+++ b/src/engine/workthreads/DisconnectTrashTask.java
@@ -0,0 +1,52 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+package engine.workthreads;
+
+import engine.Enum;
+import engine.gameManager.DbManager;
+import engine.gameManager.SessionManager;
+import engine.objects.PlayerCharacter;
+import engine.session.Session;
+import org.pmw.tinylog.Logger;
+
+import java.util.ArrayList;
+import java.util.TimerTask;
+
+public class DisconnectTrashTask extends TimerTask {
+
+    private final ArrayList<PlayerCharacter> trashList;
+
+    // Pass it a list of characters and it will disconnect them
+    // 5 seconds in the future.
+
+    public DisconnectTrashTask(ArrayList<PlayerCharacter> trashList)
+    {
+        this.trashList = new ArrayList<>(trashList);
+    }
+
+    public void run() {
+
+       Logger.info("Disconnecting actives from pool of: " + trashList.size());
+
+        Session trashSession;
+        int accountUID;
+
+                for (PlayerCharacter trashPlayer:trashList) {
+                    trashSession = SessionManager.getSession(trashPlayer);
+                    accountUID = trashPlayer.getAccount().getObjectUUID();
+
+                    if (trashSession != null)
+                        trashSession.getConn().disconnect();
+
+                    // Remove account from cache
+
+                    DbManager.removeFromCache(Enum.GameObjectType.Account, accountUID);
+                }
+            };
+}
diff --git a/src/engine/workthreads/HourlyJobThread.java b/src/engine/workthreads/HourlyJobThread.java
new file mode 100644
index 00000000..9ea37d97
--- /dev/null
+++ b/src/engine/workthreads/HourlyJobThread.java
@@ -0,0 +1,131 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.workthreads;
+
+import engine.Enum;
+import engine.gameManager.DbManager;
+import engine.gameManager.SimulationManager;
+import engine.gameManager.ZoneManager;
+import engine.net.MessageDispatcher;
+import engine.objects.*;
+import engine.server.world.WorldServer;
+import org.pmw.tinylog.Logger;
+
+import java.time.LocalDateTime;
+import java.util.ArrayList;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class HourlyJobThread implements Runnable {
+
+	private static int hotzoneCount = 0;
+
+	public HourlyJobThread() {
+
+	}
+
+	public void run() {
+
+		// *** REFACTOR: TRY TRY TRY TRY {{{{{{{{{{{ OMG
+
+		Logger.info("Hourly job is now running.");
+
+			try {
+
+					ZoneManager.generateAndSetRandomHotzone();
+					Zone hotzone = ZoneManager.getHotZone();
+
+					if (hotzone == null) {
+						Logger.error( "Null hotzone returned from mapmanager");
+					} else {
+						Logger.info( "new hotzone: " + hotzone.getName());
+						WorldServer.setLastHZChange(System.currentTimeMillis());
+					}
+
+			} catch (Exception e) {
+				Logger.error( e.toString());
+			}
+
+			//updateMines.
+			try {
+
+				// Update mine effective date if this is a midnight window
+
+				if (LocalDateTime.now().getHour() == 0 || LocalDateTime.now().getHour() == 24)
+					Mine.effectiveMineDate = LocalDateTime.now().withHour(0).withMinute(0).withSecond(0);
+
+				ArrayList<Mine> mines = Mine.getMines();
+				LocalDateTime now = LocalDateTime.now();
+
+				for (Mine mine : mines) {
+					try {
+
+						if (mine.getOwningGuild() == null) {
+							mine.handleStartMineWindow();
+							Mine.setLastChange(System.currentTimeMillis());
+							continue;
+						}
+
+						//handle claimed mines
+                        LocalDateTime mineWindow = mine.openDate.withMinute(0).withSecond(0).withNano(0);
+
+						if (mineWindow != null && now.plusMinutes(1).isAfter(mineWindow))
+							if (!mine.getIsActive()) {
+								mine.handleStartMineWindow();
+								Mine.setLastChange(System.currentTimeMillis());
+
+							}
+							else if (mine.handleEndMineWindow())
+								Mine.setLastChange(System.currentTimeMillis());
+					} catch (Exception e) {
+						Logger.error ("mineID: " + mine.getObjectUUID(), e.toString());
+					}
+				}
+			} catch (Exception e) {
+				Logger.error( e.toString());
+			}
+
+			for (Mine mine : Mine.getMines()) {
+
+				try {
+					mine.depositMineResources();
+				} catch (Exception e) {
+					Logger.info(e.getMessage() + " for Mine " + mine.getObjectUUID());
+				}
+			}
+
+
+			// Update city population values
+
+			ConcurrentHashMap<Integer, AbstractGameObject> map = DbManager.getMap(Enum.GameObjectType.City);
+
+			if (map != null) {
+
+				for (AbstractGameObject ago : map.values()){
+
+					City city = (City)ago;
+
+					if (city != null)
+						if (city.getGuild() != null) {
+							ArrayList<PlayerCharacter> guildList = Guild.GuildRoster(city.getGuild());
+							city.setPopulation(guildList.size());
+						}
+				}
+				City.lastCityUpdate = System.currentTimeMillis();
+			} else {
+				Logger.error("missing city map");
+			}
+
+			// Log metrics to console
+			Logger.info( WorldServer.getUptimeString());
+			Logger.info( SimulationManager.getPopulationString());
+			Logger.info( MessageDispatcher.getNetstatString());
+			Logger.info(PurgeOprhans.recordsDeleted.toString() + "orphaned items deleted");
+	}
+}
diff --git a/src/engine/workthreads/PurgeOprhans.java b/src/engine/workthreads/PurgeOprhans.java
new file mode 100644
index 00000000..c75f4d69
--- /dev/null
+++ b/src/engine/workthreads/PurgeOprhans.java
@@ -0,0 +1,65 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+package engine.workthreads;
+
+import engine.db.archive.DataWarehouse;
+import org.pmw.tinylog.Logger;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.util.concurrent.atomic.LongAdder;
+
+/*
+ * This thread runs at bootstrap to ensure cleanup of
+ * orphaned items (deleted items).  God does this mess
+ * ever need to be refactored and re-use of item uuid's
+ * implemented.
+ */
+public class PurgeOprhans implements Runnable {
+
+    public static LongAdder recordsDeleted = new LongAdder();
+
+    public PurgeOprhans() {
+
+        recordsDeleted.reset();
+
+    }
+
+    public static void startPurgeThread() {
+
+        Thread purgeOrphans;
+        purgeOrphans = new Thread(new PurgeOprhans());
+
+        purgeOrphans.setName("purgeOrphans");
+        purgeOrphans.start();
+    }
+
+    public void run() {
+
+        // Member variable declaration
+
+        try (
+                Connection connection = DataWarehouse.connectionPool.getConnection();
+                PreparedStatement statement = connection.prepareStatement("SELECT * from `object` where `type` = 'item' AND `parent` IS NULL", ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE);
+                ResultSet rs = statement.executeQuery()) {
+
+            while (rs.next()) {
+                rs.deleteRow();
+                recordsDeleted.increment();
+            }
+
+        } catch (Exception e) {
+            Logger.error( e.toString());
+        }
+
+        Logger.info("Thread is exiting with " + recordsDeleted.toString() + " items deleted");
+    }
+
+}
diff --git a/src/engine/workthreads/TransferCityThread.java b/src/engine/workthreads/TransferCityThread.java
new file mode 100644
index 00000000..ab2d049a
--- /dev/null
+++ b/src/engine/workthreads/TransferCityThread.java
@@ -0,0 +1,99 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+package engine.workthreads;
+
+/*
+ * This thread is spawned to process transfer
+ * ownership of a player owned city, including
+ * subguild and database cleanup.
+ *
+ */
+
+import engine.Enum;
+import engine.gameManager.BuildingManager;
+import engine.gameManager.DbManager;
+import engine.gameManager.GuildManager;
+import engine.net.DispatchMessage;
+import engine.net.client.msg.CityZoneMsg;
+import engine.objects.AbstractCharacter;
+import engine.objects.City;
+import engine.objects.Guild;
+import org.pmw.tinylog.Logger;
+
+import java.util.ArrayList;
+
+public class TransferCityThread implements Runnable {
+
+    City city;
+    AbstractCharacter newOwner;
+
+    public TransferCityThread(City city, AbstractCharacter newOwner) {
+
+        this.city = city;
+        this.newOwner = newOwner;
+    }
+
+    public void run(){
+
+        Guild formerGuild;
+        ArrayList<Guild> subGuildList;
+
+        formerGuild = this.city.getTOL().getGuild();
+
+        // Former guild loses it's tree!
+
+        if (formerGuild != null)
+            if (DbManager.GuildQueries.SET_GUILD_OWNED_CITY(formerGuild.getObjectUUID(), 0)) {
+                formerGuild.setGuildState(Enum.GuildState.Errant);
+                formerGuild.setNation(null);
+                formerGuild.setCityUUID(0);
+                GuildManager.updateAllGuildTags(formerGuild);
+                GuildManager.updateAllGuildBinds(formerGuild, null);
+            }
+
+        // By losing the tree, the former owners lose all of their subguilds.
+
+        if (formerGuild.getSubGuildList().isEmpty() == false) {
+
+            subGuildList = new ArrayList<>();
+
+            for (Guild subGuild : formerGuild.getSubGuildList()) {
+                subGuildList.add(subGuild);
+            }
+
+            for (Guild subGuild : subGuildList) {
+                formerGuild.removeSubGuild(subGuild);
+            }
+        }
+
+        //Reset TOL to rank 1
+
+        city.getTOL().setRank(1);
+
+        // Transfer all assets to new owner
+
+        city.claim(newOwner);
+
+        //Set name of City to attacker's guild name
+        BuildingManager.setUpgradeDateTime(city.getTOL(),null, 0);
+        city.getTOL().setName(newOwner.getGuild().getName());
+
+        // Send updated cityZone to players
+        CityZoneMsg czm = new CityZoneMsg(2, city.getTOL().getLoc().x, city.getTOL().getLoc().y, city.getTOL().getLoc().z, city.getTOL().getName(), city.getTOL().getParentZone(), 0f, 0f);
+
+        DispatchMessage.dispatchMsgToAll(czm);
+
+        // Reset city timer for map update
+
+        City.lastCityUpdate = System.currentTimeMillis();
+
+        Logger.info("uuid:" + city.getObjectUUID() + "transferred from " + formerGuild.getName() +
+                       " to " + newOwner.getGuild().getName());
+    }
+}
diff --git a/src/engine/workthreads/WarehousePushThread.java b/src/engine/workthreads/WarehousePushThread.java
new file mode 100644
index 00000000..39757da5
--- /dev/null
+++ b/src/engine/workthreads/WarehousePushThread.java
@@ -0,0 +1,450 @@
+// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
+// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
+// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
+// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
+// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
+//      Magicbane Emulator Project © 2013 - 2022
+//                www.magicbane.com
+
+
+package engine.workthreads;
+
+/*
+ * This thread pushes cumulative warehouse data to
+ * a remote database.
+ *
+ */
+
+import engine.Enum;
+import engine.db.archive.*;
+import engine.gameManager.ConfigManager;
+import org.pmw.tinylog.Logger;
+
+import java.sql.*;
+
+public class WarehousePushThread implements Runnable {
+
+	// Used to track last push.  These are read
+	// at thread startup and written back out
+	// when we're done
+
+	public static int charIndex, charDelta;
+	public static int cityIndex, cityDelta;
+	public static int guildIndex, guildDelta;
+	public static int realmIndex, realmDelta;
+	public static int baneIndex, baneDelta;
+	public static int pvpIndex, pvpDelta;
+	public static int mineIndex, mineDelta;
+
+	public WarehousePushThread() {
+
+	}
+
+	public void run() {
+
+		int recordCount = 0;
+		boolean writeSuccess = true;
+
+        if ( ConfigManager.MB_WORLD_WAREHOUSE_PUSH.getValue().equals("false")) {
+            Logger.info("WAREHOUSEPUSH DISABLED: EARLY EXIT");
+            return;
+        }
+
+		// Cache where we left off from the last push
+		// for each of the warehouse tables
+
+		if (readWarehouseIndex() == false)
+			return;
+
+		// Log run to console
+
+		Logger.info( "Pushing records to remote...");
+
+		// Push records to remote database
+
+		for (Enum.DataRecordType recordType : Enum.DataRecordType.values()) {
+
+			switch (recordType) {
+			case PVP:
+				if (pushPvpRecords() == true) {
+					recordCount = Math.max(0, pvpDelta - pvpIndex);
+					pvpIndex += recordCount;
+				} else
+					writeSuccess = false;
+				break;
+			case CHARACTER:
+				if (pushCharacterRecords() == true) {
+					recordCount = Math.max(0, charDelta - charIndex);
+					charIndex += recordCount;
+				} else
+					writeSuccess = false;
+				break;
+			case REALM:
+				if (pushRealmRecords() == true) {
+					recordCount = Math.max(0, realmDelta - realmIndex);
+					realmIndex += recordCount;
+				}
+				else
+					writeSuccess = false;
+				break;
+			case GUILD:
+				if (pushGuildRecords() == true) {
+					recordCount = Math.max(0, guildDelta - guildIndex);
+					guildIndex += recordCount;
+				}
+				else
+					writeSuccess = false;
+				break;
+			case BANE:
+				if (pushBaneRecords() == true) {
+					recordCount = Math.max(0, baneDelta - baneIndex);
+					baneIndex += recordCount;
+				}
+				else
+					writeSuccess = false;
+				break;
+			case CITY:
+				if (pushCityRecords() == true) {
+					recordCount = Math.max(0, cityDelta - cityIndex);
+					cityIndex += recordCount;
+				} else
+					writeSuccess = false;
+				break;
+			case MINE:
+				if (pushMineRecords() == true) {
+					recordCount = Math.max(0, mineDelta - mineIndex);
+					mineIndex += recordCount;
+				} else
+					writeSuccess = false;
+				break;
+			default:
+				recordCount = 0;
+				writeSuccess = false;
+				break; // unhandled type
+			}
+
+			if (writeSuccess == true)
+				Logger.info( recordCount + " " + recordType.name() + " records sent to remote");
+			else
+				Logger.info( recordCount + " returning failed success");
+
+		}  // Iterate switch
+
+		// Update indices
+
+		updateWarehouseIndex();
+
+		// Update dirty records
+
+		Logger.info( "Pushing updates of dirty warehouse records");
+		CharacterRecord.updateDirtyRecords();
+
+		if (charDelta > 0)
+			Logger.info( charDelta + " dirty character records were sent");
+		;
+		BaneRecord.updateDirtyRecords();
+
+		if (baneDelta > 0)
+			Logger.info( baneDelta + " dirty bane records were sent");
+
+		Logger.info( "Process has completed");
+
+	}
+
+	public static boolean pushMineRecords() {
+
+		try (Connection localConnection = DataWarehouse.connectionPool.getConnection();
+				PreparedStatement statement = MineRecord.buildMineQueryStatement(localConnection);
+				ResultSet rs = statement.executeQuery()) {
+
+			while (rs.next()) {
+				pushMineRecord(rs);
+				mineDelta = rs.getInt("event_number");
+			}
+
+			return true;
+		} catch (SQLException e) {
+			Logger.error( "Error with local DB connection: " + e.toString());
+			e.printStackTrace();
+			return false;
+		}
+	}
+
+	public static boolean pushCharacterRecords() {
+
+		try (Connection localConnection = DataWarehouse.connectionPool.getConnection();
+				PreparedStatement statement = CharacterRecord.buildCharacterQueryStatement(localConnection);
+				ResultSet rs = statement.executeQuery()) {
+
+			while (rs.next()) {
+				pushCharacterRecord(rs);
+				charDelta = rs.getInt("event_number");
+			}
+
+			return true;
+		} catch (SQLException e) {
+			Logger.error( "Error with local DB connection: " + e.toString());
+			e.printStackTrace();
+			return false;
+		}
+	}
+
+	private static boolean pushGuildRecords() {
+
+		try (Connection localConnection = DataWarehouse.connectionPool.getConnection();
+				PreparedStatement statement = GuildRecord.buildGuildQueryStatement(localConnection);
+				ResultSet rs = statement.executeQuery()) {
+
+			while (rs.next()) {
+				pushGuildRecord(rs);
+				guildDelta = rs.getInt("event_number");
+			}
+
+			return true;
+		} catch (SQLException e) {
+			Logger.error("Error with local DB connection: " + e.toString());
+			e.printStackTrace();
+			return false;
+		}
+	}
+
+	private static boolean pushMineRecord(ResultSet rs) {
+
+		try (Connection remoteConnection = DataWarehouse.remoteConnectionPool.getConnection();
+				PreparedStatement statement = MineRecord.buildMinePushStatement(remoteConnection, rs)) {
+
+			statement.execute();
+			return true;
+
+		} catch (SQLException e) {
+			Logger.error( e.toString());
+			return false;
+		}
+	}
+
+	private static boolean pushGuildRecord(ResultSet rs) {
+
+		try (Connection remoteConnection = DataWarehouse.remoteConnectionPool.getConnection();
+				PreparedStatement statement = GuildRecord.buildGuildPushStatement(remoteConnection, rs)) {
+
+			statement.execute();
+			return true;
+
+		} catch (SQLException e) {
+			Logger.error(e.toString());
+			return false;
+		}
+	}
+
+	private static boolean pushBaneRecords() {
+
+		try (Connection localConnection = DataWarehouse.connectionPool.getConnection();
+				PreparedStatement statement = BaneRecord.buildBaneQueryStatement(localConnection);
+				ResultSet rs = statement.executeQuery()) {
+
+			while (rs.next()) {
+				pushBaneRecord(rs);
+				baneDelta = rs.getInt("event_number");
+			}
+
+			return true;
+		} catch (SQLException e) {
+			Logger.error("Error with local DB connection: " + e.toString());
+			e.printStackTrace();
+			return false;
+		}
+	}
+
+	private static boolean pushBaneRecord(ResultSet rs) {
+
+		try (Connection remoteConnection = DataWarehouse.remoteConnectionPool.getConnection();
+				PreparedStatement statement = BaneRecord.buildBanePushStatement(remoteConnection, rs)) {
+
+			statement.execute();
+			return true;
+
+		} catch (SQLException e) {
+			Logger.error(e.toString());
+			return false;
+		}
+	}
+
+	private static boolean pushCityRecords() {
+
+		try (Connection localConnection = DataWarehouse.connectionPool.getConnection();
+				PreparedStatement statement = CityRecord.buildCityQueryStatement(localConnection);
+				ResultSet rs = statement.executeQuery()) {
+
+			while (rs.next()) {
+				pushCityRecord(rs);
+				cityDelta = rs.getInt("event_number");
+			}
+
+			return true;
+		} catch (SQLException e) {
+			Logger.error( "Error with local DB connection: " + e.toString());
+			e.printStackTrace();
+			return false;
+		}
+	}
+
+	private static boolean pushPvpRecords() {
+
+		try (Connection localConnection = DataWarehouse.connectionPool.getConnection();
+				PreparedStatement statement = PvpRecord.buildPvpQueryStatement(localConnection);
+				ResultSet rs = statement.executeQuery()) {
+
+			while (rs.next()) {
+
+				if (pushPvpRecord(rs) == true)
+					pvpDelta = rs.getInt("event_number");
+			}
+
+			return true;
+		} catch (SQLException e) {
+			Logger.error("Error with local DB connection: " + e.toString());
+			return false;
+		}
+	}
+
+	private static boolean pushPvpRecord(ResultSet rs) {
+
+		try (Connection remoteConnection = DataWarehouse.remoteConnectionPool.getConnection();
+				PreparedStatement statement = PvpRecord.buildPvpPushStatement(remoteConnection, rs)) {
+
+			statement.execute();
+			return true;
+		} catch (SQLException e) {
+			Logger.error(e.toString());
+			return false;
+		}
+
+	}
+
+	private static boolean pushRealmRecords() {
+
+		try (Connection localConnection = DataWarehouse.connectionPool.getConnection();
+				PreparedStatement statement = RealmRecord.buildRealmQueryStatement(localConnection);
+				ResultSet rs = statement.executeQuery()) {
+
+			while (rs.next()) {
+
+				if (pushRealmRecord(rs) == true)
+					realmDelta = rs.getInt("event_number");
+			}
+
+			return true;
+		} catch (SQLException e) {
+			Logger.error( "Error with local DB connection: " + e.toString());
+			e.printStackTrace();
+			return false;
+		}
+	}
+
+	private static boolean pushRealmRecord(ResultSet rs) {
+
+		try (Connection remoteConnection = DataWarehouse.remoteConnectionPool.getConnection();
+				PreparedStatement statement = RealmRecord.buildRealmPushStatement(remoteConnection, rs)) {
+			statement.execute();
+			return true;
+
+		} catch (SQLException e) {
+			Logger.error( e.toString());
+			return false;
+		}
+
+	}
+
+	private static boolean pushCharacterRecord(ResultSet rs) {
+
+		try (Connection remoteConnection = DataWarehouse.remoteConnectionPool.getConnection();
+				PreparedStatement statement = CharacterRecord.buildCharacterPushStatement(remoteConnection, rs)) {
+
+			statement.execute();
+			return true;
+
+		} catch (SQLException e) {
+			Logger.error(e.toString());
+			return false;
+		}
+
+	}
+
+	private static boolean pushCityRecord(ResultSet rs) {
+
+		try (Connection remoteConnection = DataWarehouse.remoteConnectionPool.getConnection();
+				PreparedStatement statement = CityRecord.buildCityPushStatement(remoteConnection, rs)) {
+
+			statement.execute();
+			return true;
+
+		} catch (SQLException e) {
+			Logger.error( e.toString());
+			return false;
+		}
+	}
+
+	private static boolean readWarehouseIndex() {
+
+		// Member variable declaration
+
+		String queryString;
+
+		queryString = "SELECT * FROM `warehouse_index`";
+
+		try (Connection localConnection = DataWarehouse.connectionPool.getConnection();
+				CallableStatement statement = localConnection.prepareCall(queryString);
+				ResultSet rs = statement.executeQuery()) {
+
+			while (rs.next()) {
+				charIndex = rs.getInt("charIndex");
+				cityIndex = rs.getInt("cityIndex");
+				guildIndex = rs.getInt("guildIndex");
+				realmIndex = rs.getInt("realmIndex");
+				baneIndex = rs.getInt("baneIndex");
+				pvpIndex = rs.getInt("pvpIndex");
+				mineIndex = rs.getInt("mineIndex");
+			}
+
+			return true;
+
+		} catch (SQLException e) {
+			Logger.error( "Error reading warehouse index" + e.toString());
+			e.printStackTrace();
+			return false;
+		}
+	}
+
+	private static boolean updateWarehouseIndex() {
+
+		try (Connection connection = DataWarehouse.connectionPool.getConnection();
+				PreparedStatement statement = WarehousePushThread.buildIndexUpdateStatement(connection)) {
+
+			statement.execute();
+			return true;
+
+		} catch (SQLException e) {
+			Logger.error( e.toString());
+			return false;
+		}
+	}
+
+	private static PreparedStatement buildIndexUpdateStatement(Connection connection) throws SQLException {
+
+		PreparedStatement outStatement = null;
+		String queryString = "UPDATE `warehouse_index` SET `charIndex` = ?, `cityIndex` = ?, `guildIndex` = ?, `realmIndex` = ?, `baneIndex` = ?, `pvpIndex` = ?, `mineIndex` = ?";
+		outStatement = connection.prepareStatement(queryString);
+
+		// Bind record data
+
+		outStatement.setInt(1, charIndex);
+		outStatement.setInt(2, cityIndex);
+		outStatement.setInt(3, guildIndex);
+		outStatement.setInt(4, realmIndex);
+		outStatement.setInt(5, baneIndex);
+		outStatement.setInt(6, pvpIndex);
+		outStatement.setInt(7, mineIndex);
+		return outStatement;
+	}
+}
+