// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . // ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· // ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ // ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ // ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ // Magicbane Emulator Project © 2013 - 2022 // www.magicbane.com package engine.gameManager; import engine.InterestManagement.InterestManager; import engine.InterestManagement.WorldGrid; import engine.job.JobContainer; import engine.job.JobScheduler; import engine.jobs.UpgradeBuildingJob; import engine.loot.WorkOrder; import engine.math.Bounds; import engine.math.Vector3fImmutable; import engine.mbEnums; import engine.mbEnums.BuildingGroup; import engine.mbEnums.GameObjectType; import engine.net.client.msg.ErrorPopupMsg; import engine.objects.*; import engine.server.MBServerStatics; import org.pmw.tinylog.Logger; import java.time.LocalDateTime; import java.time.ZoneId; import java.util.ArrayList; import java.util.EnumSet; import java.util.HashMap; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ThreadLocalRandom; public enum BuildingManager { BUILDINGMANAGER; public static HashMap> _stuckLocations = new HashMap<>(); public static HashMap> _slotLocations = new HashMap<>(); public static HashMap> _buildingFriends = new HashMap<>(); public static HashMap> _buildingCondemned = new HashMap<>(); public static HashMap> _buildingPatrolPoints = new HashMap<>(); public static int getAvailableSlot(Building building) { ArrayList slotLocations = _slotLocations.get(building.meshUUID); // Some meshes might not have slot locations assigned. if (slotLocations == null || slotLocations.isEmpty()) return -1; int numOfSlots = _slotLocations.get(building.meshUUID).size(); for (int i = 1; i <= numOfSlots; i++) { if (!building.getHirelings().containsValue(i)) return i; } return -1; } public static int getLastAvailableSlot(Building building) { ArrayList slotLocations = _slotLocations.get(building.meshUUID); // Some meshes might not have slot locations assigned. if (slotLocations == null || slotLocations.isEmpty()) return -1; int numOfSlots = _slotLocations.get(building.meshUUID).size(); for (int i = numOfSlots; i > 0; i--) if (!building.getHirelings().containsValue(i)) return i; return -1; } public static BuildingLocation getSlotLocation(Building building, int slot) { BuildingLocation buildingLocation = new BuildingLocation(); if (slot == -1) return buildingLocation; buildingLocation = _slotLocations.get(building.meshUUID).get(slot - 1); // array index if (buildingLocation == null) Logger.error("Invalid slot for building: " + building.getObjectUUID()); return buildingLocation; } public static boolean playerCanManage(PlayerCharacter player, Building building) { if (player == null) return false; if (building == null) return false; //cannot access destroyed buildings if (building.getRank() == -1) return false; //admin characters can always access buildings if (player.isCSR()) return true; //owner can always access their own building if (IsOwner(building, player)) return true; //check for default IC access if building belongs to same guild if(player.guild.equals(building.getGuild())) { if (building.getBlueprint() != null && building.getBlueprint().getBuildingGroup() != null) { switch (building.getBlueprint().getBuildingGroup()) { case TOL: case BARRACK: case SPIRE: case SHRINE: case BANESTONE: case MINE: case WAREHOUSE: case BULWARK: case SIEGETENT: if (GuildStatusController.isInnerCouncil(player.getGuildStatus())) return true; if (GuildStatusController.isGuildLeader(player.getGuildStatus())) return true; break; } } } //check against friends list entries if any present if (building.getFriends() != null) { //check individuals if (building.getFriends().get(player.getObjectUUID()) != null) return true; if (building.getFriends().get(player.guild.objectUUID) != null) { //check friend type for guild related access switch (building.getFriends().get(player.guild.objectUUID).friendType) { case 8: //full member if (GuildStatusController.isFullMember(player.getGuildStatus())) return true; break; case 9: //Inner Council if (GuildStatusController.isInnerCouncil(player.getGuildStatus())) return true; if (GuildStatusController.isGuildLeader(player.getGuildStatus())) return true; break; } } } //did not meet access grant criteria, deny access 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; } ItemTemplate template = ItemTemplate.templates.get(1705032); // Elan Stone if (!player.charItemManager.hasRoomInventory(template.item_wt)) return false; if (!ItemManager.MakeItemForPlayer(template.template_id, player, amount)) return false; shrine.setFavors(0); break; case WAREHOUSE: City city = building.getCity(); if (city == null) return true; Warehouse warehouse = city.warehouse; if (warehouse == null) return false; for (mbEnums.ResourceType resourceType : EnumSet.allOf(mbEnums.ResourceType.class)) { if (!player.charItemManager.hasRoomInventory(resourceType.template.item_wt)) { ChatManager.chatSystemInfo(player, "You can not carry any more of that item."); return false; } if (warehouse.resources.get(resourceType) == null) continue; int resourceAmount = warehouse.resources.get(resourceType); if (resourceAmount <= 0) continue; if (Warehouse.loot(warehouse, player, resourceType, resourceAmount, true)) { ChatManager.chatInfoInfo(player, "You have looted " + resourceAmount + ' ' + resourceType.name()); } } 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 removeHireling(Building building, AbstractCharacter hireling) { if (hireling.getObjectType().equals(GameObjectType.Mob)) { Mob guardCaptain = (Mob) hireling; // Clear minions from database if a guard captain if (guardCaptain.agentType.equals(mbEnums.AIAgentType.GUARDCAPTAIN)) DbManager.MobQueries.REMOVE_ALL_MINIONS(hireling.getObjectUUID()); } // Clear minions from world for (Integer minionUUID : hireling.minions) { Mob minionMob = Mob.getMob(minionUUID); DbManager.removeFromCache(minionMob); WorldGrid.RemoveWorldObject(minionMob); WorldGrid.unloadObject(minionMob); if (minionMob.parentZone != null) minionMob.parentZone.zoneMobSet.remove(minionMob); } // Clear all workorders for this hireling if (hireling.getObjectType().equals(GameObjectType.NPC)) { NPC hirelingNPC = (NPC) hireling; if (ForgeManager.vendorWorkOrderLookup.get(hirelingNPC) != null) for (WorkOrder workOrder : ForgeManager.vendorWorkOrderLookup.get(hirelingNPC)) { workOrder.runCompleted.set(true); workOrder.vendor = null; // Remove any cooking items from collections // to ensure we don't leak memory. for (Item item : workOrder.cooking) { DbManager.removeFromCache(item); ForgeManager.itemWorkOrderLookup.remove(item); } DbManager.WarehouseQueries.DELETE_WORKORDER(workOrder); } // Finally remove the NPC from ForgeManager ForgeManager.vendorWorkOrderLookup.remove(hirelingNPC); } // Remove hireling from building building.getHirelings().remove(hireling); // Remove from zone mob set if (hireling.getObjectType().equals(GameObjectType.Mob)) { Mob hirelingMob = (Mob) hireling; if (hirelingMob.parentZone != null) if (hirelingMob.parentZone.zoneMobSet.contains(hirelingMob)) hirelingMob.parentZone.zoneMobSet.remove(hireling); } if (hireling.getObjectType().equals(GameObjectType.NPC)) { NPC hirelingNPC = (NPC) hireling; if (hirelingNPC.getParentZone() != null) if (hirelingNPC.getParentZone().zoneNPCSet.contains(hirelingNPC)) hirelingNPC.getParentZone().zoneNPCSet.remove(hireling); } // Unload hireling from world DbManager.removeFromCache(hireling); WorldGrid.RemoveWorldObject(hireling); WorldGrid.removeObject(hireling); // Delete hireling from database if (hireling.getObjectType().equals(GameObjectType.Mob)) DbManager.MobQueries.DELETE_MOB((Mob) hireling); else DbManager.NPCQueries.DELETE_NPC((NPC) 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()) BuildingManager.removeHireling(building, slottedNPC); return; } // Delete hireling if building has deranked. for (AbstractCharacter hireling : building.getHirelings().keySet()) if (building.getHirelings().get(hireling) > building.getBlueprint().getSlotsForRank(building.getRank())) BuildingManager.removeHireling(building, hireling); refreshHirelings(building); } public static Building getBuilding(int id) { if (id == 0) return null; Building building; building = (Building) DbManager.getFromCache(mbEnums.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().isEmptyGuild()) return false; if (building.getGuild().isGuildLeader(player.getObjectUUID())) return true; if (!Guild.sameGuild(building.getGuild(), player.getGuild())) return false; return GuildStatusController.isGuildLeader(player.getGuildStatus()) || GuildStatusController.isInnerCouncil(player.getGuildStatus()); } public static boolean IsPlayerHostile(Building building, PlayerCharacter player) { if (Guild.sameNationExcludeErrant(building.getGuild(), player.getGuild())) return false; if (!building.reverseKOS) { Condemned condemn = building.getCondemned().get(player.getObjectUUID()); if (condemn != null && condemn.active) return true; if (player.getGuild() != null) { Condemned guildCondemn = building.getCondemned().get(player.getGuild().getObjectUUID()); if (guildCondemn != null && guildCondemn.active) return true; Condemned nationCondemn = building.getCondemned().get(player.getGuild().getNation().getObjectUUID()); return nationCondemn != null && nationCondemn.active && nationCondemn.friendType == Condemned.NATION; } else { //TODO ADD ERRANT KOS CHECK } } else { Condemned condemn = building.getCondemned().get(player.getObjectUUID()); if (condemn != null && condemn.active) return false; if (player.getGuild() != null) { Condemned guildCondemn = building.getCondemned().get(player.getGuild().getObjectUUID()); if (guildCondemn != null && guildCondemn.active) return false; Condemned nationCondemn = building.getCondemned().get(player.getGuild().getNation().getObjectUUID()); return nationCondemn == null || !nationCondemn.active || nationCondemn.friendType != 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 = NPCManager.getPirateName(NpcID.getMobbaseID()); NPC npc; npc = NPC.createNPC(pirateName, NpcID.getObjectUUID(), NpcLoc, building.getGuild(), zone, (short) rank, building); if (npc == null) return false; npc.setObjectTypeMask(MBServerStatics.MASK_NPC); npc.setLoc(npc.bindLoc); InterestManager.setObjectDirty(npc); return true; } public static synchronized boolean addHireling(Building building, PlayerCharacter contractOwner, Zone zone, Contract contract, Item item) { int rank; if (building.getBlueprintUUID() == 0) return false; if (building.getBlueprint().getMaxSlots() == building.getHirelings().size()) return false; String pirateName = NPCManager.getPirateName(contract.getMobbaseID()); if ((byte) item.chargesRemaining > 0) rank = (byte) item.chargesRemaining * 10; else rank = 10; Mob mobile; NPC npc; if (NPC.ISWallArcher(contract)) { mobile = Mob.createMob(contract.getMobbaseID(), Vector3fImmutable.ZERO, contractOwner.getGuild(), zone, building, contract, pirateName, rank, mbEnums.AIAgentType.GUARDWALLARCHER); if (mobile == null) return false; // Configure AI and write new mobile to disk mobile.behaviourType = mbEnums.MobBehaviourType.GuardWallArcher; mobile = DbManager.MobQueries.PERSIST(mobile); // Spawn new mobile mobile.setLoc(mobile.getLoc()); return true; } if (NPC.ISGuardCaptain(contract.getContractID())) { mobile = Mob.createMob(contract.getMobbaseID(), Vector3fImmutable.ZERO, contractOwner.getGuild(), zone, building, contract, pirateName, rank, mbEnums.AIAgentType.GUARDCAPTAIN); if (mobile == null) return false; // Configure AI and write new mobile to disk mobile.behaviourType = mbEnums.MobBehaviourType.GuardCaptain; mobile = DbManager.MobQueries.PERSIST(mobile); // Spawn new mobile mobile.setLoc(mobile.getLoc()); return true; } if (contract.getContractID() == 910) { //guard dog mobile = Mob.createMob(contract.getMobbaseID(), Vector3fImmutable.ZERO, contractOwner.getGuild(), zone, building, contract, pirateName, rank, mbEnums.AIAgentType.GUARDCAPTAIN); if (mobile == null) return false; // Configure AI and write new mobile to disk mobile.behaviourType = mbEnums.MobBehaviourType.GuardCaptain; mobile = DbManager.MobQueries.PERSIST(mobile); // Spawn new mobile mobile.setLoc(mobile.getLoc()); return true; } npc = NPC.createNPC(pirateName, contract.getObjectUUID(), Vector3fImmutable.ZERO, contractOwner.getGuild(), zone, (short) rank, building); if (npc == null) return false; npc.setObjectTypeMask(MBServerStatics.MASK_NPC); npc.setLoc(npc.bindLoc); InterestManager.setObjectDirty(npc); 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 mbEnums.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; } public static void rebuildMine(Building mineBuilding) { setRank(mineBuilding, 1); mineBuilding.meshUUID = mineBuilding.getBlueprint().getMeshForRank(mineBuilding.rank); // New rank mean new max hit points. mineBuilding.healthMax = mineBuilding.getBlueprint().getMaxHealth(mineBuilding.rank); mineBuilding.setCurrentHitPoints(mineBuilding.healthMax); mineBuilding.getBounds().setBounds(mineBuilding); } public static void setRank(Building building, int rank) { int newMeshUUID; boolean success; // If this building has no blueprint then set rank and exit immediatly. if (building.blueprintUUID == 0 || building.getBlueprint() != null && building.getBlueprint().getBuildingGroup().equals(BuildingGroup.MINE)) { building.rank = rank; DbManager.BuildingQueries.CHANGE_RANK(building.getObjectUUID(), rank); 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 jobContainer = building.getTimers().get("UPGRADE"); if (jobContainer != null) { if (!JobScheduler.getInstance().cancelScheduledJob(jobContainer)) Logger.error("failed to cancel existing upgrade job."); } // Attempt to write to database or delete the building // if we are destroying it. if (rank == -1) success = DbManager.BuildingQueries.DELETE_FROM_DATABASE(building); else success = DbManager.BuildingQueries.updateBuildingRank(building, rank); if (!success) { Logger.error("Error writing to database UUID: " + building.getObjectUUID()); return; } building.isDeranking.compareAndSet(false, true); // Change the building's rank building.rank = rank; // New rank means new mesh newMeshUUID = building.getBlueprint().getMeshForRank(building.rank); if ((building.getBlueprint().getBuildingGroup() == BuildingGroup.TOL) && (building.rank == 8)) newMeshUUID = Realm.getRealmMesh(building.getCity()); building.meshUUID = newMeshUUID; // New rank mean new max hitpoints. building.healthMax = building.getBlueprint().getMaxHealth(building.rank); building.setCurrentHitPoints(building.healthMax); if (building.getUpgradeDateTime() != null) setUpgradeDateTime(building, null, 0); // If we destroyed this building make sure to turn off // protection if (building.rank == -1) building.protectionState = mbEnums.ProtectionState.NONE; // update object to clients building.refresh(true); if (building.getBounds() != null) building.getBounds().setBounds(building); // Cleanup hirelings resulting from rank change cleanupHirelings(building); building.isDeranking.compareAndSet(true, false); } public static Building getBuildingAtLocation(Vector3fImmutable loc) { for (AbstractWorldObject awo : WorldGrid.getObjectsInRangePartial(loc, 64, MBServerStatics.MASK_BUILDING)) { Building building = (Building) awo; if (building == null) continue; if (Bounds.collide(loc, building.getBounds())) return building; } return null; } }