Public Repository for the Magicbane Shadowbane Emulator
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

859 lines
28 KiB

// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
// 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<Integer, ArrayList<BuildingLocation>> _stuckLocations = new HashMap<>();
public static HashMap<Integer, ArrayList<BuildingLocation>> _slotLocations = new HashMap<>();
public static HashMap<Integer, ConcurrentHashMap<Integer, BuildingFriends>> _buildingFriends = new HashMap<>();
public static HashMap<Integer, ConcurrentHashMap<Integer, Condemned>> _buildingCondemned = new HashMap<>();
public static HashMap<Integer, ArrayList<Vector3fImmutable>> _buildingPatrolPoints = new HashMap<>();
public static int getAvailableSlot(Building building) {
ArrayList<BuildingLocation> 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<BuildingLocation> 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;
}
}